Blaming the operating system for allowing people to create files with unusual characters in their names

Raymond Chen

Raymond

A security vulnerability report came in that claimed that Windows had a remote code execution vulnerability because it allows malicious characters in file names.

Specifically, they noted that if you created a Web server with an image upload page, and if an uploaded file contained a special character like an ampersand, then you could obtain remote code execution.

They included a complicated server-side script, but here’s the gist:

void OnFileUploaded(string path)
{
 system("dosomething.exe " + path);
}

A user could upload files with odd names like

x & logoff &.jpg
x & certutil -installcert -f badcert.jpg
x & sc stop server &.jpg

The resulting commands passed to the system function are

dosomething.exe x & logoff &.jpg
dosomething.exe x & certutil -installcert -f badcert.jpg
dosomething.exe x & sc stop server &.jpg

The ampersand is a CMD.EXE metacharacter for combining multiple commands into a single line, so each of the above lines is treated as multiple commands. The first is dosomething.exe x, which presumably fails because there is no file named x. The second is something dangerous. And sometimes there’s a third command .jpg which is probably going to fail with '.jpg' is not recognized as an internal or external command, operable program or batch file.

The finders claimed that this proves that ampersands in file names are a remote code execution vulnerability in Windows.

What we have here is a case of creating an insecure system and then being surprised that the system is insecure. The On­File­Uploaded function passes untrusted data directly to the system function without any attempt to sanitize it first. This is a classic injection attack (obXKCD), and it is the responsibility of code that builds commands out of strings to be alert to issues like this.

You’d be best advised to avoid things that do their own level of nontrivial parsing over the generated command line. SQL commands and command lines are examples of this. In the above example, it would be better to run the dosomething.exe program directly and pass the file name (suitably quoted), to avoid any funny business with CMD.EXE command line parsing.¹

When we informed the finders that there was no Windows vulnerability here, they insisted that the issue they found is a critical remote code execution vulnerability, shared a paper they planned on presenting, and warned that publication could likely result in widespread attacks within hours of disclosure, resulting in compromised Windows systems around the globe.

I reviewed the paper and verified that it didn’t do anything beyond what they had described in their original report. We advised them that the vulnerability was in their Web server, not in Windows, and gave them permission to disclose.

As far as I can tell, they never did publish that paper anywhere.

Bonus chatter: They claimed that the issue could be fixed by simply adding the ampersand to the list of illegal file name characters. They forgot about the percent sign (for injecting environment variables), the caret (for escaping), and possibly even the apostrophe. They may also want to file vulnerabilty reports against unix since it is vulnerable to the exact same problem: If somebody takes an untrusted file name and injects into into a command passed to the system function, bad things can happen. It’s even worse on unix, because unix has almost no forbidden file name characters. Even a newline is a legal filename character! So go ahead, put a bitcoin miner in your file name, let the server do some real work for you.

¹ If you don’t trust yourself to quote potentially-malicious file names safely (and I don’t blame you), you could pass the file name some other way entirely, like via stdin or by putting the dangerous file name in a file, and passing that file as an indirect parameter: something.exe @indirect.

14 comments

