Skip to content

Write recovered secrets and carved artifacts owner-only (0600/0700) - #30

Open
SackOfHacks wants to merge 1 commit into
mainfrom
fix/restrict-output-permissions
Open

Write recovered secrets and carved artifacts owner-only (0600/0700)#30
SackOfHacks wants to merge 1 commit into
mainfrom
fix/restrict-output-permissions

Conversation

@SackOfHacks

Copy link
Copy Markdown
Owner

Fixes #29.

The problem

pcapper deliberately does not redact what it recovers (reporting._redact_secret is a documented no-op — the right call for a credential-recovery tool). The consequence is that reports, exports, the run log, carved artifacts, extracted files and decrypted streams routinely contain cleartext credentials, LDAP binds, SNMP community strings, session tokens and malware samples.

None of those write paths restricted permissions, so under a default umask they landed at 0644 and output directories at 0755. On a shared analysis host — a jump box, a lab VM, a multi-analyst forensics workstation — every other local account could read recovered passwords out of another analyst's case directory. On an IR engagement those files are also evidence, and world-readable evidence is harder to defend on chain-of-custody grounds.

The fix

Two best-effort helpers in utils.py, applied at every write path:

Location Writes Now
utils.safe_write_text reports, JSON export 0600
exporting.export_csv CSV + _hosts CSV 0600
exporting.export_sqlite SQLite export 0600, set immediately after sqlite3.connect()
cli._log_event --log-file run log 0600
cli case dirs, export output dirs 0700
carving carved artifacts + output dir 0600 / 0700
files extracted artifacts + output dir 0600 / 0700
decryption decrypted TLS/SSH streams + output dir 0600 / 0700

Two deliberate design choices, both raised in the issue:

  • Best effort. POSIX mode bits are largely a no-op on Windows, and a failure to tighten permissions never aborts an analysis run — the chmod is wrapped and swallowed.
  • restrict_dir_permissions only tightens a directory it actually creates. Pointing --case-dir / --carve-out / --log-file at a directory the analyst already has does not silently re-permission it; the files written into it are still 0600. mkdir errors propagate exactly as a bare mkdir would — only the chmod is best effort.

The SQLite export is tightened right after connect() and before any rows are inserted, so the database is never briefly readable while being populated. Directories are tightened at mkdir time, not at end of run.

One ordering bug surfaced while testing and is fixed here: _log_event fires at run start and creates the log file's parent, so with --log-file <case>/run.log the case directory was being created at 0755 before _analyze_paths got to it. That mkdir now goes through the same helper.

Verification

Run under WSL with umask 022, which would otherwise yield 0644/0755:

--- A: fresh dirs, full flag set ---
exit=0
700  /tmp/.../case
700  /tmp/.../carved
600  carve_0e6acef3a702_client_0_gif.bin
600  case.json
600  e.csv
600  e.db
600  e.json
600  run.log
--- B: second run into the SAME dirs (idempotent) ---
exit=0
700  /tmp/.../case
600  /tmp/.../case/run.log
--- C: pre-existing 0755 dir left alone, file still 0600 ---
exit=0
755  /tmp/.../shared
600  /tmp/.../shared/e.json
600  /tmp/.../shared/run.log

The files.py extraction path was verified separately on Windows (where dpkt is installed) against a synthetic HTTP GIF transfer — files/ 0700, secret.gif 0600.

decryption.py was not exercised end-to-end (needs tshark plus a keylog file); its edits are the same shape as the verified paths and the module compiles and imports cleanly.

python -m compileall pcapper is clean and --self-check passes apart from pre-existing optional-dependency warnings in the WSL env (dpkt, geoip2, pydicom, libpcap).

The alternative floated in the issue — a --permissive-output escape hatch — is not included here; restrictive-by-default with no flag seemed like the better starting point, and it is easy to add later if a shared-team workflow needs it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XxomH3dRdLtPFFABGp1AEW

pcapper deliberately does not redact what it recovers (reporting._redact_secret
is a documented no-op), so reports, exports, the run log, carved artifacts,
extracted files and decrypted streams routinely contain cleartext credentials,
LDAP binds, SNMP community strings, session tokens and malware samples.

None of those write paths restricted permissions, so under a default umask they
landed at 0644 and output directories at 0755. On a shared analysis host — a
jump box, a lab VM, a multi-analyst forensics workstation — every other local
account could read recovered passwords out of another analyst's case directory.
On an IR engagement those files are also evidence, and world-readable evidence
is harder to defend on chain-of-custody grounds.

Adds utils.restrict_permissions() (0600 files, 0700 dirs) and
utils.restrict_dir_permissions() (mkdir + chmod), applied at every write path:

- utils.safe_write_text  — reports and JSON export
- exporting.export_csv   — CSV export and the _hosts CSV
- exporting.export_sqlite — tightened immediately after sqlite3.connect(), so
  the database is never briefly readable while rows are being inserted
- cli._log_event         — the --log-file run log, which records event fields
- cli                    — case directories and export output directories
- carving                — carved artifacts and their output directory
- files                  — extracted file artifacts and their output directory
- decryption             — decrypted TLS/SSH streams and their output directory

Both helpers are best effort by design: POSIX mode bits are largely a no-op on
Windows, and failing to tighten permissions never aborts an analysis run.
restrict_dir_permissions only tightens a directory it actually creates, so
pointing an output flag at an existing shared directory does not silently
re-permission it; mkdir errors propagate exactly as a bare mkdir would.

Verified under WSL with umask 022, which would otherwise yield 0644/0755:
freshly created case/carve/export/files directories come out 0700 and every
report, export, log, carved blob and extracted artifact 0600; a second run into
the same directories is idempotent; a pre-existing 0755 directory keeps its mode
while the files written into it are still 0600.

Fixes #29

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxomH3dRdLtPFFABGp1AEW
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recovered cleartext credentials and carved artifacts are written world-readable

1 participant