Skip to content

Latest commit

 

History

History
156 lines (117 loc) · 4.46 KB

File metadata and controls

156 lines (117 loc) · 4.46 KB

Running with Docker

There is no shell in the container

The image is built FROM scratch. It contains one file: the server binary. There is no bash, no sh, no ls, no package manager. If you try to open a shell you get this:

$ docker compose exec app bash
OCI runtime exec failed: exec failed: unable to start container process:
exec: "bash": executable file not found in $PATH

That is expected, and it is the point. A container that stores whatever a target sends it should not also contain tools an attacker could use. Nothing is broken.

Everything you would use a shell for is a subcommand of the binary instead.

Running commands

The binary lives at /exploit-server inside the container:

docker compose exec app /exploit-server <command>

For example:

docker compose exec app /exploit-server config
docker compose exec app /exploit-server instance new --label acme
docker compose exec app /exploit-server instance ls
docker compose exec app /exploit-server log acme --follow
docker compose exec app /exploit-server help

That is long to type. An alias makes it bearable — wes, for web exploit server:

alias wes='docker compose exec app /exploit-server'
wes instance ls
wes log acme --follow

Add it to ~/.bashrc or ~/.zshrc to keep it. The alias only works from the directory holding compose.yaml.

To pipe a file in, add -T so Docker does not try to allocate a terminal:

cat payload.html | docker compose exec -T app /exploit-server store acme /exploit -

Everyday operations

Task Command
Start docker compose up -d
Stop docker compose down
Restart after a config change docker compose restart app
Follow the server's own output docker compose logs -f app
Is it healthy? docker compose ps
Panel password docker compose exec app /exploit-server config
Everything the CLI can do docker compose exec app /exploit-server help

A config change made with config set is written to the volume but read only at startup, so restart the container afterwards.

Where the data lives

In a named Docker volume, not in the project directory. Instances, stored responses, the access log, captured mail and config.json are all inside it.

docker volume ls | grep exploit

docker compose down keeps it. docker compose down -v deletes it, along with every instance and every log entry.

To back it up, mount it into a throwaway container that does have a shell:

docker run --rm \
  -v exploit-server_data:/data:ro \
  -v "$PWD":/out \
  alpine:3 tar czf /out/exploit-backup.tar.gz -C /data .

To restore:

docker run --rm \
  -v exploit-server_data:/data \
  -v "$PWD":/in \
  alpine:3 tar xzf /in/exploit-backup.tar.gz -C /data

Updating

docker compose pull        # or: docker compose build
docker compose up -d

The volume is untouched, so instances and logs survive. Back it up first anyway.

Without Compose

The published image runs on its own. Keep the data in a named volume so it survives the container:

docker run -d --name exploit-server \
  -p 127.0.0.1:80:80 \
  -p 127.0.0.1:8080:8080 \
  -v exploit-server_data:/var/lib/exploit-server \
  -e EXPLOIT_BASE_DOMAIN=example.com \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --sysctl net.ipv4.ip_unprivileged_port_start=0 \
  --read-only --tmpfs /tmp:mode=1777 \
  ghcr.io/inneromost/web-exploit-server:latest

docker exec exploit-server /exploit-server config

--sysctl net.ipv4.ip_unprivileged_port_start=0 is what lets an unprivileged user inside the container bind port 80. Without it the server cannot start.

Compose is still the better option: it also creates the isolated network that deploy/firewall.sh locks down.

Without make

Every make target is a short alias. If you do not have make:

Instead of Run
make up docker compose up -d
make down docker compose down
make logs docker compose logs -f app
make image docker build -t web-exploit-server:latest .
make cmd ARGS='instance ls' docker compose exec app /exploit-server instance ls