Comments are closed. Login to edit/delete your existing comments

  • Avatar
    Andrew

    This is amazing. I’ve written code that runs on a web server and processes uploaded files but I don’t think I’ve ever used the file name provided by the client. If the file gets written to a file on the server at all, it’s probably named xyzE51F.tmp or similar. In fact, I once had to write my own version of Path.GetExtension() because the .NET Framework implementation barfs if you feed it a file name containing characters that are illegal on Windows, which isn’t much use when you could be dealing with files uploaded from a Mac or some flavour of UNIX.

    • Avatar
      Brian MacKay

      I’ve kept the same file name on uploads before (sometimes it’s useful). But, you run the name through a path and/or file name sanitizer (based on Path.GetInvalidFileNameChars and/or Path.GetInvalidPathChars). But, since all the characters in Raymond’s examples are legal, no sanitizing would likely happen.

  • Avatar
    J.O.

    Windows is insecure because it allows my child to use it, and someone could tell my child to type in a dangerous command, and I can’t be bothered to supervise them.

  • Avatar
    Stefan Kanthak

    If you don’t trust yourself to quote potentially-malicious file names safely (and I don’t blame you), you could pass the file name some other way entirely, like via stdin or by putting the dangerous file name in a file, and passing that file as an indirect parameter: something.exe @indirect.

    Let’s feed some really malicious filenames to several something.exe shipped with Windows:

    mkdir "foo bar"
    hh.exe -decompile "foo bar" "%windir%\help\mui\0409\mmc.chm"
    hh.exe -decompile "foo bar" %windir%\help\mui\0409\mmc.chm
    hh.exe -decompile . "%windir%\help\mui\0409\mmc.chm"
    rundll32.exe cryptext.dll,CryptExtOpenCAT "‹any valid .CAT›"
    start "" "‹any valid .CAT›"
    rundll32.exe cryptext.dll,CryptExtOpenCER "‹any valid .CER›"
    start "" "‹any valid .CER›"
    rundll32.exe cryptext.dll,CryptExtOpenCRL "‹any valid .CRL›"
    start "" "‹any valid .CRL›"
    copy NUL: "foo bar.sed"
    iexpress.exe "foo bar.sed"
    echo >"foo bar.cmd" set /p ?=!cmdcmdline!
    cmd.exe /c "foo bar.cmd" "first" ... "last"
    'foo' is not recognized as an internal or external command, operable program or batch file.
    cmd.exe /c @"foo bar.cmd"
    '"foo bar.cmd"' is not recognized as an internal or external command, operable program or batch file.
    del "foo bar.*"
    
    • Avatar
      Joshua Hudson

      For extra fun, let’s see how many backup solutions fail on creating a directory called “/” with a file called “CON” in it. Yes, you can make both of these. Gotta use the NtXxxxxXxxx function for the former, but the latter can be made with \\?\ syntax.

      • Avatar
        Stefan Kanthak

        It’s MUCH simpler than that, there’s absolutely no need for a third party application to demonstrate such beginner’s errors in Windows components!
        Create files named ‹prefix›%PUBLIC%‹suffix›.bat, ‹prefix›%PATH%‹suffix›.cmd, ‹prefix›!ProgramFiles!‹suffix›.bat, ‹prefix›!SystemRoot!‹suffix›.cmd etc., then open them per double-click … and admire the rather short flashing window of the command processor with the message "The filename, directory name, or volume label syntax is incorrect"

        Quoting Raymond Chen: … it is the responsibility of code that builds commands out of strings to be alert to issues like this.

        • Avatar
          Danielix Klimax

          Would you kindly explain what it demonstrates besides your lack of understanding of anything Windows related?

          ETA: Last time you demonstrated Raymond Chen’s point about how to attack yourself while being on the other side of airlock, so what it is this time? How to self-sabotage if you are admin?

          • Ben Voigt
            Ben Voigt

            It demonstrates that the shell does not correctly quote filenames when invoking verbs. You can try to blame the verb author for just doing `myprogram.exe “%1″` and assuming that a double-quote will be sufficient, but that falls right back on the shell (and the ShellExecute function) for not giving verb authors any more powerful quoting options.

            Besides, these particular verbs are preinstalled by Microsoft, so Stefan’s point still applies.

  • Avatar
    cheong00

    I can see where it’s going, as there are lots of people passing the uploaded images to imagemagick for resizing because they don’t bother to write their own, and I’ve seen some of the imagemagick wrappers do what is described above. (The similar thing goes to ghostscript and some other situation that uses command line utility to process file without sanitizing the input)

    So this is possibly a real world problem, and yes, the problem is in the code of website, or possibly on a library they choose to use, but not on the operating system itself.

  • Avatar
    Ian Boyd

    It reminds me of the hysteria a few years ago, when everyone was convinced that Chrome was storing passwords in plaintext, because you could go into the password manager and view the saved password.

    They can see the plaintext password with their eyes, so their mental model can only conclude that the passwords themselves must be stored in plaintext. No amount of explaining Cryptography, showing them the source code, showing them the CryptProtectData function documentation, dumping the internal sqlite database showing that the password is indeed encrypted.

    They’ve had something they’ve seen with their eyes, and their mental model is now indelible.

    This guy has seen code executed remotely. He now has that mental model set in concrete.

  • Avatar
    Stefan Kanthak

    This is a classic injection attack (obXKCD), and it is the responsibility of code that builds commands out of strings to be alert to issues like this.

    Amen!

    ftype | findstr.exe /I "%%SystemRoot%% %%ProgramFiles%% %%ProgramData%% %%CommonProgramFiles%%"

    What we have here is a case of creating an insecure system and then being surprised that the system is insecure.

    JFTR: I’m not surprised, I know who I’m talking to!