前言

前言:在刚过去的祥云杯中,web类型题目有一个rustwaf的题目(corCTF 2022 simplewaf 改编而来),其中有个关于readFileSync的trick,这里分析一下

源码分析

看下官方文档对readFileSync的解释,注意看,path可以是字符串,Buffer类对象,URL类对象等等

image-20221101105307823

调用链

1
2
3
4
5
6
fs.readFileSync
-->fs.openSync(lib/fs.js)
--> getValidatedPath(lib/internal/fs/utils.js)
-->toPathIfFileURL(lib/internal/url.js)
-->fileURLToPath(/lib/internal/url.js)
-->getPathFromURLPosix(lib/internal/url.js)

node/fs.js at v18.x · nodejs/node (github.com)

lib/fs.js,查看readFileSync的源码,其调用了fs.openSync,并将path和options作为参数传入

image-20221101112915789

跟进fs.openSync(lib/fs.js),其调用了getValidatedPath函数,将path作为参数传入

image-20221101113136386

跟进getValidatedPath(/lib/internal/fs/utils.js),根据函数名,也就是获取路径的意思

这里调用了toPathIfFileURL,将path传入作为参数,字面意思(如果是URL实例,就转换为路径)image-20221101114820054

跟进toPathIfFileURL(lib/internal/url.js),如果不是URL实例,就直接返回fileURLOrPath;如果是URL实例,就返回fileURLToPath(fileURLOrPath);

image-20221101114951320

我们跟进isURLInstance(/lib/internal/url.js)看下是怎么判断是否为URL实例的

我们发现判断是否为URL实例的依据就是是否含有href和origin属性,所以我们添加这两个属性之后就可以伪造URL实例

image-20221101145324153

而判断URL实例成功后,则返回fileURLToPath(fileURLOrPath),跟进(/lib/internal/url.js)

发现需要path.protocol为file:,然后根据操作系统,调用对应的函数

image-20221101151618908

我们看下getPathFromURLPosix(url)

hostname属性需要为空,然后对pathname进行url解码

image-20221101154255347

调试分析

为什么一开始没有源码分析呢?

因为刚开始从readFileSync函数下断点,进不去函数里面,后来发现了一篇文章corCtf2022一道有意思的node题 - 腾讯云开发者社区-腾讯云 (tencent.com),配了下环境,就可以了,就是把skipFiles中node_internals给注释掉

image-20221101155151938

前面的跳过,主要看最后的

for循环用于检验传入的URL实例中的属性 pathname 中是否包含 url编码后的 / ,若包含则抛出一个异常

在1410行,将传入的URL实例中 pathname 中的值进行url解码并返回

image-20221101155717217

这也就是最终fs.openSync函数中最终获取的path

image-20221101160131202

payload

所以最后该json实例需要满足如下条件:

  1. .href exists
  2. .origin exists
  3. .protocol === ‘file:’
  4. .hostname === ‘’
  5. .pathname is /app/flag.txt 这里我们知道了传入之后,其会进行一次url解码

参考链接

  1. File system | Node.js v19.0.0 Documentation (nodejs.org)
  2. corCTF 2022 Challenges (brycec.me)
  3. corCtf2022一道有意思的node题 - 腾讯云开发者社区-腾讯云 (tencent.com)