Skip to content

Fix command injection via crafted Wi-Fi QR code - #36

Open
ph-rast wants to merge 3 commits into
kokoye2007:masterfrom
ph-rast:qr-command-injection
Open

Fix command injection via crafted Wi-Fi QR code#36
ph-rast wants to merge 3 commits into
kokoye2007:masterfrom
ph-rast:qr-command-injection

Conversation

@ph-rast

@ph-rast ph-rast commented Jul 28, 2026

Copy link
Copy Markdown

Fix command injection via crafted Wi-Fi QR code

Summary

Audited wifi-qr with Claude Opus 4.8. Audit returned a security issue:

Scanned QR content is parsed into an associative array with eval (in call_wifi_scan,
via urimap_str). The value side of each field is safely quoted with @Q, but the
key side (${element%%:*}) is interpolated into the eval unquoted. Since QR content
is fully attacker-controlled, a command substitution placed in the key position is
executed by the shell.

Because the tool's purpose is to scan untrusted QR codes, this lets anyone who can get
a user to scan a QR image/code they control run arbitrary commands as that user.

  • Type: OS command injection (CWE-78)
  • Privilege: runs as the invoking user (not setuid; no privilege escalation)
  • Vector: all scan modes — -s, -q, -f FILE, -p

Reproduce

# benign PoC: the payload creates /tmp/INJECTED_PROOF
rm -f /tmp/INJECTED_PROOF
qrencode -o evil.png 'WIFI:$(touch /tmp/INJECTED_PROOF);;'
wifi-qr -f evil.png
ls /tmp/INJECTED_PROOF   # exists on the unpatched version -> code executed

The decoded string flows zbarcam/zbarimg --rawwifiqrdata
urimap_str "${wifiqrdata:5:-1}"eval, so everything between the WIFI: prefix and
the trailing ; reaches eval.

Root cause

eval re-parses its argument as shell. The value is protected by @Q, but the key is
not, so an unquoted $(...) / backtick in the key position is command-substituted. The
intermediate eval is also unnecessary — the consumer only reads a fixed set of keys.

Fix

Remove the eval entirely: urimap_str becomes urimap_parse, which receives the target
associative array by nameref and assigns keys/values directly. For a declare -A array
the subscript is treated as a string, so a hostile key is inert. The legacy pre-processing
block is left byte-for-byte identical to the original.

Testing

  • The PoC payload no longer executes, in both normal and -L legacy mode.
  • Parsing behaviour is otherwise identical to the previous implementation across normal
    codes, percent-encoded values, empty passwords, legacy escaped-semicolon inputs,
    values with spaces, and reordered fields.
  • Builds and runs on Arch Linux (installed from source and exercised via -f).

ph-rast added 3 commits July 28, 2026 18:59
Scanned QR content was parsed into an associative array via eval. The
value side was quoted with @q, but the key (${element%%:*}) was left
unquoted, so a command substitution placed in the key position of a
crafted QR code was executed by eval as the invoking user.

Replace urimap_str with urimap_parse, which populates the array by
nameref and assigns keys/values directly, removing the eval entirely.
For a declare -A array the subscript is a plain string, so hostile keys
are inert. Normal WiFi:S:...;T:...;P:...; codes still parse unchanged.

Reachable via all scan modes (-s, -q, -f, -p). CWE-78.
Restore legacy escaped-semicolon handling
legacy mode escapes corrected to original. Got doubled during copy-paste
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.

1 participant