Skip to content

Recovered cleartext credentials and carved artifacts are written world-readable #29

Description

@SackOfHacks

Found during a security review of the repo. This is the only security finding in pcapper — noting up front that the rest of the tool held up well under review: no shell=True (all subprocess calls are list-form), no eval/exec/pickle/yaml.load, no XML parsing (so no XXE), no ReDoS-prone patterns, and .gitignore correctly excludes PCAPs, carved output, case directories and pcapper.toml.

I also specifically tried to break the two spots that matter most for a tool writing attacker-controlled data to disk, and both hold up: _safe_output_path (pcapper/files.py:1153) resolves and checks relative_to, correctly rejecting both ../ traversal and absolute filenames — which Path.__truediv__ would otherwise silently honour, since base / "/etc/passwd" discards the base. _safe_decompress (pcapper/files.py:1180) bounds output against decompression bombs, and carving generates its own filenames rather than trusting names off the wire.

The issue: recovered cleartext secrets written world-readable

Redaction is deliberately disabled — reporting._redact_secret and _redact_in_text (pcapper/reporting.py:241,319) are documented no-ops, which is the right call for a credential-recovery tool. The consequence is that reports, exports, the run log, carved artifacts and decrypted streams routinely contain cleartext credentials, LDAP binds, SNMP community strings, session tokens and malware samples.

None of those write paths restrict permissions, so under a default umask they land at 0644 (and case directories at 0755). On a shared analysis host — a jump box, a lab VM, a multi-analyst forensics workstation — every other local account can read recovered passwords out of another analyst's case directory.

Note this is not just an exposure question: on an IR engagement those files are evidence, and world-readable evidence is harder to defend on chain-of-custody grounds.

Affected write paths

Location Writes
pcapper/utils.pysafe_write_text reports, JSON export (via exporting.export_json)
pcapper/exporting.py:187,200 CSV export + _hosts CSV
pcapper/exporting.py:212 SQLite export
pcapper/cli.py:755 run log (--log), which records event fields
pcapper/cli.py:2246,5071 case directories
pcapper/carving.py:299 + :273 carved artifacts and their output dir
pcapper/files.py:4335 + :4327 extracted file artifacts and their output dir
pcapper/decryption.py:161,215 + :147,203 decrypted TLS/SSH streams and their output dir

Suggested fix

A small best-effort helper in utils.py, called after each write:

def restrict_permissions(path: Path) -> None:
    """Restrict a written artifact to owner-only access (0600, or 0700 for a dir).

    pcapper deliberately does not redact recovered secrets (see
    reporting._redact_secret), so its outputs routinely contain cleartext
    credentials, session tokens and malware. Under a default umask those land
    world-readable, exposing them to every other local account on a shared
    analysis host.

    Best effort by design: POSIX mode bits are largely a no-op on Windows, and
    failing to tighten permissions must never abort an analysis run.
    """
    try:
        path.chmod(0o700 if path.is_dir() else 0o600)
    except Exception:
        pass

Two implementation notes from prototyping this:

  • For the SQLite export, tighten immediately after sqlite3.connect() and before rows are inserted, so the database is never briefly readable while being populated.
  • Directories should be tightened right after mkdir(parents=True, exist_ok=True), not only at the end of the run.

I had this implemented and compiling cleanly across all modules, but backed it out so the change could be considered on its own rather than landing alongside unrelated fixes. Happy to open a PR if you want it.

A reasonable alternative, if a blanket 0600 is too aggressive for shared-team workflows, is a --umask/--permissive-output flag that defaults to restrictive.

🤖 Generated with Claude Code

https://claude.ai/code/session_015w8bmKfJjNSUgEKUZgJ8tS

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions