Skip to content

v2.0.1

Choose a tag to compare

@uziii2208 uziii2208 released this 30 Aug 01:32
· 7 commits to master since this release

What's Changed


Security Hardening — Full Niche Operator Quality Pass (FIX-01 – FIX-09)

  • FIX-01 (CWE-400 / Silent Data Corruption): Fixed closure rebinding bug in download() stream collector (pwnshell.py:788–822). bytearray += bytes inside the inner closure was rebinding the local parameter name rather than mutating the shared buffer — outer buf stayed empty across all chunks. Replaced with in-place buf.extend(chunk) and added diagnostic separation between empty-stream and collection-failure error paths.

  • FIX-02 (CWE-78 / CWE-88): Resolved PowerShell single-quote injection in upload() user-supplied dst path (pwnshell.py:631–674). Replaced all single-quoted PS strings with double-quoted equivalents escaped via _pde() and enforced -LiteralPath across every file manipulation cmdlet (Add-Content, Move-Item, Remove-Item).

  • FIX-03 (CWE-73 / CWE-22): Fixed client-side trust of server-returned Resolve-Path output for local filename derivation (pwnshell.py:709–745). Default local destination is now derived strictly from args[0] (user-supplied RPATH via PureWindowsPath(args[0]).name), not from the server-resolved path. Added CWD-escape check: resolved dst is validated against Path.cwd() and rejected on traversal.

  • FIX-04 (CWE-693): Replaced static 6-byte AMSI patch signature b8 57 00 07 80 c3 (ret 0x80070057) with dynamic build_amsi_patch() polymorphic NOP generator (commands.py:38–54, evasion.py:54–75). Stub prefix is selected at runtime from xchg ax,ax / nop / multi-byte NOP variants via secrets.choice(). Eliminated hardcoded Start-Sleep -Seconds 1 OPSEC anti-pattern, replaced with VirtualProtect RWX → Marshal::Copy → RX transition without deterministic sleep.

  • FIX-05 (CWE-330): Replaced non-CSPRNG Mersenne Twister random.randint / randbytes with secrets module across all security-sensitive call sites (commands.py:56–95, spnego.py:140–155, transports.py:295–310). XOR keys now use secrets.randbelow(254) + 1 (range [1,254], never zero-key), CredSSP nonces use secrets.token_bytes(32), and PS namespace/classname identifiers use secrets.token_hex().

  • FIX-06 (CWE-611): Integrated defusedxml.ElementTree as a safe XML parsing layer in core/psrp.py:5–16. Implementation monkey-patches ET.fromstring and ET.parse with defusedxml equivalents on import, with graceful fallback to stdlib ET if defusedxml is unavailable. defusedxml>=0.7.1 added to requirements.txt.

  • FIX-07 (CWE-22 / CWE-78): Reimplemented missing _validate_remote_path() regression (pwnshell.py:727–748). Strict regex whitelist _SAFE_WINPATH (^[A-Za-z]:\\...) and _SAFE_UNCPATH (^\\\\...) blocking directory traversal sequences (..), shell metacharacters ($, `, ;, &, `), and control characters.

  • FIX-08 (CWE-400): Enforced MAX_SYNC_BYTES = 1 * 1024 * 1024 output ceiling in run_sync() (pwnshell.py:298–315). Overflow triggers output truncation with a warning and active runspace interrupt via signal — prevents OOM from anomalous or adversarially large remote command output.

  • FIX-09 (CWE-295 / CWE-300): Implemented cryptographic pubKeyAuth echo-back verification in CredSSP handshake (transports.py:318–350). Server-returned pubKeyAuth blob is unwrapped and compared against SHA256(b"CredSSP Server-To-Client Binding Hash\x00" + nonce + pubkey) — MitM relay without the private key is now detectable. Added CredSSP v5 protocol fallback for legacy domain configurations.


