题目

打开是一段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 服务器

[NSSCTF]prize_p2-LMLPHP

所谓提供不同路由就是说

路由是用于定义应用程序如何响应客户端发起的不同 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

[NSSCTF]prize_p2-LMLPHP

多试几次 但是最多显示的字符是32个

所以本题的关键是在这里setTimeout(()

也就是说

要传a的值要符合随机生成的三位数

b的值是flag的路径

c的值决定延迟时间 在86400 * 1000之间取大,也就是说如果a的值不匹配至少延迟一天

所以我们这里先查询一下这个setTimeout()函数的绕过

解题思路

还真的找到绕过方式

看这个函数的官方文档setTimeout(callback, delay[, ...args]) | Node.js API 文档

[NSSCTF]prize_p2-LMLPHP

那么我们传参c大于2147483647即可绕过

然后我试了一下

/getflag?a=111&b=/app/src/flag.txt&c=2147483648

发现不行

重返代码看看

[NSSCTF]prize_p2-LMLPHP

原来这里把flag.txt文件删掉了

那怎么绕过呢

看了别人的wp

原来可以用文件描述符就是proc这个目录

之前也接触过

但是没有完全理解

现在再来学习一下

滑动验证页面

一、文件描述符概念

  Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。

这里引用一下

之前就学过self文件

[NSSCTF]prize_p2-LMLPHP

也就是说

/app/src/flag.txt 文件被 open() 打开,但最终没有关闭,虽然删除了该文件,但在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容。/proc/self/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/self/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。id可爆破猜测获得。

我这里爆了一下 好像把服务器爆宕了 (看别的wp这么说的)

最后得到18

payload:

05-05 00:05