Skip to content

Command injection in bundled netntlm.pl via shell metacharacters in the capture file name #6011

Open

Description

@nuartvision

Command injection in run/netntlm.pl

Advisory fields

  • Title: netntlm.pl executes shell commands from untrusted capture file name
  • Vulnerability class: Command injection
  • Affected version/commit: b544069b3 (bleeding-jumbo)
  • Affected code path:
    • run/netntlm.pl:68
    • run/netntlm.pl:79
    • run/netntlm.pl:111
    • run/netntlm.pl:146
    • run/netntlm.pl:181
  • Trust boundary crossed: An attacker-controlled hash/capture file is passed to a helper script that the user is expected to run on security data. The script then interpolates the file name directly into a shell pipeline (open ... "|" and system(...)), so untrusted file-system metadata becomes shell code.

Description

run/netntlm.pl is a bundled helper that takes an LM/NTLM challenge/response capture file (--file) and orchestrates John runs. The file name is interpolated unescaped into multiple shell command strings:

open (HAND, "$JOHN -format:netlm -show $opt{'file'} |") || die(...);
...
open (HAND, "$JOHN -format:netntlm -config:$tmpconf -wordlist:$tmpdict -rules -user:\"$account\" -session:$tmpsession $opt{'file'} |") || die(...);
...
system("$JOHN -format:netlm -config:$tmpconf -external:HalfLM -incremental:LM -session:$tmpsession $tmppasswd");

Because Perl open with a trailing | invokes a shell, and system with a single string also invokes a shell, shell metacharacters in $opt{'file'} are executed before john is even started. The account name parsed from previously cracked output is similarly double-quoted and susceptible to backtick/$() injection, but the file-name vector is the simplest to reproduce.

Preconditions

  • The attacker can provide or influence the name of the capture file passed to netntlm.pl.
  • The victim runs netntlm.pl --file <attacker-influenced name>.
  • The injected command must avoid / characters in the file name itself because Linux file names cannot contain /. Commands that use relative paths or shell builtins work directly; arbitrary absolute-path commands can be staged with a first-stage download/relative payload.

Step-by-step reproduction

  1. Create a capture file whose name contains an embedded shell command. The following uses backticks to run id > pwn.txt (relative to the current working directory):
# create a file literally named: /tmp/evil`id>pwn.txt`.lc
python3 - <<'PY'
open('/tmp/evil`id>pwn.txt`.lc', 'w').close()
PY
  1. Run the helper:
cd /home/john/run
perl netntlm.pl --file '/tmp/evil`id>pwn.txt`.lc'
  1. Observe that the injected command executes before the script reports "No seed supplied for testing." and that pwn.txt is created in the current directory:
uid=1000(test) gid=1000(test) groups=1000(test),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)

The uid=... line comes from the shell pipeline spawned by netntlm.pl, not from John output.

Impact

An attacker who can get a user to run netntlm.pl on a maliciously named file achieves arbitrary command execution as the user running the script. In a security-research or SOC workflow where analysts run helper scripts on untrusted capture files, this can lead to local privilege escalation if the script is invoked from a privileged wrapper, or to data exfiltration / malware installation otherwise.

Why this is not just an unsafe configuration

The vulnerability is in the script's handling of a normal product input (the capture file path). No optional or knowingly dangerous configuration is required; the script is intended to be run on third-party capture files.

Activity

  1. solardiz commented on Jul 12, 2026

    @solardiz
    Member

    Thank you for reporting this.

    The account name parsed from previously cracked output is similarly double-quoted and susceptible to backtick/$() injection, but the file-name vector is the simplest to reproduce.

    Actually, this may be a bigger concern than the filename vector.

    the script is intended to be run on third-party capture files.

    Is it? Maybe we should clearly document that it is not?

    Any other suggested fix?

  2. nuartvision commented on Jul 13, 2026

    @nuartvision
    ContributorAuthor

    as a "quick dirty fix" - replace all shell-invoking open ... "|" and system calls with Perl list-form invocation so the file name and account name are passed as separate execvp arguments and never interpreted by a shell. Example replacements for the five injection points:

    open(HAND, "-|", $JOHN, '-format:netlm', '-show', $opt{'file'})
        || die("Failed to execute john: $!");
    
    open(HAND, "-|", $JOHN, '-format:netntlm', '-show', $opt{'file'})
        || die("Failed to execute john: $!");
    
    open(HAND, "-|", $JOHN, '-format:netntlm', "-config:$tmpconf",
         "-wordlist:$tmpdict", '-rules', "-user:$account",
         "-session:$tmpsession", $opt{'file'})
        || die("Failed to execute john: $!");
    
    open(HAND, "-|", $JOHN, '-format:netntlm', "-config:$tmpconf",
         "-wordlist:$tmpdict", '-rules', "-session:$tmpsession",
         $opt{'file'})
        || die("Failed to execute john: $!");
    
    system($JOHN, '-format:netlm', "-config:$tmpconf",
           '-external:HalfLM', '-incremental:LM',
           "-session:$tmpsession", $tmppasswd);
  3. solardiz commented on Jul 13, 2026

    @solardiz
    Member

    replace all shell-invoking open ... "|" and system calls with Perl list-form invocation so the file name and account name are passed as separate execvp arguments and never interpreted by a shell.

    Well, but they could still be interpreted as options by the invoked john. We do not currently have a -- to stop option parsing, maybe we should add and use that.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions