Security: Don't add relative directories to PATH

Issue #90 closed
created an issue

As documented on the Wiki, uru currently adds two dummy entries to the PATH to mark the location where it added new Ruby directories to that variable. I understand why this is needed, but the specific names chosen for these markers have some unfortunate side effects which could be exploited by an attacker to trick a user into executing malicious code.

Specifically, if a malicious actor creates a directory with the name _U1_, any executable files added to that directory will override system commands when the user cd's to that directory's parent after invoking uru. So the file _U1_/ls could easily be run by a user accidentally if they cd to _U1_'s parent directory and then decide they want to know what files are in it.

In order to prevent this, uru should use absolute paths to a directory it controls in place of the relative _U1_ and _U2_ markers. (E.g. /path/to/uru/dummy/_U1_ and /path/to/uru/dummy/_U2_ instead of _U1_ and _U2_.)

For more discussion on this, see Is it safe to add . to my PATH? How come? on StackExchange. All the same concerns there apply to this case.

Comments (20)

  1. Jon repo owner

    Using absolute PATH markers does not appear to solve your scenario.

    For example, try something similar to the following after adding a local bin dir (e.g. - /home/you/bin) to your PATH:

    echo echo downloading something bad >> /home/you/bin/apt-get
    chmod +x /home/you/bin/apt-get
    cd ~
    apt-get clean

    It doesn't appear to matter whether an absolute path or relative _U1_ path component is appended to PATH. In either case, the attacker could create the fake dir and drop the malicious exe in that dir. Regardless of whether the attacker pawnd your system or tricked you into running a script, the result is the same. You are in trouble because the malicious exe (appended to your PATH via either an absolute or relative path) now overide's a system command.

  2. Ajedi32 reporter

    The difference with that case is that it requires the attacker to have the ability to plant the malicious executable at a specific path in the user's home directory, instead of just anywhere the user might cd to.

    If I add an absolute directory like /home/me/bin to the front of my PATH, I can easily make sure that only I have write access to that directory, not every user on the system, and I can avoid placing untrusted files in that directory.

    With a relative path though, I have to trust literally every directory I cd to to not contain malicious code.

    Imagine I download a tar file from the internet with some cool cat pictures in it, and extract it to ~/downloads. Then I cd to that directory and run ls to see what photos I have. If a relative directory is on my PATH, I could easily get pwned just by doing that. If instead I used an absolute directory in my PATH, I won't get pwned unless I untarred those cat photos to, for example, /home/me/bin, which would be rather stupid of me, especially given that I'm fully aware that /home/me/bin is on my PATH.

  3. Jon repo owner

    Multiple scenario's exist, including the following relevant to uru's current PATH behavior:

    1. Your system gets pawned - uru's behavior is the least of your worries in this game over scenario.
    2. Attacker convinces you to run, with privileges, a malicious exe - another variant of (1) with uru's behavior essentially a don't care.
    3. Attacker convinces you to run a malicious exe - the exe runs with your privileges and can do anything you can do, including tweaking your PATH, creating subdirs in $HOME, downloading payloads into newly created subdirs, etc.

    Potential security concerns are not fixed If uru switches to using absolute PATH markers. In fact, the attack surface may be larger. In that case, a malicious exe would be usable (via short name) from any location in your filesystem. With uru's current behavior, the malicious exe is usable (via short name) from the parent dir of the "fake" relative dir when relative PATH markers are used.

    The entire relative vs. absolute PATH marker distinction may be meaningless. If an attacker who's written a piece of malware that can create dirs, determine its current location, download, and do other nasty things, one should assume the attacker will not be slowed down by either absolute or relative PATH components.

    I don't see how changing uru's current relative PATH markers to absolute paths meaningfully enhances security.

  4. Ajedi32 reporter

    I already presented a scenario in my previous comment that falls into none of those three categories you mentioned, yet still allows an attacker to execute malicious code on your system.

    tar files are not executables, and I don't have to "run" a tar file in order to extract it. Yet, as things are, cding to a directory I just extracted from a tar file and then running any common system command while uru is active could easily result in my computer being compromised.

    To be clear, let me explain the scenario I previously described in more detail:

    1. You download a tar file (such as the one I attached to this comment) from the internet: wget
    2. You extract the tar file to somewhere perfectly reasonable, a directory not on your PATH: tar -xf archive.tar Note that you are not executing the tar file or anything in it, just extracting it.
    3. You cd into the place you extracted the tar file to, then try to look at (not execute) the contents of one of the files you just extracted: cat cat_this_file.txt
    4. Oops. In addition to cat_this_file.txt, that tar file also contained an executable file _U1_/cat that does something malicious. You just got compromised. Note that you never intentionally "ran" anything from that archive, you were just casually inspecting its contents.

    And that's just one possible scenario. More generally, anytime you cd to a directory of files whose contents might be controlled by an attacker (a mounted network drive, another user's home directory on the same computer, etc), you're opening yourself up to unintentionally executing malicious code. Not including relative paths in your PATH variable would prevent this.

  5. Jon repo owner

    I understood your original scenario. That said, thank you for explicitly describing it. The details may entice others who view this issue as important to weigh in.

    The fundamental problem I have with your recommendation is that, while it appears to solve your particular scenario, it does not generally solve the root concern. Specifically, uru's current behavior of prepending stable, known dirs (absolute or relative) as PATH markers can be exploited.

    Absolute PATH markers (e.g. - /home/me/_U1_ or /home/_U1_ or /_U1_) can minimize the initial attack surface for your tar example, but you are still not immune. Because the PATH markers are currently fixed, any attacker who convinces you to run a malicious exe can populate those fixed, absolute dirs with a payload exe. Once that malicious payload is on your PATH, your tar scenario is not immune from compromise. It becomes a two step attack (reducing the attack surface) rather than the single step of your scenario.

    I'm still open to changing uru's current behavior. A bit of background...

    1. I originally used a ::: or ;;; as the single PATH marker. This did not accomomdate user's who prepended the PATH after using uru to activate a ruby via prepend PATH.
    2. I considered using pseudo random PATH markers (hashed current timestamp?), storing their values in the $HOME/.uru/rubies.json file, and using those values (that change every time you switch rubies) to munge PATH. The idea being that if the prepended PATH tokens kept changing, an attacker cannot easily know the PATH, thereby making compromise much harder. I did not like the extra complexity and corner cases.

    Based upon our discussions, I am reconsidering options. For example, can I easily use two ::: to compartmentalize uru's prepending PATH behavior? (e.g. - :::/home/me/.gems/bin:/home/me/.rubies/ruby-2.3/bin::: or even /_U1_ and /_U2_ for both *nix and windows). The goal is to (a) minimize attack surface, (b) ensure acceptable usage performance, and (c) not create new behaviors that can be exploited.

  6. Ajedi32 reporter

    I'm not really all that concerned about a scenario where an attacker has already convinced you to run a malicious exe. In such a scenario you are already compromised (rule 1 of the 10 immutable laws of security) and the attacker gains nothing from placing more malicious executables on your PATH.

    And even if they did want to override system executables with their own applications, they could easily do so by changing your ~/.profile or ~/.bashrc to put any directory they want on your PATH. No need to hijack any directory used by uru.

    I would not recommend using ::: as a delimiter, as many systems interpret an empty value between two : characters as equivalent to having . on your path, which presents a very similar problem as a relative directory like _U1_.

    /_U1_ and /_U2_ seem fine, so long as we can be certain other users aren't able to create those directories. On Linux it seems like only root can do that, which is fine, but I'm uncertain about Windows. (I'm pretty sure normal users can write to the root of the C drive.) I'd suggest ~/_U1_ and ~/_U2_ as the best alternative. It's simple, and points to a directory that only the current user should have write access to on all systems.

  7. Jon repo owner

    I don't currently see a way to completely solve this scenario, but I can minimize the attack surface by using /_U1_ and /_U2_ as the PATH delimiters.

    Try the v0.8.3.b1 test version now available at the downloads page and confirm that it (a) fixes your tar scenario, and (b) still behaves correctly in your other scenarios.

    Simply back up your existing uru_rt and extract the new version to the previous location.

  8. Ajedi32 reporter

    Yes, that resolves the problem for me. It also looks like git bash on Windows uses C:\Program Files\Git as /, so that directory should be safe from modification from other users on the same system too.

  9. Jon repo owner

    Great. The fix is not currently working in my git bash on windows, so the v0.8.3 release will be delayed until I root cause.

  10. Jon repo owner

    Using git-bash.exe from Git for Windows v2.9.3.2 on Win8.1 x64, I get the following incorrect behavior.

    Uru test version 0.8.3.b1 adds the /_U1_:...:/_U2_ PATH hunk correctly but (a) a following uru ls does not indicate which ruby was added to PATH, and (b) uru does not appear able to remove the PATH hunk correctly when uru nil is executed or when uru attempts to switch to another ruby.

    Jon@BLACK MINGW64 ~/Documents
    $ git --version
    git version
    Jon@BLACK MINGW64 ~/Documents
    $ uru ver
    uru v0.8.3.b1 [windows/386 go1.7]
    Jon@BLACK MINGW64 ~/Documents
    $ uru ls
        226p369-x32 : ruby 2.2.6p369 (2016-08-16 revision 55943) [i386-mingw32]
        232p181-x32 : ruby 2.3.2p181 (2016-08-28 revision 56022) [i386-mingw32]
        jruby       : jruby (2.3.0) 2016-05-26 7357c8f Java HotSpot(TM) 64-Bit...
    Jon@BLACK MINGW64 ~/Documents
    $ uru 232
    ---> now using ruby 2.3.2-p181 tagged as `232p181-x32`
    Jon@BLACK MINGW64 ~/Documents
    $ echo $PATH
    /_U1_:/C/Apps/rubies/ruby-2.3/bin:/_U2_:/C/Users/Jon/bin:/C/Apps/git/mingw64/bin:/C/Apps/git/usr/local/bin:/C/Apps/git/usr/bin:/C/Apps/git/usr/bin:/C/Apps/git/mingw64/bin:/C/Apps/git/usr/bin:/C/Users/Jon/bin:/C/Windows/system32:/C/Windows:/C/Windows/System32/Wbem:/C/Windows/System32/WindowsPowerShell/v1.0:/C/ProgramData/chocolatey/bin:/C/tools:/C/Apps/git/cmd:/C/Apps/Mercurial:/C/Program Files/Java/jdk1.8.0_102/bin:/C/Apps/git/usr/bin/vendor_perl:/C/Apps/git/usr/bin/core_perl
    Jon@BLACK MINGW64 ~/Documents
    $ uru ls
        226p369-x32 : ruby 2.2.6p369 (2016-08-16 revision 55943) [i386-mingw32]
        232p181-x32 : ruby 2.3.2p181 (2016-08-28 revision 56022) [i386-mingw32]
        jruby       : jruby (2.3.0) 2016-05-26 7357c8f Java HotSpot(TM) 64-Bit...
    Jon@BLACK MINGW64 ~/Documents
    $ ruby --version
    ruby 2.3.2p181 (2016-08-28 revision 56022) [i386-mingw32]
    Jon@BLACK MINGW64 ~/Documents
    $ uru nil
    Jon@BLACK MINGW64 ~/Documents
    $ echo $PATH
    /_U1_:/C/Apps/rubies/ruby-2.3/bin:/_U2_:/C/Users/Jon/bin:/C/Apps/git/mingw64/bin:/C/Apps/git/usr/local/bin:/C/Apps/git/usr/bin:/C/Apps/git/usr/bin:/C/Apps/git/mingw64/bin:/C/Apps/git/usr/bin:/C/Users/Jon/bin:/C/Windows/system32:/C/Windows:/C/Windows/System32/Wbem:/C/Windows/System32/WindowsPowerShell/v1.0:/C/ProgramData/chocolatey/bin:/C/tools:/C/Apps/git/cmd:/C/Apps/Mercurial:/C/Program Files/Java/jdk1.8.0_102/bin:/C/Apps/git/usr/bin/vendor_perl:/C/Apps/git/usr/bin/core_perl
    Jon@BLACK MINGW64 ~/Documents
    $ uru jr
    ---> now using jruby 9.1.2 tagged as `jruby`
    Jon@BLACK MINGW64 ~/Documents
    $ uru ls
        226p369-x32 : ruby 2.2.6p369 (2016-08-16 revision 55943) [i386-mingw32]
        232p181-x32 : ruby 2.3.2p181 (2016-08-28 revision 56022) [i386-mingw32]
        jruby       : jruby (2.3.0) 2016-05-26 7357c8f Java HotSpot(TM) 64-Bit...
    Jon@BLACK MINGW64 ~/Documents
    $ echo $PATH
    /_U1_:/C/Apps/rubies/jruby/bin:/_U2_:/C/Apps/git/_U1_:/C/Apps/rubies/ruby-2.3/bin:/C/Apps/git/_U2_:/C/Users/Jon/bin:/C/Apps/git/mingw64/bin:/C/Apps/git/usr/local/bin:/C/Apps/git/usr/bin:/C/Apps/git/usr/bin:/C/Apps/git/mingw64/bin:/C/Apps/git/usr/bin:/C/Users/Jon/bin:/C/Windows/system32:/C/Windows:/C/Windows/System32/Wbem:/C/Windows/System32/WindowsPowerShell/v1.0:/C/ProgramData/chocolatey/bin:/C/tools:/C/Apps/git/cmd:/C/Apps/Mercurial:/C/Program Files/Java/jdk1.8.0_102/bin:/C/Apps/git/usr/bin/vendor_perl:/C/Apps/git/usr/bin/core_perl
    Jon@BLACK MINGW64 ~/Documents
    $ jruby --version
    jruby (2.3.0) 2016-05-26 7357c8f Java HotSpot(TM) 64-Bit Server VM 25.102-b14 on 1.8.0_102-b14 +jit [mswin32-x86_64]
  11. Ajedi32 reporter

    Confirmed; I'm seeing the same issue. I wonder if that might be because git bash is presenting a different value of the PATH to uru than what git bash shows. Unix-style paths like /bin don't really exist on Windows, so git bash is probably translating that into something more "windowsy" before passing it to uru.

    For example:

    $ uru 223
    ---> now using ruby 2.2.3-p173 tagged as `223p173`
    $ echo $PATH
    $ ruby -e 'puts ENV["PATH"]'
    C:\Program Files\Git\_U1_;C:\Ruby22-x64\bin;C:\Program Files\Git\_U2;...
  12. Jon repo owner

    Thanks for checking, I was speculating the same thing re: git bash (aka msys2 cygwin fork) behavior.

    That said, I've first got to spelunk the uru chunk removal code for bad assumptions/coding.

  13. Jon repo owner

    @Ajedi32 given msys2's munging of absolute PATHs as you've shown and I've documented in #92, what do you think of this solution:

    ...before adding _U1_ and _U2_ relative canaries to PATH, uru first checks for the presence of those relative dirs. If one exists, uru immediately exists with a useful error message, refusing to add _U1_ and _U2_ to PATH?

  14. Ajedi32 reporter

    Assuming you're only checking when uru is first activated, that's not a secure solution since merely changing directories could change whether _U1_ and _U2_ exist, by virtue of the fact that they're relative paths. In particular, the scenario I outlined in my previous comment would not be prevented by that change.

    My recommendation would be to use a known absolute path for _U1_ and _U2_; maybe the same path uru is installed in. Something like "$(dirname `which uru_rt`)/_U1_" and "$(dirname `which uru_rt`)/_U2_". The assumption being that anyone who can write to that directory would already have the ability to replace uru_rt with a malicious executable, and that users would be smart enough to not add untrusted executables to a location they know is on their PATH.

  15. Jon repo owner

    @Ajedi32 I really like your dynamic canary idea, and have implemented a simpler variant in the new 0.8.3.rc1 downloads.

    They work for me on Win8.1 x64, and git-bash from GfW v2.10.2 x64....not yet tested on Ubuntu 16.10 or Fedora 24 x64. It's untested on OSX as I don't use that platform anymore.

  16. Jon repo owner

    Fix weakness with PATH canaries. Closes #90, #92

    Uru's use of relative PATH canaries _U1_ and _U2_ to sandbox PATH to enable easy activation and deactivation of registered rubies was a security weakness. This commit minimizes that weakness by using absolute PATH canaries /_U1_ and /_U2_ in Windows, Linux, and OSX environments. In MSYS2-based systems on Windows such as git-bash, U:\_U1_ and U:\_U2_ are used.

    → <<cset cf4751fec569>>

  17. Log in to comment