题目
打开是一段js代码
// 导入所需的模块
const { randomBytes } = require('crypto'); // 导入 crypto 模块,用于生成随机字节
const express = require('express'); // 导入 Express.js 模块,用于构建 Web 应用程序
const fs = require('fs'); // 导入文件系统模块,用于文件操作
// flag.txt 文件的路径
const fp = '/app/src/flag.txt';
// 创建 Express 应用程序
const app = express();
// 用于存储 flag 数据的缓冲区
const flag = Buffer(255);
// 以只读模式打开 flag.txt 文件
const a = fs.open(fp, 'r', (err, fd) => {
// 将文件的前 44 个字节读取到 flag 缓冲区中
fs.read(fd, flag, 0, 44, 0, () => {
// 读取完文件内容后,删除 flag.txt 文件
fs.rm(fp, () => {});
});
});
// 根路径的路由
app.get('/', function (req, res) {
// 以文本/javascript;charset=utf-8 格式发送此文件的内容作为响应
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync(__filename));
});
// 获取 flag 提示的路由
app.get('/hint', function (req, res) {
// 根据随机数发送 flag 缓冲区的一个片段作为提示
res.send(flag.toString().slice(0, randomBytes(1)[0] % 32));
});
// 获取 flag 的路由
app.get('/getflag', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
try {
let a = req.query.a;
// 检查提供的值是否与随机生成的值匹配
if (a === randomBytes(3).toString()) {
// 如果匹配,作为响应发送指定文件的内容
res.send(fs.readFileSync(req.query.b));
} else {
// 如果不匹配,在一定时间延迟后发送指定文件的内容(如果未提供延迟时间,默认为一天)
const t = setTimeout(() => {
res.send(fs.readFileSync(req.query.b));
}, parseInt(req.query.c) ? Math.max(86400 * 1000, parseInt(req.query.c)) : 86400 * 1000);
}
} catch {
// 捕获任何错误并发送问号作为响应
res.send('?');
}
});
// 启动服务器监听 80 端口
app.listen(80, '0.0.0.0', () => {
console.log('开始监听');
});
代码分析
先问g大哥 简单知道代码表达的意思
这是一段Express.js 服务器
所谓提供不同路由就是说
路由是用于定义应用程序如何响应客户端发起的不同 HTTP 请求的机制。换句话说,路由决定了当用户访问特定的 URL 时,服务器应该做出什么样的响应。
Express.js 允许开发人员使用 app.get()
、app.post()
、app.put()
、app.delete()
等方法来定义不同类型的路由。其中,app.get()
用于处理 HTTP GET 请求,app.post()
用于处理 HTTP POST 请求,依此类推。
例如,本题代码中,我们可以看到以下几个不同的路由:
根路径路由:
app.get('/', function (req, res) { res.set('Content-Type', 'text/javascript;charset=utf-8'); res.send(fs.readFileSync(__filename)); });
这个路由处理了根路径 '/'
的 GET 请求,当用户访问根路径时,服务器会发送当前文件的内容作为响应。
提示路由:
app.get('/hint', function (req, res) { res.send(flag.toString().slice(0, randomBytes(1)[0] % 32)); });
这个路由处理了 /hint
路径的 GET 请求,当用户访问 /hint
路径时,服务器会发送 flag 的一个片段作为提示。
分析到这里 火速去看一下hint
得到片段flag
多试几次 但是最多显示的字符是32个
所以本题的关键是在这里setTimeout(()
也就是说
要传a的值要符合随机生成的三位数
b的值是flag的路径
c的值决定延迟时间 在86400 * 1000之间取大,也就是说如果a的值不匹配至少延迟一天
所以我们这里先查询一下这个setTimeout()函数的绕过
解题思路
还真的找到绕过方式
看这个函数的官方文档setTimeout(callback, delay[, ...args]) | Node.js API 文档
那么我们传参c大于2147483647即可绕过
然后我试了一下
/getflag?a=111&b=/app/src/flag.txt&c=2147483648
发现不行
重返代码看看
原来这里把flag.txt文件删掉了
那怎么绕过呢
看了别人的wp
原来可以用文件描述符就是proc这个目录
之前也接触过
但是没有完全理解
现在再来学习一下
一、文件描述符概念
Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。
这里引用一下
之前就学过self文件
也就是说
/app/src/flag.txt 文件被 open() 打开,但最终没有关闭,虽然删除了该文件,但在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容。/proc/self/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/self/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。id可爆破猜测获得。
我这里爆了一下 好像把服务器爆宕了 (看别的wp这么说的)
最后得到18
payload: