题目
打开是一段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操作的系统调用都会通过文件描述符。
这里引用一下
我理解是打开一个文件就会创建一个进程,就会返回一个文件描述符,而这个文件描述符就指向这个打开的文件。很巧的是这题打开了flag.txt却没有关闭,我们可以通过文件描述符来获取到被删除文件的内容。linux的/proc目录是一个伪文件系统,linux一切皆文件,linux常见的进程也要变成文件存储在/proc目录下。在/proc目录下有很多以数字为名字的文件夹,就是进程运行时对应的进程号,而在这些文件夹下有一个fd文件夹,用于存放这个进程所拥有的文件描述符(数字)
之前就学过self文件
也就是说
/app/src/flag.txt 文件被 open() 打开,但最终没有关闭,虽然删除了该文件,但在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容。/proc/self/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/self/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。id可爆破猜测获得。
我这里爆了一下 好像把服务器爆宕了 (看别的wp这么说的)
最后得到18
payload:
/getflag?a=111&b=/proc/self/fd/18&c=2147483648