GYCTF2020-Ez-Express
从这个题学到不少东西,记录一下。
初识原型链
首先这题是有个原型链污染,js中每个类都有个属性__proto__
,指向他的基类,他会继承__proto
所拥有的特性。
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)。
在js中,当我们调用一个对象的一个属性时,会首先在对象自身寻找这一属性,如果自身找不到则会寻找__proto__
,找不到就继续往上找,以此类推。
哪些情况下原型链会被污染
在含有能够控制数组(对象)的“键名”的操作即可,一般是以出现:
merge和clone对象不安全的对象递归合并
以merge,因为merge执行的就是递归,为例,先构造一个简单的merge函数
js
const merge = (target, source) => { // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties for (const key of Object.keys(source)) { if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key])) } // Join `target` and modified `source` Object.assign(target || {}, source) return target } function Person(name,age,gender){//构造一个person类 this.name=name; this.age=age; this.gender=gender; } let newperson=new Person("test1",22,"male"); let job=JSON.parse('{"title":"Security Engineer","country":"China","__proto__":{"x":1}}');//新建一个job对象 merge(newperson,job);//这个job对象有用title、contry、__proto__属性,并对其进行赋值 console.log(newperson); console.log(Person.prototype);//此时per的原型已经被改变为x=1
这里解释一下,merge将这几个键值作为一个属性合并到newperson这个对象中了,merge函数执行的其实可以认为是
Person.job.title=...... Person.job.__proto__=....
这个时候job的原型是Person,所以Person的值就发生了改变。
按路径定义属性
有些JavaScript库的函数支持根据指定的路径修改或定义对象的属性值。通常这些函数类似以下的形式:theFunction(object, path, value),将对象object的指定路径path上的属性值修改为value。如果攻击者可以控制路径path的值,那么将路径设置为_proto_.theValue,运行theFunction函数之后就有可能将theValue属性注入到object的原型中。
引用自https://hwwg.github.io/2021/07/22/js%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/#%E5%93%AA%E4%BA%9B%E6%83%85%E5%86%B5%E4%B8%8B%E5%8E%9F%E5%9E%8B%E9%93%BE%E4%BC%9A%E8%A2%AB%E6%B1%A1%E6%9F%93%EF%BC%9F
审计代码
这个题有个www.zip
,下载下来是有源代码的,进行审计。
主要的逻辑如下
var express = require('express');
var router = express.Router();
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}
return undefined
}
router.get('/', function (req, res) {
if(!req.session.user){
res.redirect('/login');
}
res.outputFunctionName=undefined;
res.render('index',data={'user':req.session.user.user});
});
router.get('/login', function (req, res) {
res.render('login');
});
router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>")
}
req.session.user={
'user':req.body.userid.toUpperCase(),
'passwd': req.body.pwd,
'isLogin':false
}
res.redirect('/');
}
else if(req.body.Submit=="login"){
if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;
}
else{
res.end("<script>alert('error passwd');history.go(-1);</script>")
}
}
res.redirect('/'); ;
});
router.post('/action', function (req, res) {
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});
router.get('/info', function (req, res) {
res.render('index',data={'user':res.outputFunctionName});
})
module.exports = router;
一般ctf中的原型链污染会出现在merge
,clone
一类的函数中,这里看到有merge函数,clone又对它封装了一层,最后唯一调用clone的是/action
路由,但是这里要求管理员登录,所以先研究一下登录。
他要求admin登录但是过滤了admin,这里可以用上p神博客的一个小trick。
"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'
"K".toLowerCase() == 'k'
js太神辣🥵
登录解决了
在action路由我们可以通过上传json让其解析为键值对达成控制runtime。
再来看info路由,这里调用了个没有定义的outputFunctionName
,可以污染outputFunctionName来rce,具体的原理得分析一下ejs的渲染。
ejs渲染分析
分析思路方法来自于https://evi0s.com/2019/08/30/expresslodashejs-%e4%bb%8e%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93%e5%88%b0rce/
主要是探究一下render函数后面到底发生了什么
----12.17更----
时间拖太久了懒得再调一次了,总之就是ejs里面有一坨字符串拼接会把outputFunctionName
拼进去直接中断渲染提前return造成代码执行。当然这里的outputFunctionName
是由我们的原型链污染来控制的,在我们有原型链污染的前提之下,我们可以控制基类的成员。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)