loading...
NodeJS常见漏洞总结(上)
Published in:2023-02-05 | category: Node.js

0x00前言

目前Web安全经过日新月异的发展,许多新兴框架都替换掉了原来老旧的技术。而在CTF上,NodeJS安全的题目也出的越来越频繁,常见的一般为NodeJS原型链污染。预计未来NodeJS也会像JAVA,PHP等后端语言一样同样在安全问题上受到更多的重视。

简介

Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型, 让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHPPythonPerlRuby等服务端语言平起平坐的脚本语言

0x01启动服务

1
2
3
4
5
6
7
8
9
10
var http = require('http');
http.createServer(function(request,response){
//发送HTTP头部
//内容类型:text/plain
response.writeHead(200,{'content-type':'text/plain'});

//发送响应数据
response.end('response hello world\n');
}).listen(8888);//监听端口
console.log('Server running');

0x02大小写转换攻击

toUpperCase()————小写转大写

对于toUpperCase(): 字符"ı""ſ" 经过toUpperCase处理后结果为 "I""S"

toLowerCase()————大写转小写

对于toLowerCase(): 字符"K"经过toLowerCase处理后结果为"k"注:(这个K不是大写字母K)

0x03弱类型比较漏洞

数字与字符比较

数字与字符比较时,会将纯数字字符串强制转换为数字类型再比较。

字符串与字符串比较时,会将字符串的首位字符转换为ASCII码进行比较

示例

1
2
3
4
5
6
console.log(1=='1'); //true 
console.log(1>'2'); //false
console.log('1'<'2'); //true
console.log(111>'3'); //true
console.log('111'>'3'); //false
console.log('asd'>1); //false

数组间比较

空数组间比较永远返回false,数组间只比较各个数组的第一个元素值

数组与非数值型字符串比较,永远小于非数值型字符串

数组与数值型字符串比较,是取数组的第一位元素与数值型字符串进行比较

示例

1
2
3
4
5
6
console.log([]==[]); //false 
console.log([]>[]); //false
console.log([6,2]>[5]); //true
console.log([100,2]<'test'); //true
console.log([1,2]<'2'); //true
console.log([11,16]<"10"); //false

特殊类型

null是等于undefined的,但是不是全等于undefined

NaN不等于/全等于自身

示例

1
2
3
4
console.log(null == undefined) //true
console.log(null === undefined) //false
console.log(NaN == NaN) //false
console.log(NaN === NaN) //false

0x04MD5绕过

MD5是根据原文加密的一串序列,NodeJS中当两者都为对象时,MD5的值也会相等

1
a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)

代码解释:定义一个a和b对象,其中a和b为真且a的长度与b的长度相同,并且a不等于b,则它们md5的值都相等

1
2
3
4
5
6
a={'x':'1'}
b={'x':'2'}

console.log(a+"test") //[Object Object]test
console.log(b+"test") //[Object Object]test

0x05编码绕过

与一般的语言相同,NodeJS也可以编码绕过

常见编码

base64编码

1
eval(Buffer.from('Y29uc29sZS5sb2coImhhaGFoYWhhIik7','base64').toString())

16进制编码

1
console.log("a"==="\x61"); // true

unicode编码

1
console.log("\u0061"==="a"); // true

0x06常见危险函数

命令执行

exec()

该命令需要调用child_process模块

child_process是node的一个模块,负责子进程调用

创建子进程的方式:

1
2
3
4
5
异步方式:spawn、exec、execFile、fork
同步方式:spawnSync、execSync、execFileSync
经过上面的同步和异步思想的理解,创建子进程的同步异步方式应该不难理解。
在异步创建进程时,spawn是基础,其他的fork、exec、execFile都是基于spawn来生成的。
同步创建进程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。
1
require('child_process').exec('open /System/Applications/Calculator.app');	//打开计算器

eval()

1
console.log(eval("document.cookie")); //执行document.cookie

文件读写

readFile()————异步文件读

readFileSync()————同步文件读

1
2
3
4
require('fs').readFile('文件路径','utf-8',(err,data)=>{
if(err) throw err;
console.log(data);
})
1
require('fs').readFileSync('文件路径','utf-8');

writeFile()————异步文件写

1
require('fs').writeFile('文件路径','文件内容',(err)=>{});

writeFileSync()————同步文件写

1
require('fs').wirteFileSync('文件路径''文件内容');

0x07NodeJS拆分攻击

属于HTTP走私攻击,因其主要原理利用了HTTP的回车换行注入(CRLF)

CRLF

回车换行注入\r\n(%0d%0a)

引用一篇简书文章

正常数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK

Location:http://www.sina.com

但是如果改成:http://www.sina.com%0aSet-cookie:sessionid%3Dtest

服务器返回:

HTTP/1.1 200 OK

Location:http://www.sina.com

Set-cookie:sessionid=test

是一种

NodeJS中,默认的编码是latin1编码,该编码是单字节编码,不能解析高位编码unicode

当请求路径有多字节编码的unicode字符,会被截断取最低字节,比如 \u0130 就会被截断为 \u30

1
2
3
4
5
> v = "/caf\u{E9}\u{01F436}"
'/café🐶'

> Buffer.from(v,'latin1').toString('latin1')
'/café=6'

该漏洞需要在Node环境小于8时产生,在Node的10版本里修复,当检测到非法unicode编码会报错

配合CRLF

1
2
require('http').get('http://example.com/\r\n/test')._header
'GET //test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n'

其数据包变为

1
2
3
4
GET //test
HTTP/1.1
Host: example.com
Connection: close

已经通过回车换行覆盖原数据包

我在做CTF题的时候碰到一题是利用拆分攻击+SSRF再配合pug模板注入实现rce,题目来自GYCTF2020,感兴趣可以去看看

还有一个为NodeJS原型链污染内容比较多,需要先理解原型链,下一章再分析这个漏洞

Prev:
NodeJS常见漏洞总结(下)
Next:
浅谈RSA加密
catalog
catalog