-
-
[原创]polarisctf招新赛-2026-web方向wp
-
发表于: 1天前 634
-
题目给出了源代码,直接白盒。有个文件执行的功能,试了下/flag直接出了
这题的考点是python对象注入也是给了源代码,分析一下
merge函数有个常见的漏洞,原型链污染,不过这是python环境,应该叫对象污染instance.config.filename可以被覆盖因为post传参是json格式,构造payload
这题真的考点是jwt伪造+文件上传这题直接扫描了目录,发现有个flag.php,访问就得到flag被修复了的题目是这样的
login页面获得token,没有找到key泄露的地方,直接爆破key:cdef
构造admin的token登录
页面允许上传了后端只对MIME进行校验,文件后缀可以找包修改,对文件内容进行了限制,过滤了php,system。可以用拼接绕过。
uploads/71324fb40f788f93ad5285cdc0d3fc28_4.php?cmd=已经getshell了执行cat /flag
这题的考点是个Node.js原型链污染也是白盒,做下代码审计
这里好绕过,可以用constructor.prototype代替
接着看
现在思路已经很明显了,通过/api/config上传json形式的键值,因为
if (key === 'NODE_OPTIONS') { const value = process.env[key] || "";
这个指定了攻击路线污染一下NODE_OPTIONS,按照出题人的惯例,应该是让我们读/flag
这题的考点是jsc沙箱逃逸题目描述的提示很明显指纹识别一下,看看有没有存在的引擎版本漏洞
我查了一下并没有明显的历史漏洞查看源代码,<title>BunEdge Platform – Serverless Edge Compute</title> 这是一个边缘计算平台,以我的理解,这是一个将我本地数据传输到离我近的节点处的平台。
这块区域允许我们输入 TypeScript 代码,还有一个部署的按钮。
这里可以看到部署后的反馈,这样意味着我们可以RCE得到很多信息
这里规定了api,考虑先从fetch(req)入手,显然它能使用的方法是有限的,我们要弄清这个程序的内容。第一步找全局变量
不过globalThis被过滤了,可以用this代替,不过这样出来的是这个沙箱定义的全局变量不是我们要的。结合一下前面学得原型链污染,尝试绕到上面一个全局,用到constructor,不过也被过滤了
我先尝试了下十六进制/unicode转义
''这个不能用,也就是说没有转义。那就换个转义方式,String.fromCharCode:可接受一个指定的Unicode 值,然后返回一个字符串
看着好像平平无奇,但我们确实拿到了全局对象,现在来枚举全局键。
可谓是庞大一坨啊,在里面找能用到的,题目也是有点小关联的,Bun提供的对象有读取文件的功能,来验证一下有没有
有file,可以读取文件了,按照前面几题,应该也是读/flag

这个题目主要就是考察代码审计的能力几个接口依次介绍一下
展示所有可用的api接口 
先看最重要也是最明显的地方吧,这里提到了flag是怎么出来的
有很多的func调用啊,一步步来。
这里告诉我们从哪里提取的token,规定了必须在Authorization请求头中Bearer token
这是在从数据库中提取rows,DESC倒叙,所有是最后一条,这里需要跳转到如何写入数据库的->/api/caps/sync。COALESCE()这个函数用于从左到右依次检查参数,返回第一个非NULL的值,后面会用到。
接到来自上面一个func提取到的CapabilityView,限制了role必须是maintainer,lane必须是release。
这是获得expected的函数,sha1加密,内容是sid,nonce,RUNNER_KEY的拼接,这里我们需要获得RUNNER_KEY才能在本地算出正确的proof,const RUNNER_KEY = process.env.RUNNER_KEY || 'dev-runner-key';在代码的开头,告诉我们RUNNER_KEY在环境变量中,通过执行process.env。
这个看输出文字,应该是在防止重放,在代码的开始就标识了,挑战一次只有3分钟
获得guest身份的JWT令牌,最普通的一集。
这里承接/api/release/claim的数据库操作
这个接口竟然限制guest就能访问
row中的元素排序由rows.sort决定,先取source比较,若相等比时间戳,若不等用localeCompare,按字母表顺序排。const keepRole = input.keepRole !== false;如果这里的keepRole=false,if (keepRole) {row.role = 'guest';}。这里的role就不会被赋值,也就是role=NULL,还记得/api/release/claim中的COALESCE(role, 'maintainer'),role=NULL,这里返回的就是maintainer,同理lane也是,完美绕过。keepRole和keepLane存放在ops中,ops必须是数组形式,长度最少为2,且元素得是对象,由于我要传的是{ "keepRole": false, "keepLane": false },这个在json里是一个对象算一个元素,所以至少写两边。还有要在对象中加入source

这个接口的开头和/api/release/claim一样,也是只允许role=maintainer
这里创建了一个执行函数runner,函数的作用域仅在参数里面,return('+expr+')中的expr如果是用户可控,这里就是执行命令并返回结果。这里runner()的参数是context,这个对象经过冻结处理,里面没有process属性,想要执行需要越狱到全局作用域,方法就是constructor越到构造函数,自己写函数。js里面什么都可以是对象,[]这是个数组对象,它需要两次constructor才能到function构造器。还有就是黑名单的存在,最先想到的是拼接绕过,因为是先检查在拼接。
在/api/release/execute中,expr=expression,规定了是字符串类型才给赋值,不然是空。这里也是function构造器的特性,能将字符串变成可执行命令。 
这里很好理解,没过过多的function,只是验证了下guest身份,然后开始了挑战,发放了nonce。
返回一些一些信息,不太重要
从数据库读取初始基本能力信息
获得一些状态信息。