From b0e00a6cc453a34db7f93eb4636b92a3e283ef88 Mon Sep 17 00:00:00 2001 From: anti Date: Sat, 18 Apr 2026 02:12:32 -0400 Subject: [PATCH] =?UTF-8?q?fix(ssh-capture):=20drop=20relay=20FIFO,=20rsys?= =?UTF-8?q?log=E2=86=92/proc/1/fd/1=20direct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The named pipe at /run/systemd/journal/syslog-relay had two problems beyond its argv leak: any root-in-container process could (a) `cat` the pipe and watch the live SIEM feed, and (b) write to it and inject forged log lines. Since an attacker with a shell is already root inside the honeypot, file permissions can't fix it. Point rsyslog's auth/user actions directly at /proc/1/fd/1 — the container-stdout fd Docker attached to PID 1 — and delete the mkfifo + cat relay from the entrypoint. No pipe on disk, nothing to read, nothing to inject, and one fewer cloaked process in `ps`. --- templates/ssh/Dockerfile | 12 +++++++----- templates/ssh/entrypoint.sh | 13 ++++--------- tests/test_ssh.py | 26 ++++++++++++-------------- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/templates/ssh/Dockerfile b/templates/ssh/Dockerfile index 9f67c9f..6dd46a3 100644 --- a/templates/ssh/Dockerfile +++ b/templates/ssh/Dockerfile @@ -34,13 +34,15 @@ RUN sed -i \ -e 's|^#\?LogLevel.*|LogLevel VERBOSE|' \ /etc/ssh/sshd_config -# rsyslog: forward auth.* and user.* to named pipe in RFC 5424 format. -# The entrypoint relays the pipe to stdout for Docker log capture. +# rsyslog: forward auth.* and user.* to PID 1's stdout in RFC 5424 format. +# /proc/1/fd/1 is the container-stdout fd Docker attached — writing there +# surfaces lines in `docker logs` without needing a named pipe + relay cat +# (which would be readable AND writable by any root-in-container process). RUN printf '%s\n' \ - '# syslog-relay log bridge — auth + user events → named pipe as RFC 5424' \ + '# auth + user events → container stdout as RFC 5424' \ '$template RFC5424fmt,"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n"' \ - 'auth,authpriv.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ - 'user.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ + 'auth,authpriv.* /proc/1/fd/1;RFC5424fmt' \ + 'user.* /proc/1/fd/1;RFC5424fmt' \ > /etc/rsyslog.d/50-journal-forward.conf # Silence default catch-all rules so we own auth/user routing exclusively diff --git a/templates/ssh/entrypoint.sh b/templates/ssh/entrypoint.sh index 0ac78d5..8c59325 100644 --- a/templates/ssh/entrypoint.sh +++ b/templates/ssh/entrypoint.sh @@ -31,15 +31,10 @@ ls /var/www/html HIST fi -# Logging pipeline: named pipe → rsyslogd (RFC 5424) → stdout → Docker log capture. -# Pipe lives under /run/systemd/journal/ and the relay process is cloaked via -# exec -a so `ps aux` shows "systemd-journal-fwd" instead of a raw `cat`. -mkdir -p /run/systemd/journal -mkfifo /run/systemd/journal/syslog-relay - -bash -c 'exec -a "systemd-journal-fwd" cat /run/systemd/journal/syslog-relay' & - -# Start rsyslog (reads /etc/rsyslog.d/50-journal-forward.conf, writes to the pipe above) +# Logging pipeline: rsyslogd (RFC 5424) → /proc/1/fd/1 → Docker log capture. +# No intermediate pipe/relay — a named FIFO would be readable AND writable +# by any root-in-container process, letting an attacker either eavesdrop on +# the SIEM feed or inject forged log lines. rsyslogd # File-catcher: mirror attacker drops into host-mounted quarantine with attribution. diff --git a/tests/test_ssh.py b/tests/test_ssh.py index e6985b0..3a3dc6e 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -144,27 +144,25 @@ def test_dockerfile_prompt_command_logger(): assert "logger" in df -def test_entrypoint_creates_named_pipe(): - assert "mkfifo" in _entrypoint_text() - - -def test_entrypoint_relay_pipe_path_is_disguised(): +def test_entrypoint_has_no_named_pipe(): + # Named pipes in the container are a liability — readable and writable + # by any root process. The log bridge must not rely on one. ep = _entrypoint_text() - # Pipe lives under /run/systemd/journal/, not the obvious /var/run/decnet-logs. - assert "/run/systemd/journal/syslog-relay" in ep - assert "decnet-logs" not in ep + assert "mkfifo" not in ep + assert "syslog-relay" not in ep -def test_entrypoint_cat_relay_is_cloaked(): +def test_entrypoint_has_no_relay_cat(): + # No intermediate cat relay either (removed together with the pipe). ep = _entrypoint_text() - # `cat` is invoked via exec -a so ps shows systemd-journal-fwd. - assert "systemd-journal-fwd" in ep - assert "exec -a" in ep + assert "systemd-journal-fwd" not in ep -def test_dockerfile_rsyslog_uses_disguised_pipe(): +def test_dockerfile_rsyslog_targets_pid1_stdout(): df = _dockerfile_text() - assert "/run/systemd/journal/syslog-relay" in df + # rsyslog writes straight to /proc/1/fd/1 — no pipe file on disk. + assert "/proc/1/fd/1" in df + assert "syslog-relay" not in df assert "decnet-logs" not in df