Operational Improvements (OPT-01 – OPT-08)

  • OPT-01: Eliminated CtrlCHandler re-entry race condition in run_with_interrupt() (pwnshell.py:317–335). Handler now covers the entire generator stream duration rather than re-entering per-chunk, closing the window where a Ctrl+C between iterations would fall through to the default SIGINT handler.

  • OPT-02: Upload progress display overhauled (pwnshell.py:648–665). Progress is now calculated in-flight before chunk submission and rendered as a real-time percentage (Upload X/Y (Z%)), replacing the post-chunk counter that lagged one iteration behind actual transfer state.

  • OPT-03: help() refactored from a hardcoded static string into a structured COMMAND_REGISTRY dict (pwnshell.py:65–135). All commands with syntax and descriptions are sourced from a single registry — new commands added to the plugin system appear in help output automatically without manual string editing.

  • OPT-04: Transcript log writes now pass through strip_ansi() (pwnshell.py:270–295). Raw VT100/ANSI escape codes no longer pollute .log files — output is clean ASCII/UTF-8 readable in any editor or SIEM pipeline.

  • OPT-05: Replaced colloquial comment explaining SPNEGO/Kerberos AP_REQ extraction as "hijack" with proper technical rationale citing RFC 4121 NegTokenInit unwrapping and MS-WSMV §3.2.5.1 GSS-API framing requirements (transports.py:257–270).

  • OPT-06: Hardcoded magic constant 0xe0088237 in NTLM Type 1 flags defined as named constant NTLM_FLAGS_STANDARD with comprehensive bitwise breakdown documentation (spnego.py:38–53).

  • OPT-07: Sensitive download buffer zeroized on MD5 integrity failure (pwnshell.py:810–822). buf[:] = b'\x00' * len(buf) executes immediately on checksum mismatch before error is surfaced — prevents credential material from lingering in process memory after a corrupt transfer.

  • OPT-08: Introduced HISTORY_EXCLUDE_PATTERN regex filter on prompt_toolkit FileHistory (pwnshell.py:55–65). Commands matching password|secret|hash|token|pfx|ccache (case-insensitive) are not written to .pwnrm_history, preventing credential strings from persisting to disk across sessions.


Niche Source Audit Fixes (NICHE-01 – NICHE-02)

  • NICHE-01: Replaced bare except: clause in utfstr() (utils.py:34–40) with except Exception: — preserves KeyboardInterrupt and SystemExit propagation during XML decode errors, fixing silently swallowed Ctrl+C in long enumeration runs.

  • NICHE-02: Sanitized single-quote injection in SMB target array construction in get_shares_ps() (shares.py:18–28). Target strings are now stripped and double-quote escaped via t.strip().replace('"', '"')before splicing into the@('TARGETS')` PowerShell array literal.


Architectural Additions (Tier 1–3)

  • Ephemeral Session Encryption: In-memory Fernet key (_SESSION_KEY = Fernet(Fernet.generate_key())) generated at process start and used to encrypt all serialized session metadata written to ~/.pwnrm/sessions/. Key exists only in process memory — session files are unreadable after PwnRM exits, eliminating post-engagement forensic credential recovery from session artifacts.

  • Dual ETW Telemetry Evasion: !evasion now patches both EtwEventWrite and EtwEventWriteFull in ntdll.dll. Windows 10 21H2+, Windows 11, and Server 2022/2025 route a significant subset of kernel ETW events through EtwEventWriteFull — patching only EtwEventWrite left a detectable telemetry path open on all post-21H2 targets.

  • Structured Loot MANIFEST.json (core/loot.py): LootManager now maintains an operational MANIFEST.json under ~/.pwnrm/loot/ with per-artifact entries including filename, type, category, source_command, ISO 8601 timestamp, sha256, target, and path. Both store_credential() and store_artifact() update the manifest atomically via O_CREAT | O_WRONLY | O_TRUNC with 0o600 permissions.

  • Multi-Session Output Labeling: fan_out_exec output streams in !session exec-all are now prefixed with [S:{sid} | {host}] tags per line, disambiguating interleaved output across concurrent sessions.

  • Plugin Integrity Verification: ModuleManager now calls verify_plugin_integrity() before dynamic import — validates that each *.py module file is owned by the current user (st_uid == os.getuid()) and is not world-writable (st_mode & stat.S_IWOTH == 0). World-writable plugin files are rejected with a warning.

  • BloodHound CE v6 Meta Headers: !bloodhound in-memory LDAP enumeration output now includes correct BloodHound CE v6 metadata headers {"meta": {"type": "...", "count": N, "version": 6, "methods": 0}} — previously missing headers caused silent rejection or misparse on BloodHound CE ingestion.

  • Playbook on_fail Cleanup Hooks: PlaybookRunner YAML DSL now supports cleanup_on_fail keys per step. On mid-playbook failure, registered cleanup commands are executed in reverse order against the active session to remove staged artifacts before the operator loses access.

  • RBCD Inspection in !kerberos: Added msDS-AllowedToActOnBehalfOfOtherIdentity attribute enumeration. Surfaces write-capable principals and flags RBCD abuse candidates alongside existing S4U2Self / constrained delegation checks.

  • Enterprise CA Health Check in !adcs: Added CA certificate expiration detection and CRL/AIA distribution point reachability probing. Offline CDP and AIA misconfiguration are flagged as strong indicators of unmaintained PKI — correlated with elevated ESC4/ESC7 probability in the output.

  • Type Annotations (PEP 484): Comprehensive type annotations added across core/api.py, core/credentials.py, core/psrp.py, core/runspace.py, core/session_mgr.py, core/transports.py, and core/utils.py. All public method signatures now carry Optional, Union, Tuple, List, Callable, and Generator hints compatible with Python 3.10+ strict mode.

  • Test Suite Expansion: 22 unit and security regression tests passing at 100% success rate (pytest / unittest), up from 21 in v2.0.0.


Full Changelog: v2.0.0...v2.0.1