2024 Seccon13 Online Web | Writeup
ssrf-self 非常超模的一题,能做出来的都是狠人(当然不排除很大一部分是猜对的,实际分析起来确实复杂) 先来看代码 用bun起的服务,这个bun是个什么呢?可以理解成一种全新的nodejs,对nodejs很多功能进行了重构提升性能 非常正常的包引用。接着来看逻辑代码。 根路由判断了query是否给flag传值,如果传了才能继续跳转到其他路由。也就是说,不管访问的是什么,因为有next事件的存在,所以必须经过根路由的这个检查 携带正确的flag值访问flag路由就能得到flag。但我们肯定不知道flag是什么 最后就是ssrf路由,会把请求拿去URL类做实例化,并判断是否正确与LOCALHOST进行拼接,重新赋值pathname为flag,添加参数flag和正确的flag值,代替我们访问/flag路由。 乍一看好像直接访问/ssrf就应该返回给我们正确的flag才对,但问题就在于根路由的next,必须先赋值正确的flag值才能访问ssrf 看到这里我就在想,append是会把flag覆盖过去还是把flag变成数组添加一个值?用附件起一个服务试试 为了看到传到flag路由的具体内容是什么,我们需要修改一下代码 注意只能存在一个res.send,否则会报错 先来看看直接传flag值会发生什么 跳到了正则表达式的第二个结果里。而我们的目的肯定是第一个结果,所以来看看传进来的req.query是什么 会发现append最终是把我们的flag参数变成了数组,所以不会跳到第一个结果。 那要怎么绕过呢?猜测漏洞点应该就出在 URL对req.url的处理,可能存在什么符号或格式让URL类在过了根路径的检查后仍然认为flag这个参数不存在 bun本身自带的express框架对req.query的识别与URL对req.query的识别不一致 而两种思路也正对应了这题的两种解法 1./ssrf?flag[=] 我们在php里经常用?a[]=1&b[]=2来绕过md5比较,在js里也有类似的用法。比如我们想用url给某个数组的某个键值赋值,可以用这种方法 ?flag[1]=1 那要是我们的键值不是数字,而是别的什么呢 接下来就是想办法怎么让req.query觉得存在flag参数,又让URL觉得不存在flag参数 方法就是标题里的payload。当传进来的值是flag[=]时,req.query会觉得你在给flag的=键赋值,URL会觉得你在给flag[赋值 2./ssrf?flag\ufeff 先来看看bun带了什么模块 负责url处理的猜测是parseurl。且在express/lib/requests.js里也能看到引用 到github搜项目https://github.com/pillarjs/parseurl/blob/1.3.3/index.js 读取到URL数据后先判断是不是空的,如果不为空,将在缓存中查找是否存在(fresh方法)。如果不存在,将进入fastparse 判断url是否是字符串且以/开头。如果不是则交给bun更底层的parse函数去处理 这个parse函数又是怎么处理的呢?这就得进到bun到项目文件里一探究竟了https://github.com/oven-sh/bun/blob/bun-v1.1.36/src/node-fallbacks/url.js#L49 这一段主要就是对反斜杠进行处理,然后赋值url给rest 紧接着将rest后面的空白符删除 什么是空白符?有很多,其中有个比较不常见的叫BOM(Byte Order Mark)分隔符 接着先回到parseurl往下继续分析 如果没跳进上面的if,就会来到这个for循环。简单来说就是先以?为分隔符分出三个部分,再对各种特殊字符作处理,但有一个case格外亮眼,就是0xfeff 意思就是,如果我们的发送的数据里带了0xfeff(也就是\ufeff)也会跳进parse进行处理 而根据我们上面对parse的分析,他会将包括0xfeff在内的空白符删掉,导致在req.query做判断时会认为我们传递进来的参数就是flag而不是flag\ufeff,因此就能绕过undefined 但URL类并不会认为flag和flag\ufeff是一样的,他会认为这是两个不一样的变量,在append时就会单独起一个flag变量赋值 持续更新ing……