Skip to content

Argument injection in less-osc8-open enables RCE on link activation #779

@mat-mo

Description

@mat-mo

The shipped script less-osc8-open.sh is registered as the default value of LESS_OSC8_OPEN_ANY in decode.c:

static char dflt_vartable[] =
{
    "LESS_OSC8_OPEN_ANY\0\201"
#ifdef LIBEXECDIR
        "-" LIBEXECDIR "/less-osc8-open"
#else
        "-less-osc8-open"
#endif
        "\0"
};

When the user activates an OSC 8 hyperlink (Ctrl-O Ctrl-O, or a second mouse click in mouse mode), osc8_open() in search.c invokes the handler with the URI as a single shell-quoted argument:

uri_q = shell_quoten(op.uri_start, uri_len);
cmd = ecalloc(strlen(handler) + strlen(uri_q) + 2, sizeof(char));
sprintf(cmd, "%s %s", handler, uri_q);
...
lsystem(exec_cmd, done_msg);

That delivers the URI verbatim into the script as $1. Inside the script, the man:NAME(SECTION) case splits the URI:

case $1 in
man:?*\(*\) )
    sect=${1#*\(}; sect=${sect%?}
    name=${1#man:};   name=${name%%\(*}
    man ${sect:+"$sect"} "$name"
    ;;

${sect:+"$sect"} expands to a single argv word, but man still interprets a word beginning with - as an option. Both GNU man-db and BSD man accept -P pager/-Ppager/--pager=pager with shell-tokenised pager strings, and man-db documents that the value "may be a simple command name or a command with arguments." Tokenisation alone is sufficient for execution: the pager argv is execvp'd with man's formatted output on stdin.

Reproduction

URI inside OSC 8 sequence:    man:ls(-P /usr/bin/touch /tmp/pwn)
OSC 8 wire form:              \x1b]8;;man:ls(-P /usr/bin/touch /tmp/pwn)\x1b\\click here\x1b]8;;\x1b\\

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