本文由掌控安全学院 - 我是大白 投稿
nodejs调试记录
问题 & 环境搭建
环境搭建
1,安装nodejs,这个直接网上找教程就行
2,安装vscode
3,我是直接在nodejs安装路径下,创建了一个app文件,然后在里面写js代码的。
然后接下来,就是写代码,进行调试,以下代码是让node监听33456端口,然后,获取file参数,返回指定文件内容的一个功能。
const
express =
require
(
"express"
);
const
fs =
require
(
"fs"
);
const
app = express();
const
PORT = process.env.PORT ||
33456
;
app.get(
"/"
,
(
req, res
) =>
{
try
{
res.setHeader(
"Content-Type"
,
"text/html"
);
console
.log(req.query.file);
res.send(fs.readFileSync(req.query.file ||
"index.html"
).toString());
}
catch
(err) {
console
.log(err);
res.status(
500
).send(
"Internal server error"
);
}
});
app.listen(PORT,
()
=>
console
.log(
`web/simplewaf listening on port
${PORT}
`
));
问题
我在vscode中点击运行和调试
后,报错:windows Cannot find module 'express'
,提示说找不到express这个模块
问题解决:https://juejin.cn/post/7159201118457692174
在写代码的目录下打开cmd,然后运行一下npm install express
进行模块安装即可。
调试过程
现在当前目录创建一个launch.json配置文件,一般在vscode左上角有创建提示,点击即可创建,然后将node_internals那一行注释掉,在将js文件改成实际的js文件名。
//launch.json:
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version"
:
"0.2.0"
,
"configurations"
: [
{
"type"
:
"node"
,
"request"
:
"launch"
,
"name"
:
"启动程序"
,
"skipFiles"
: [
// "<node_internals>/**"
],
"program"
:
"
${workspaceFolder}
\main.js"
}
]
}
对于下面这句话不是很理解,因此尝试自己手动调试看结果会更理解一些。
express 使用 qs npm 模块来提供 req.query.file (file 为查询字符串参数名) ,这意味着它可以与字符串以外的其他类型一起使用。
如:?file[]=
1
&file[]=
2
或者 ?file=
1
&file=
2
,这样最后 req.query.file 获取到的就是一个数组 [
'1'
,
'2'
] ; 还有 ?file[a]=b&file[c]=d , req.query.file 获取到的是一个对象 {
'a'
:
'b'
,
'c'
:
'd'
}
首先,我们得先理解:req.query.file
的作用是,req.query是获取URL中传递的参数,req.query.file就是获取URL中传递的file参数,同时呢,它除了字符串类型的传参file=abcd
,还可以传参数组file=1&file=2
以及对象file[a]=b
代码就是上面的代码,我们直接访问本地的33456传参查看结果:
可以看到,当我们进行重复传参file=b&file=a
,req.query.file
当我们传参数组,那么得到的就是一个对象,file[a]=b&file[c]=d,其中含有a属性,值为b,c属性,值为d
readFileSync调试
有以上前置知识,接下来我们进入readFileSync调试,来看看如何拿到/app/flag.txt中的flag.
在以上传参过程中,我们在vscode中,可以很明显的看到readFileSync的报错:The "path" argument must be of type string or an instance of Buffer or URL. Received an instance of Object
,这个报错提示我们,readFileSync方法接收的路径必须是 字符串 、Buffer实例化对象以及URL实例化对象
而我们的传参file=b&file=a 以及file[a]=b&file[c]=d,一个是数组,一个是普通对象,显然readFileSync方法是不接受的。
接下来我们另写一段代码,并且在当前目录创建一个flag.txt,进行验证:
const
fs =
require
(
'fs'
);
// 注意:路径需要绝对路径
let
file1 =
new
URL(
"file:///G:/Software/nodejs/app/fl%61g.txt"
);
// URL类是内置的类,可以直接new URL对象
let
file2 = Buffer.from(
"G:/Software/nodejs/app/flag.txt"
);
// Buffer
let
file3 =
"G:/Software/nodejs/app/flag.txt"
;
// 字符串
console
.log(fs.readFileSync(file1).toString());
console
.log(fs.readFileSync(file2).toString());
console
.log(fs.readFileSync(file3).toString());
正常读取flag,那么我们在做simplewaf这一题时,首先就是要想办法传入一个能够读取到flag的URL类,或者字符串,至于Buffer貌似是没法构造的【毕竟我不是这方面的master】,并且在路径中存在URL编码也同样可以正常读取文件。
接下来,我们来进行下断点调试,看看当readFileSync方法接收到参数之后,在其内部是如何进行处理的,看看是否能通过其工作原理,来达到我们绕过的目的。
我们将其他无关紧要的代码全部注释掉,只留下核心代码进行DEBUG。
const
fs =
require
(
'fs'
);
let
file1 =
new
URL(
"file:///G:/Software/nodejs/app/fl%61g.txt"
);
console
.log(fs.readFileSync(file1).toString());
在第三行下断点,代码运行后,我们跟进,进入到readFileSync方法内部:
第一行代码,根据英文释义,这应该是设置对文件如何进行操作的标志位,默认应该是r
读取操作。这一行可以直接步过
第二行是类似于文件描述符样的东西,这一行也可以直接步过
第三行,是一个三目运算符,由于前面一行代码运行完之后,isUserFd为false,所以会进入到表达式fs.openSync当中。
此时path会传入到openSync方法当中进行一些处理,可以看到openSync的代码,我们在步过方法当中的第一行代码之后,传入的path从"file:///G:/Software/nodejs/app/fl%61g.txt"
变成了"file:///G:/Software/nodejs/app/flag.txt"
,这说明在方法getValidatedPath
中对我们传入的path进行了解码操作。
我们可以进入到getValidatedPath
内部,看看它对我们传的URL对象是如何处理的:可以看到,第一行代码就调用了toPathIfFileURL
,那么进入这个方法看看
进来之后,可以看到由调用了isURLInstance
方法,继续跟进,看看该方法如何处理
跟进之后可以看到,首先判断是否为空,然后判断是否有href属性以及 origin属性,最终的结果返回一个布尔值,我们可以判断一下,因为我们传入的是一个URL对象,因此 不等于null,并且是具有href和origin属性的,因此三个都为true(这个地方如果有不懂的,可以停止程序,出去将这三者都输出一下就知道结果了),最终返回结果true。
代码继续往下运行:因为isURLInstance方法得出结果为true,所以!true即为false,代码运行到1515行,此时又调用了一个fileURLToPath方法对URL对象path
进行处理,进入该函数看看:
第一个if判断,判断是不是string类型,显然不是,是一个URL对象
第二判断,判断是不是URL对象,是
第三个判断,判断protocol是不是file:,是file:,来到return这一行,不是的话就抛出异常;
来到return 三目运算,isWindows是判断当前是linux系统还是windows系统,因为我这里调试使用的是windows系统,所以isWindows是有值的,所以会进入到 getPathFromURLWin32 方法
进入该方法看看:大致阅读代码,会从传入的url中检测是否包含%
,并且不能包含有编码后的\
,/
,否则会抛出异常,然后会对字符串进行替换,将/
全部替换成\
,然后使用decodeURIComponent方法进行URL解码。
解码之后还会检测是否为绝对路径,如果不是绝对路径会抛出异常,返回URL解码后的路径。
再往下看,判断hostname属性是否为空,不为空则会返回一个UNC路径,为空往下继续(在对应的linux方法中只判断hostname是否为空,如果为空则继续,不为空则抛出异常,毕竟linux下默认是没有SMB服务的):
接下来获取盘符,判断盘符是否合法a-Z,接着获取冒号,判断路径是否为绝对路径(这个判断在windows对应的方法下有,linux下没有,毕竟linux下没有盘符的说法):
最终,将路径前面多余的\
去掉并返回:
出来之后便进入到validatePath函数,
validatePath函数主要是对路径是否为字符串以及是否为空的检测,如果未通过则抛出异常,显然是通过的,直接步过这个函数。
之后就是对flag.txt的读取操作了。
因此,这里我们可以画个草图方便理解:
因此,我们要获取flag需要满足以下条件:
1,传参要为URL对象(避免引发readFileSync报错)
2,存在href
属性
3,存在origin
属性(只要具有href、origin属性且不为null,isURLInstance方法就返回true)
4,protocol
的属性值必须为file:
5,hostname`属性值必须为空
6,进行双重url编码(因为express模块会进行一次编码),绕过对flag
关键字的检测(并且在getPathFromURLWin32或getPathFromURLPosix方法中会进行一次URL解码,因此可以进行URL编码绕过)
7,pathname
设置为要读取的文件路径,注意包含flag
字符就需要进行url编码绕过
linux:
?file[href]=1&file[origin]=2&file[protocol]=file:&file[hostname]=&file[pathname]=/etc/passwd
?file[href]=1&file[origin]=2&file[protocol]=file:&file[hostname]=&file[pathname]=/app/fl%2561g.txt
windows:
?file[href]=1&file[origin]=2&file[protocol]=file:&file[hostname]=&file[pathname]=/G:/Software/nodejs/app/fl%2561g.txt
最后解释一下为什么windows下,路径长这样/G:/Software/nodejs/app/fl%2561g.txt
,前面多了一个斜杠
因为在getPathFromURLWin32
方法中,letter会获取盘符,取的是字符串中的下标为1的字符,因此需要多加一个斜杠,并且seq取冒号,取得是字符串中的下标为2的字符。在使用file协议的时候,通常也会是用file:///路径
的形式。
参考资料
https://cloud.tencent.com/developer/article/2123023
https://brycec.me/posts/corctf_2022_challenges#simplewaf
感谢大佬的分享,【膜膜膜】
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径, 所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):新手入门级 | nodejs调试
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论