hubertf

sc

Sep 28th, 2025
58
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 27.78 KB | None | 0 0
  1. $ cat /challenge/chat-server
  2. #!/usr/bin/env python
  3.  
  4. import functools
  5. import hashlib
  6. import os
  7. import sqlite3
  8. import textwrap
  9.  
  10. from Crypto.Cipher import AES
  11. from Crypto.Util.Padding import pad, unpad
  12. from flask import Flask, request, session, redirect
  13.  
  14.  
  15. HOST = os.getenv("CHAT_HOST", "127.0.0.1")
  16. PORT = int(os.getenv("CHAT_PORT", 8080))
  17. DB_PATH = os.getenv("DB_PATH", "./chat.db")
  18.  
  19.  
  20. app = Flask(__name__)
  21.  
  22.  
  23. def initialize_db():
  24. db = sqlite3.connect(DB_PATH)
  25. db.execute("CREATE TABLE IF NOT EXISTS users (username TEXT, hashed_password TEXT, admin BOOLEAN)")
  26. db.execute("CREATE TABLE IF NOT EXISTS encrypted_chats (encrypted_username_1 TEXT, encrypted_username_2 TEXT, encrypted_contents TEXT)")
  27. return db
  28.  
  29. db = initialize_db()
  30.  
  31.  
  32. def encrypt(data, key=None):
  33. cipher = AES.new(app.secret_key, AES.MODE_ECB)
  34. return cipher.encrypt(pad(data, cipher.block_size))
  35.  
  36.  
  37. def decrypt(data, key=None):
  38. cipher = AES.new(app.secret_key, AES.MODE_ECB)
  39. try:
  40. return unpad(cipher.decrypt(data), cipher.block_size)
  41. except ValueError:
  42. raise RuntimeError("Invalid padding")
  43.  
  44.  
  45. def authed_only(func):
  46. @functools.wraps(func)
  47. def wrapper(*args, **kwargs):
  48. if "username" not in session:
  49. return "You are not logged in! Please <a href='/login'>login</a> or <a href='/register'>register</a>.", 403
  50. return func(*args, **kwargs)
  51. return wrapper
  52.  
  53.  
  54. @app.after_request
  55. def format_response(response):
  56. if response.content_type.startswith("text/html"):
  57. original_data = response.get_data(as_text=True)
  58. dedented_data = textwrap.dedent(original_data).strip() + "\n"
  59. response.set_data(dedented_data)
  60. return response
  61.  
  62.  
  63. @app.route("/")
  64. def index():
  65. if "username" not in session:
  66. return 'Hello! Please <a href="/login">login</a> or <a href="/register">register</a>.'
  67. return f"""Hello {session['username']}! You are {'an admin' if session.get('admin') else 'not an admin'}.
  68. <br>
  69. <form action="/chat" method="get">
  70. <input type="text" name="other_username" placeholder="Username" required>
  71. <input type="submit" value="Chat">
  72. </form>
  73. """
  74.  
  75.  
  76. @app.route("/register", methods=["GET", "POST"])
  77. def register():
  78. if request.method == "GET":
  79. return """
  80. <form method="post">
  81. <input type="text" name="username" placeholder="Username" required>
  82. <input type="password" name="password" placeholder="Password" required>
  83. <input type="submit" value="Register">
  84. </form>
  85. """
  86. try:
  87. username = request.form["username"]
  88. password = request.form["password"]
  89. except KeyError:
  90. return "Missing username or password. Back to <a href='/'>home</a>.", 400
  91. if db.execute(f"SELECT * FROM users WHERE username='{username}'").fetchone():
  92. return "Username already exists. Back to <a href='/'>home</a>.", 400
  93. hashed_password = hashlib.sha256(password.encode()).hexdigest()
  94. admin = request.remote_addr == "127.0.0.1"
  95. db.execute(f"INSERT INTO users VALUES ('{username}', '{hashed_password}', {'true' if admin else 'false'})")
  96. db.commit()
  97. session["username"] = username
  98. session["admin"] = admin
  99. return redirect("/")
  100.  
  101.  
  102. @app.route("/login", methods=["GET", "POST"])
  103. def login():
  104. if request.method == "GET":
  105. return """
  106. <form method="post">
  107. <input type="text" name="username" placeholder="Username" required>
  108. <input type="password" name="password" placeholder="Password" required>
  109. <input type="submit" value="Login">
  110. </form>
  111. """
  112. try:
  113. username = request.form["username"]
  114. password = request.form["password"]
  115. except KeyError:
  116. return "Missing username or password. Back to <a href='/'>home</a>.", 400
  117. hashed_password = hashlib.sha256(password.encode()).hexdigest()
  118. result = db.execute(f"SELECT * FROM users WHERE username='{username}' and hashed_password='{hashed_password}'").fetchone()
  119. if not result:
  120. return "Invalid username or password. Back to <a href='/'>home</a>.", 400
  121. username, _, admin = result
  122.  
  123. if admin and request.remote_addr != "127.0.0.1":
  124. return "Admins may only login locally. Back to <a href='/'>home</a>.", 403
  125.  
  126. session["username"] = username
  127. session["admin"] = admin
  128. return redirect("/")
  129.  
  130.  
  131. @app.route("/chat/", methods=["GET"])
  132. @app.route("/chat/<other_username>", methods=["HEAD", "GET", "POST"])
  133. @authed_only
  134. def chat(other_username=None):
  135. if request.args.get("other_username"):
  136. return redirect(f"/chat/{request.args['other_username']}")
  137.  
  138. username = session["username"]
  139.  
  140. result = db.execute(f"SELECT * FROM users WHERE username='{other_username}'").fetchone()
  141. if not result:
  142. return "User does not exist. Back to <a href='/'>home</a>.", 404
  143.  
  144. encrypted_username_1, encrypted_username_2 = sorted([encrypt(username.encode()).hex(), encrypt(other_username.encode()).hex()])
  145. result = db.execute(f"SELECT encrypted_contents FROM encrypted_chats WHERE encrypted_username_1='{encrypted_username_1}' AND encrypted_username_2='{encrypted_username_2}'").fetchone()
  146. try:
  147. contents = decrypt(bytes.fromhex(result[0])).decode() if result else ""
  148. except RuntimeError:
  149. # Failed to correctly decrypt, web server restarted (and generated a new secret key) or it was tampered with
  150. # Delete the chat and start a new one
  151. contents = ""
  152.  
  153. etag = hashlib.sha256(contents.encode()).hexdigest()
  154.  
  155. if request.method == "HEAD":
  156. return "", 200, {"ETag": etag}
  157.  
  158. if request.method == "GET":
  159. contents_html = "\n<br>\n".join(contents.splitlines())
  160. return f"""
  161. <main>
  162. {contents_html}
  163. </main>
  164. <form method="post">
  165. <input id="message" type="text" name="message" placeholder="Message" required>
  166. <input type="submit" value="Send">
  167. </form>
  168. <script>
  169. let etag = "{etag}";
  170. window.scrollTo(0, document.body.scrollHeight);
  171. message.value = localStorage.getItem("message") || "";
  172. localStorage.removeItem("message");
  173. message.focus();
  174. interval = setInterval(() => {{
  175. fetch(location.href, {{method: "HEAD"}}).then(response => {{
  176. if (response.headers.get("ETag") !== etag) {{
  177. localStorage.setItem("message", message.value);
  178. location.reload();
  179. }}
  180. }});
  181. }}, 5000);
  182. </script>
  183. """, 200, {"ETag": etag}
  184.  
  185. if request.method == "POST":
  186. try:
  187. message = request.form["message"].strip().replace("\n", " ")
  188. except KeyError:
  189. return "Missing message. Back to <a href='/'>home</a>.", 400
  190. encrypted_contents = encrypt((contents + f"{username}: {message}\n").encode()).hex()
  191. if contents:
  192. db.execute(f"UPDATE encrypted_chats SET encrypted_contents='{encrypted_contents}' WHERE encrypted_username_1='{encrypted_username_1}' AND encrypted_username_2='{encrypted_username_2}'")
  193. else:
  194. db.execute(f"INSERT INTO encrypted_chats VALUES ('{encrypted_username_1}', '{encrypted_username_2}', '{encrypted_contents}')")
  195. db.commit()
  196. return redirect(f"/chat/{other_username}")
  197.  
  198.  
  199. @app.route("/user/modify", methods=["GET"])
  200. @app.route("/user/<username>/modify", methods=["GET", "POST"])
  201. @authed_only
  202. def modify_user(username=None):
  203. if username is None:
  204. return redirect(f"/user/{session['username']}/modify")
  205.  
  206. result = db.execute(f"SELECT * FROM users WHERE username='{username}'").fetchone()
  207. if not result:
  208. return "User does not exist. Back to <a href='/'>home</a>.", 400
  209. username, password, admin = result
  210. admin = bool(admin)
  211.  
  212. if request.method == "GET" and session["admin"]:
  213. return f"""
  214. Modify user {username}:
  215. <form method="post">
  216. <input type="text" name="username" placeholder="Username" value="{username}">
  217. <input type="password" name="password" placeholder="Password">
  218. <input type="checkbox" name="admin" value="{'true' if admin else 'false'}"> Admin
  219. <input type="submit" value="Modify">
  220. <input type="submit" name="delete" value="Delete User">
  221. </form>
  222. """
  223. elif request.method == "GET" and not session["admin"]:
  224. return f"""
  225. Modify user {username}:
  226. <form method="post">
  227. <input type="password" name="password" placeholder="Password">
  228. <input type="submit" value="Modify">
  229. <input type="submit" name="delete" value="Delete">
  230. </form>
  231. """
  232.  
  233. new_username = request.form.get("username", username)
  234. new_password = hashlib.sha256(request.form["password"].encode()).hexdigest() if request.form.get("password") else password
  235. new_admin = request.form.get("admin", ("true" if admin else "false")) == "true"
  236.  
  237. admin_only = not all((username == new_username == session["username"],
  238. admin == new_admin == session["admin"]))
  239.  
  240. if admin_only and not session.get("admin"):
  241. return "You must be an admin to modify other users. Back to <a href='/'>home</a>.", 403
  242.  
  243. encrypted_username = encrypt(username.encode()).hex()
  244.  
  245. if "delete" in request.form:
  246. db.execute(f"DELETE FROM users WHERE username='{username}'")
  247. db.execute(f"UPDATE encrypted_chats SET encrypted_username_1=NULL WHERE encrypted_username_1='{encrypted_username}'")
  248. db.execute(f"UPDATE encrypted_chats SET encrypted_username_2=NULL WHERE encrypted_username_2='{encrypted_username}'")
  249. db.execute(f"DELETE FROM encrypted_chats WHERE encrypted_username_1 IS NULL AND encrypted_username_2 IS NULL")
  250. db.commit()
  251. if session["username"] == username:
  252. session.clear()
  253. return "User deleted. Back to <a href='/'>home</a>."
  254.  
  255. db.execute(f"UPDATE users SET username='{new_username}', hashed_password='{new_password}', admin={'true' if new_admin else 'false'} WHERE username='{username}'")
  256.  
  257. if new_username != username:
  258. encrypted_username = encrypt(username.encode()).hex()
  259. encrypted_new_username = encrypt(new_username.encode()).hex()
  260. result = db.execute(f"SELECT * FROM encrypted_chats WHERE encrypted_username_1='{encrypted_username}' OR encrypted_username_2='{encrypted_username}'").fetchall()
  261. for encrypted_username_1, encrypted_username_2, encrypted_contents in result:
  262. contents = decrypt(bytes.fromhex(encrypted_contents)).decode()
  263. new_encrypted_username_1, new_encrypted_username_2 = sorted([encrypted_new_username if encrypted_username_1 == encrypted_username else encrypted_username_1,
  264. encrypted_new_username if encrypted_username_2 == encrypted_username else encrypted_username_2],
  265. key=lambda k: k or "")
  266. new_encrypted_contents = encrypt("".join(
  267. (line.replace(f"{username}: ", f"{new_username}: ", 1) if line.startswith(f"{username}: ") else line) + "\n"
  268. for line in contents.splitlines()
  269. ).encode()).hex()
  270. sql_set = ", ".join(f"{k}='{v}'" if v is not None else f"{k}=NULL" for k, v in
  271. dict(encrypted_username_1=new_encrypted_username_1,
  272. encrypted_username_2=new_encrypted_username_2,
  273. encrypted_contents=new_encrypted_contents).items())
  274. sql_where = " AND ".join(f"{k}='{v}'" if v is not None else f"{k} IS NULL" for k, v in
  275. dict(encrypted_username_1=encrypted_username_1,
  276. encrypted_username_2=encrypted_username_2,
  277. encrypted_contents=encrypted_contents).items())
  278. db.execute(f"UPDATE encrypted_chats SET {sql_set} WHERE {sql_where}")
  279.  
  280. db.commit()
  281. if session["username"] == username:
  282. if new_username != username:
  283. session["username"] = new_username
  284. if new_admin != admin:
  285. session["admin"] = new_admin
  286.  
  287. return "User modified. Back to <a href='/'>home</a>."
  288.  
  289.  
  290. if __name__ == "__main__":
  291. app.secret_key = os.urandom(16)
  292. app.config.update(SESSION_COOKIE_HTTPONLY=False)
  293. app.run(host=HOST, port=PORT, threaded=False)
  294. $ cat /challenge/run
  295. #!/usr/bin/exec-suid --real --environ=none -- /usr/local/bin/python -I
  296.  
  297. import atexit
  298. import logging
  299. import multiprocessing
  300. import os
  301. import pathlib
  302. import re
  303. import signal
  304. import subprocess
  305. import sys
  306. import tempfile
  307. import threading
  308. import time
  309.  
  310. import psutil
  311. import requests
  312. from dojjail import Host, Network, UNPRIVILEGED_UID
  313. from dojjail.capabilities import limit_capabilities, get_securebits, set_securebits, CAP, SECBIT
  314. from Crypto.Cipher import AES
  315. from Crypto.Util.Padding import pad, unpad
  316. from selenium import webdriver
  317. from selenium.webdriver.firefox.options import Options as FirefoxOptions
  318. from selenium.webdriver.firefox.service import Service as FirefoxService
  319. from selenium.webdriver.common.by import By
  320. from selenium.webdriver.support.wait import WebDriverWait
  321. from selenium.webdriver.support import expected_conditions as EC
  322. from selenium.common.exceptions import TimeoutException
  323.  
  324.  
  325. logger = logging.getLogger(__name__)
  326.  
  327. DH_p = int.from_bytes(bytes.fromhex(
  328. "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 "
  329. "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD "
  330. "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 "
  331. "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED "
  332. "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D "
  333. "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F "
  334. "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D "
  335. "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B "
  336. "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 "
  337. "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 "
  338. "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF"
  339. ), "big")
  340. DH_g = 2
  341.  
  342.  
  343. def start_chat_server():
  344. old_umask = os.umask(0o077)
  345. chat_server_dir = pathlib.Path(tempfile.mkdtemp())
  346. os.umask(old_umask)
  347.  
  348. os.chown(chat_server_dir, UNPRIVILEGED_UID, UNPRIVILEGED_UID)
  349.  
  350. set_securebits(get_securebits() | SECBIT.NO_SETUID_FIXUP)
  351. os.setresgid(UNPRIVILEGED_UID, UNPRIVILEGED_UID, UNPRIVILEGED_UID)
  352. os.setresuid(UNPRIVILEGED_UID, UNPRIVILEGED_UID, UNPRIVILEGED_UID)
  353. limit_capabilities(CAP.NET_BIND_SERVICE)
  354.  
  355. os.chdir(chat_server_dir)
  356.  
  357. dev_null_fd = os.open("/dev/null", os.O_RDWR)
  358. log_fd = os.open(chat_server_dir / "chat-server.log", os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o644)
  359. os.dup2(dev_null_fd, 0)
  360. os.dup2(log_fd, 1)
  361. os.dup2(log_fd, 2)
  362.  
  363. os.environ["CHAT_HOST"] = "0.0.0.0"
  364. chat_server_path = pathlib.Path(__file__).parent / "chat-server"
  365. os.execve(chat_server_path, ["chat-server"], os.environ)
  366.  
  367.  
  368. def main():
  369. if os.geteuid() != 0:
  370. print("Error: must be run as root", file=sys.stderr)
  371. sys.exit(1)
  372.  
  373. flag = open("/flag").read().strip()
  374.  
  375. cwd = os.getcwd()
  376. parent_process = psutil.Process(os.getppid())
  377. parent_euid = parent_process.uids().effective
  378. parent_env = parent_process.environ()
  379.  
  380. if parent_euid == 0:
  381. logger.setLevel(logging.DEBUG)
  382. handler = logging.FileHandler("/tmp/run.log")
  383. handler.setLevel(logging.DEBUG)
  384. logger.addHandler(handler)
  385.  
  386. os.environ["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  387.  
  388. alice_host = Host("alice")
  389. bob_host = Host("bob")
  390. mallory_host = Host("mallory")
  391. sharon_host = Host("sharon")
  392.  
  393. hacker_host = Host("hacker", privileged_uid=parent_euid)
  394.  
  395. network = Network("router", hosts={
  396. alice_host: "10.0.0.1",
  397. bob_host: "10.0.0.2",
  398. mallory_host: "10.0.0.3",
  399. sharon_host: "10.0.0.4",
  400. hacker_host: "10.0.0.5",
  401. })
  402.  
  403. print("Starting...", end="", flush=True)
  404. network.run()
  405.  
  406. os.environ["CHAT_HOST"] = network.host_ips[alice_host]
  407. os.environ["CHAT_PORT"] = "80"
  408. chat_server_url = f"http://{network.host_ips[alice_host]}"
  409.  
  410. alice_host.exec(start_chat_server, wait=False)
  411.  
  412. def wait_for_chat_server():
  413. for _ in range(50):
  414. try:
  415. requests.get("http://127.0.0.1")
  416. except requests.ConnectionError:
  417. time.sleep(0.1)
  418. continue
  419. return
  420. raise RuntimeError("Chat server did not start")
  421. alice_host.exec(wait_for_chat_server)
  422.  
  423. def create_account(username, password, *, url=chat_server_url):
  424. requests.post(f"{url}/register", data={"username": username, "password": password})
  425.  
  426. alice_password = os.urandom(16).hex()
  427. alice_host.exec(lambda: create_account("alice", alice_password, url="http://127.0.0.1"))
  428.  
  429. bob_password = os.urandom(16).hex()
  430. bob_host.exec(lambda: create_account("bob", bob_password))
  431.  
  432. mallory_password = os.urandom(16).hex()
  433. mallory_host.exec(lambda: create_account("mallory", mallory_password))
  434.  
  435. sharon_username = f"sharon{int.from_bytes(os.urandom(8), 'big')}"
  436. sharon_password = os.urandom(16).hex()
  437. sharon_host.exec(lambda: create_account(sharon_username, sharon_password))
  438.  
  439. def share_flag():
  440. """
  441. Sharon shares the flag with Bob.
  442. """
  443. session = requests.Session()
  444. session.post(f"{chat_server_url}/login", data={"username": sharon_username, "password": sharon_password})
  445. session.post(f"{chat_server_url}/chat/bob", data={"message": f"The flag is {flag}."})
  446. sharon_host.exec(share_flag)
  447.  
  448. def check_flag():
  449. """
  450. Bob notices that the flag is shared with him and notifies Mallory.
  451. """
  452. session = requests.Session()
  453. session.post(f"{chat_server_url}/login", data={"username": "bob", "password": bob_password})
  454. response = session.get(f"{chat_server_url}/chat/{sharon_username}")
  455. if flag not in response.text:
  456. raise RuntimeError("Flag not shared")
  457. session.post(f"{chat_server_url}/chat/mallory", data={"message": "Wow, someone shared the flag with me!"})
  458. bob_host.exec(check_flag)
  459.  
  460. def delete_account():
  461. """
  462. Sharon deletes her account.
  463. """
  464. session = requests.Session()
  465. session.post(f"{chat_server_url}/login", data={"username": sharon_username, "password": sharon_password})
  466. session.post(f"{chat_server_url}/user/{sharon_username}/modify", data={"delete": "Delete"})
  467. sharon_host.exec(delete_account)
  468.  
  469. def check_chat_is_gone():
  470. """
  471. Bob notices that the chat with Sharon is gone.
  472. """
  473. session = requests.Session()
  474. session.post(f"{chat_server_url}/login", data={"username": "bob", "password": bob_password})
  475. response = session.get(f"{chat_server_url}/chat/{sharon_username}")
  476. if response.status_code != 404:
  477. raise RuntimeError("Chat not deleted")
  478. bob_host.exec(check_chat_is_gone)
  479.  
  480. listening_event = multiprocessing.Event()
  481. reported_event = multiprocessing.Event()
  482.  
  483. def browse():
  484. """
  485. Alice browses chat messages with Mallory.
  486. """
  487.  
  488. os.environ["HOME"] = tempfile.mkdtemp()
  489.  
  490. options = FirefoxOptions()
  491. options.add_argument("--headless")
  492. service = FirefoxService(executable_path="/usr/local/bin/geckodriver", log_path="/dev/null")
  493. browser = webdriver.Firefox(service=service, options=options)
  494. atexit.register(browser.quit)
  495.  
  496. browser.get(f"http://127.0.0.1/login")
  497. browser.find_element(By.NAME, "username").send_keys("alice")
  498. browser.find_element(By.NAME, "password").send_keys(alice_password)
  499. browser.find_element(By.CSS_SELECTOR, "input[type='submit']").submit()
  500.  
  501. try:
  502. WebDriverWait(browser, 2).until(EC.text_to_be_present_in_element((By.TAG_NAME, "body"), "Hello alice!"))
  503. except TimeoutException:
  504. raise RuntimeError("Login failed")
  505.  
  506. browser.find_element(By.NAME, "other_username").send_keys("mallory")
  507. browser.find_element(By.CSS_SELECTOR, "input[type='submit']").submit()
  508. try:
  509. WebDriverWait(browser, 2).until(EC.url_matches(f"http://127.0.0.1/chat/mallory"))
  510. except TimeoutException:
  511. raise RuntimeError("Failed to open chat with mallory")
  512.  
  513. listening_event.set()
  514.  
  515. message_history = []
  516. while True:
  517. try:
  518. WebDriverWait(browser, 2).until(EC.presence_of_element_located((By.TAG_NAME, "main")))
  519. all_messages = [line.strip() for line in
  520. browser.find_element(By.TAG_NAME, "main").get_attribute("innerHTML").splitlines()
  521. if line.strip() and line.strip() != "<br>"]
  522. new_messages = all_messages[len(message_history):]
  523. for message in new_messages:
  524. logger.info(f"New message: {message}")
  525. if message == "mallory: I heard someone shared the flag with Bob!":
  526. browser.find_element(By.NAME, "message").send_keys("Thanks for letting me know!")
  527. browser.find_element(By.CSS_SELECTOR, "input[type='submit']").submit()
  528. reported_event.set()
  529. message_history.append(message)
  530. except Exception:
  531. logger.exception("Error while browsing")
  532. return
  533. time.sleep(1)
  534.  
  535. alice_host.exec(browse, uid=UNPRIVILEGED_UID, wait=False)
  536. listening_event.wait()
  537.  
  538. print("\r", end="", flush=True)
  539. pid = os.getpid()
  540. hacker_pid = hacker_host.exec(lambda: subprocess.run("/bin/bash", env=parent_env, cwd=cwd), wait=False)
  541. hacker_thread = threading.Thread(target=lambda: (os.waitpid(hacker_pid, 0),
  542. logger.info("Hacker shell exited"),
  543. os.killpg(pid, signal.SIGTERM)))
  544. hacker_thread.start()
  545. logger.info("Hacker shell started")
  546.  
  547. reported_event.wait()
  548. logger.info("Mallory told Alice that someone shared the flag with Bob")
  549.  
  550. def alice_message_bob():
  551. """
  552. Alice has a secure conversation with Bob, discovering from him who shared the flag.
  553. """
  554. session = requests.Session()
  555. session.post("http://127.0.0.1/login", data={"username": "alice", "password": alice_password})
  556.  
  557. dh_a = int.from_bytes(os.urandom(256), "big")
  558. dh_A = pow(DH_g, dh_a, DH_p)
  559. session.post("http://127.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}."})
  560. logger.info(f"Alice told bob to chat securely with DHE-AES: {dh_A}")
  561.  
  562. dh_B_re = re.compile(r"^bob: Hey Alice, sure: (\d+)\.$", re.MULTILINE)
  563. while True:
  564. response = session.get("http://127.0.0.1/chat/bob")
  565. if match := dh_B_re.search(response.text):
  566. break
  567. time.sleep(1)
  568. dh_B = int(match.group(1))
  569. dh_s = pow(dh_B, dh_a, DH_p)
  570. logger.info(f"Alice received DHE-AES response from Bob: {dh_B}")
  571.  
  572. time.sleep(1)
  573.  
  574. key = dh_s.to_bytes(256, "big")[:16]
  575. cipher_send = AES.new(key, AES.MODE_ECB)
  576. cipher_recv = AES.new(key, AES.MODE_ECB)
  577. encrypt = lambda data: cipher_send.encrypt(pad(data.encode(), cipher_send.block_size)).hex()
  578. decrypt = lambda data: unpad(cipher_recv.decrypt(bytes.fromhex(data)), cipher_recv.block_size).decode()
  579. logger.info(f"Alice established shared key with Bob: {key.hex()}")
  580.  
  581. session.post("http://127.0.0.1/chat/bob", data={"message": encrypt("Hey Bob, I know that someone shared the flag with you. Who was it?")})
  582. logger.info("Alice asked Bob who shared the flag")
  583.  
  584. encrypted_message_re = re.compile(r"^bob: ([0-9a-f]+)$", re.MULTILINE)
  585. flag_sharer_re = re.compile(r"Oh, it was '(\w+)'\.")
  586. while True:
  587. response = session.get("http://127.0.0.1/chat/bob")
  588. encrypted_messages = encrypted_message_re.findall(response.text)
  589. for encrypted_message in encrypted_messages:
  590. if match := flag_sharer_re.match(decrypt(encrypted_message)):
  591. break
  592. else:
  593. time.sleep(1)
  594. continue
  595. break
  596. flag_sharer = match.group(1)
  597. logger.info(f"Alice learned who shared the flag: {flag_sharer}")
  598.  
  599. time.sleep(1)
  600.  
  601. session.post("http://127.0.0.1/chat/bob", data={"message": encrypt("Thanks Bob, I'll look into it.")})
  602. logger.info("Alice thanked Bob")
  603.  
  604. def bob_message_alice():
  605. """
  606. Bob has a secure conversation with Alice, confirming that Sharon shared the flag with him.
  607. """
  608. session = requests.Session()
  609. session.post(f"{chat_server_url}/login", data={"username": "bob", "password": bob_password})
  610.  
  611. dh_A_re = re.compile(r"^alice: Hey Bob, I need to chat with you about something important\. Let's chat securely over DHE-AES: (\d+)\.$", re.MULTILINE)
  612. while True:
  613. response = session.get(f"{chat_server_url}/chat/alice")
  614. if match := dh_A_re.search(response.text):
  615. break
  616. time.sleep(1)
  617. dh_A = int(match.group(1))
  618. logger.info(f"Bob received DHE-AES request from Alice: {dh_A}")
  619.  
  620. time.sleep(1)
  621.  
  622. dh_b = int.from_bytes(os.urandom(256), "big")
  623. dh_B = pow(DH_g, dh_b, DH_p)
  624. dh_s = pow(dh_A, dh_b, DH_p)
  625. session.post(f"{chat_server_url}/chat/alice", data={"message": f"Hey Alice, sure: {dh_B}."})
  626.  
  627. key = dh_s.to_bytes(256, "big")[:16]
  628. cipher_send = AES.new(key, AES.MODE_ECB)
  629. cipher_recv = AES.new(key, AES.MODE_ECB)
  630. encrypt = lambda data: cipher_send.encrypt(pad(data.encode(), cipher_send.block_size)).hex()
  631. decrypt = lambda data: unpad(cipher_recv.decrypt(bytes.fromhex(data)), cipher_recv.block_size).decode()
  632.  
  633. encrypted_message_re = re.compile(r"^alice: ([0-9a-f]+)$", re.MULTILINE)
  634. while True:
  635. response = session.get(f"{chat_server_url}/chat/alice")
  636. encrypted_messages = encrypted_message_re.findall(response.text)
  637. if any(decrypt(encrypted_message) == "Hey Bob, I know that someone shared the flag with you. Who was it?"
  638. for encrypted_message in encrypted_messages):
  639. break
  640. time.sleep(1)
  641. logger.info("Bob asked by Alice about flag sharer")
  642.  
  643. time.sleep(1)
  644.  
  645. session.post(f"{chat_server_url}/chat/alice", data={"message": encrypt(f"Oh, it was '{sharon_username}'. I think she deleted her account.")})
  646. logger.info("Bob told Alice about flag sharer")
  647.  
  648. while True:
  649. response = session.get(f"{chat_server_url}/chat/alice")
  650. encrypted_messages = encrypted_message_re.findall(response.text)
  651. if any(decrypt(encrypted_message) == "Thanks Bob, I'll look into it."
  652. for encrypted_message in encrypted_messages):
  653. break
  654. time.sleep(1)
  655. logger.info("Bob thanked by Alice")
  656.  
  657. alice_host.exec(alice_message_bob, wait=False)
  658. bob_host.exec(bob_message_alice, wait=False)
  659.  
  660. hacker_thread.join()
  661.  
  662. if __name__ == "__main__":
  663. main()
  664. $
Add Comment
Please, Sign In to add comment