Command Injection in LaTeX Workshop

exploitation vulnerability

Welcome to another round of Banana tweets and unintentionally makes people mad.

I’ve had a look at some VS Code extensions that make use of shell commands with the goal to find a command injection vulnerability. For this, I’ve grepped for child_process, since this is a NodeJS API that’s commonly used to execute shell commands in VS Code.

I’ve quickly found various extensions that make use of this API. A vulnerability exists in case:

  1. A part of the shell command is user-controlled.
  2. No filtering or shell escaping is applied to user input.

This means that not every usage of child_process is problematic. However, I’ve found one flaw of this kind in LaTeX Workshop which currently has over 1.1 million installs.

The extension adds Intellisense features for LaTeX to VS Code. One part of this feature involves suggestions for names of files that are present in the current project directory. Not every file name should be a part of the generated suggestions, since files could be ignored via git. This function takes care of the filtering task:

[...]
import * as cp from 'child_process'
[...]
private filterIgnoredFiles(files: string[], baseDir: string): string[] {
    const excludeGlob = (Object.keys(vscode.workspace.getConfiguration('files', null).get('exclude') || {})).concat(vscode.workspace.getConfiguration('latex-workshop').get('intellisense.file.exclude') || [] ).concat(ignoreFiles)
    let gitIgnoredFiles: string[] = []
    /* Check .gitignore if needed */
    if (vscode.workspace.getConfiguration('search', null).get('useIgnoreFiles')) {
        try {
            /* OSX pops up a dialog box to install `git` when it is not available.
            The `which` command exits with a non-zero return code when `git` is not found,
            which makes `execSync` throw an error */
            if (os.platform() === 'darwin') {
                cp.execSync('which git')
            }
            gitIgnoredFiles = (cp.execSync('git check-ignore ' + files.join(' '), {cwd: baseDir})).toString().split('\n')
        } catch (ex) { }
    }
    return files.filter(file => {
        const filePath = path.resolve(baseDir, file)
        /* Check if the file should be ignored */
        if ((gitIgnoredFiles.includes(file)) || micromatch.any(filePath, excludeGlob, {basename: true})) {
            return false
        } else {
            return true
        }
    })
}

As can be seen, git check-ignore gets called with a list of file names to perform this check. The issue here is that the value of files.join(' ') is not escaped before being passed to execSync(), which is the API to execute a shell command.

Now, imagine downloading a LaTeX template and editing it with this extension. There could be a hidden file with the name .`sleep 1337`, which causes the shell command to be git check-ignore .`sleep 1337`. This triggers a command injection as soon as Intellisense for file names is triggered:

tex hax

Until a fix is available, disabling search.useIgnoreFiles should prevent the exploit from succeeding.


That's all.

Analysis of Satisfyer Toys: Discovering an Authentication Bypass with r2 and Frida

radare2 r2 frida r2frida reverse-engineering web vulnerability

Haxxoring a Hisense Smart TV

exploitation reverse-engineering vulnerability

SROP Exploitation with radare2

r2 radare2 rop exploitation ctf