黑下吹不动雪

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

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中的原型链污染会出现在mergeclone一类的函数中,这里看到有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是由我们的原型链污染来控制的,在我们有原型链污染的前提之下,我们可以控制基类的成员。

posted on   黑下吹不动雪  阅读(201)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示