Skip to content

command field type raises ValueError: No closing quotation for text with an unclosed quote #241

Open

Description

@Che4ter

Description

fieldtypes.command._split calls shlex.split(...). Text with an unbalanced quote raises
ValueError: No closing quotation when the record is created. A producer that builds records
from log text then stops at that line.

>>> from flow.record.fieldtypes import command
>>> command("C:\\Users\\a\\tool.exe /c 'x")
ValueError: No closing quotation

Same on release 3.21 and on main (e6b58fd), with posix=False and posix=True.

Effect

The Windows Defender MPLog plugin of dissect.target builds its lowfi, threat and command
fields from log text. One line like the above ends the generator, so every later line of the
file is lost. Reproduction without a real log: four lines of the form
<timestamp> x lowfi: C:\tool.exe /c <text>, the second with an unclosed quote, give 1 record
and then the exception; the other three are lost.

On a real Windows 11 Pro lab collection (NT 10.0 build 26200.8875), dissect.target 3.25.1 with
flow.record 3.21,
target-query -f defender.mplog exits 1 after 2,677 records with this error. With flow.record
main (e6b58fd) plus the change below, the same command exits 0 and returns 4,129 records.
A run-time version of the same fallback reports 18 values with an unclosed quote in that file. I
did not look at the content of those lines, so I do not say what causes the quote.

Proposed fix

Fall back to whitespace splitting when shlex.split raises, so the record is still created, keeps
its text and the executable is the first token:

        try:
            executable, *args = shlex.split(value, posix=self.__path_type is posix_path)
        except ValueError:
            # Unbalanced quote (for example a truncated log line): keep the text, split on whitespace.
            executable, *args = value.split()
        return executable.strip("'\" "), (*args,)

Behaviour of the fallback, checked on main with raw, RecordPacker pack/unpack and the JSON
packer: for an unclosed quote in the arguments (C:\tool.exe /c 'unclosed, /bin/x -c 'a b)
raw equals the input, the record round-trips and the JSON output holds the original text. Like
for any command, runs of spaces collapse to one. If the quote opens the executable path
('C:\Program Files\x.exe /c y) the split is only approximate (executable C:\Program) and the
leading quote is dropped, as command already does for quoted paths; a value that is only a quote
gives an empty command. In every case no exception is raised and the rest of the file is read.

Tested on main: two new cases in test_command_windows and test_command_posix
(c:\tool.exe /c 'unclosed gives ("/c", "'unclosed")) fail with ValueError: No closing quotation without the change and pass with it; all 183 tests in tests/fieldtypes pass. If you
prefer to keep the strict behaviour by default, a keyword that selects the tolerant split would
also solve it for dissect.target.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions