July 22nd, 2021

The case of the strange NT-style path that was discovered by SearchPath

A customer reported that the Search­Path function was returning an NT-style path, which is not something their program (an IIS CGI handler) could deal with. Why are they getting an NT-style path?

The problem was not reproducible when run under a debugger, so we had to resort to tracing and logging.

One theory was that IIS was putting an NT-style path in the PATH, and that’s where it was coming from. But logging the value of the PATH environment variable showed no weird \\?\ directories.

I recalled that when you have eliminated the impossible, whatever remains, however improbable, must be the truth. The SearchPath function looks in the directories specified in the PATH environment variable, and if the item can’t be found in any of those directories, then it looks in the current directory.

We have ruled out the PATH environment variable.

Therefore, the current directory must be the one with the NT-style path.

It turns out that, for security reasons, IIS runs CGI programs via their NT path, and also uses an NT path as the current directory. It so happened that the file was found in the current directory, so that’s what Search­Path returned.

We can even run a test program to check the theory.

#include <windows.h>
#include <stdio.h>

int __cdecl main()
{
  SetCurrentDirectoryW(L"\\\\?\\C:\\Users");

  wchar_t buffer[256];
  wchar_t* file;
  if (SearchPath(nullptr, L"Public", nullptr,
     256, buffer, &file)) {
     printf("%ls\n", buffer);
  }
}

Run this program and it prints

\\?\C:\Users\Public

Okay, that explains the problem. But how do they fix it?

You could try to convert the NT path to a Win32-style path before processing it. But really, since this is a CGI program, you probably shouldn’t be grabbing files from the current directory in the first place. You aren’t really in control of your current directory, and the fact that you’re looking up things in the current directory means that an attacker might be able to pass paths like ..\..\wwwroot\config\jackpot.xml, and steal files from anywhere on your system.

So the real issue is that their program is somehow reliant upon the current directory, and they should fix it to remove that dependency.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • Daniel Marschall

    I think it is OK to read files from the current directory, as long as the paths are properly escaped and path traversal is addressed (i.e. removing “..\”)

  • Neil Rashbrook

    I read this as the application looking for a known file not realising that someone was trying to hijack it by dropping a substitute in the current directory and being protected by the given behaviour of IIS, so that the application's dependence on the current directory was inadvertent.

    Best practice would appear to depend on why the application needs to use SearchPath, but maybe they could use the API to prefer the to the...

    Read more
    • Stefan Kanthak · Edited

      Enforcing the “safe process search mode” won’t help
      1. if an adversary can drop a substitute in a directory listed in %PATH% before the directory where the searched for executable is located;
      2. if an adversary can drop an arbitrary/unique named executable in a directory listed in %PATH%, including the CWD, and feeds this arbitrary/unique name to SearchPath() or CreateProcess() or …

      Better call SetCurrentDirectory(“\\\\.\\nul\\”) instead of SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE)

  • Jay K

    These are not NT-style paths, depending on definition.
    These are Win32 paths.
    NT paths are like \??\.
    Granted you said “NT style”.
    These are perfectly valid, heavily documented, etc.
    Wonky, granted.

  • 紅樓鍮

    Just as an aside for any C++ programmer reading the comments who doesn’t already know this, In C++11+ you can write raw string literals like LR"(\\?\C:\Users)" for Windows paths, avoiding the need to escape backslashes.

  • Martin Ba

    “… when you have eliminated the -improbable- …” -> that should read “… when you have eliminated the *impossible* …”, shouldn’t it?