Что такое BIDI?
Двое специалистов в своей довольно старой публикации Trojan Source: Invisible Vulnerabilities описали одну из интересных атак, суть которой заключается в следующем: при просмотре исходного кода вы видите одно, но при компиляции в конечном приложении будет реализована совсем другая логика. Суть атаки проста: не все редакторы кода отображают unicode символы и рецензенты кода их попросту не увидят. Для реализации атаки необходимо использовать определенные символы в кодировке unicode, которые заставляют компилятор читать исходный код в другом направлении либо вызывать совсем другие функции.
Для подтверждения своей идеи авторы описывают различные способы атак с применением невидимых unicod символов. Для понимания сути атак, рассмотрим самый простой способ реализации атаки с применением невидимых unicode символов на примере invisible function
.
Пример с символом 0x200B
Рассмотрим пример, как отображается символ 0x200B
(zero width space) в Github:
Что мы видим? Мы видим две визуально одинаковых функции isAdmin
в строках 3 и 7, но в имени одной из них спрятан символ 0x200B
. Он же при просмотре в другом редакторе текста:
То есть вызов isAdmin
приводит к выполнению функции из строки 7, которая в исходном коде может быть спрятана где угодно.
Суть других атак заключается в смене направления чтения кода компилятором. Далее рассмотрим способы выявления подобных атак.
Правило SAST
Самым простым способом реализации было бы написание правила для используемого вами SAST.
Пример отображения сработки SAST при поиске невидимого символа:
Пример отображения символа 0x200B в интерфейсе SAST при просмотре кода:
Слабостью SAST, как видим, также является отсутствие отображения невидимых символов. Сходу AppSec-инженер может и не понять в чем подвох и пропустить данную сработку, особенно, если данные функции были бы разнесены друг от друга далеко по коду.
Аналогично, Azure DevOps также не отображает невидимые символы:
Пример отображения кода с другими невидимыми символами в интерфейсе SAST:
Странно, что такого правила нет в SAST из коробки, разработчики коммерческих SAST заявляют о том, что они также выявляют и вредоносный код, а не только уязвимости нулевого дня.
В рамках данной статьи, хотелось бы показать также самостоятельную реализацию поиска невидимых символов без применения SAST.
Методика поиска невидимых символов
Отталкиваясь от основ, описанных в указанной ранее статье, достаточно придерживаться следующей последовательности действий:
-
Первично просканировать все репозитории для выявления всех имеющихся невидимых символов:
Провести их разбор и при необходимости занести в базу исключений
Определить расширения файлов, которые необходимо пропускать при анализе, это могут быть некоторые бинарные форматы, например,
JPG
,MP4
и другие
Вычислить список файлов измененных в pull request при помощи команды
git diff
При автоматизированной проверке pull request валидационной сборкой пробежаться по содержимому измененных файлов регулярным выражением, на языке PowerShell это
[\u200B\u202A\u202B\u202C\u202D\u202E\u2066\u2067\u2068\u2069]
, пропуская ранее определенные списки исключений в файлах и файлы целиком по их расширениюВывести историю сработок в журнал валидационной сборки
Принять решение по результату выявления не менее чем одного или двух символов в одном файле
Трудность разбора заключается в том, что для того чтобы увидеть невидимые символы:
Подозрительный файл необходимо скачать
Открыть файл в редакторе
Включить отображение всех символов, включая unicode символы
Некоторые онлайн сервисы также позволяют увидеть информацию о подозрительном символе:
Валидационная сборка
Каждый pull request мы проверяем валидационной сборкой, запускающей различные классы решений, такие как: SAST, SCA, аудит IaaC, поиск секретов и другие, а также различные скрипты. Пример реализации поиска секретов смотри в статье Внедряем Gitleaks для анализа pull request на наличие секретов в Azure DevOps Server.
Примерный код скрипта для выявления BIDI символов на языке PowerShell может выглядеть так:
$excludetExtentions = "\.(jp(|e)g|pdf|png|mp(3|4)|xls(|x)|doc(|x)|ppt(|x)|apk|webp)$"
$excluded = @("*.jpg", "*.jpeg", "*.pdf", "*.png", "*.mp3", "*.mp4", "*.xls", "*.xlsx", "*.ppt", "*.pptx", "*.txt", "*.doc", "*.docx", "*.apk", "*.webp")
$evilPattern = "[\u202A\u202B\u202C\u202D\u202E\u2066\u2067\u2068\u2069]" # Искомый IoC, также могут быть интересны: \u200B\u200C\u200D, однако, например, \u200B участвует в красивых emoji, таких как доктор ??⚕️.
$regex = [regex]::new($evilPattern)
$changedFiles = git diff $targetBranchName $sourceBranchName --name-only
Write-Host "##[command]Файлов для анализа: $($changedFiles.Count)"
foreach ($file in $changedFiles)
{
if ($null -eq $prID) # Запуск валидационной сборки без PR вручную
{
# Не входит в данную статью, задание со звездочкой
}
else # Запуск из PR
{
$sourceBranchNameNoOrigin = $sourceBranchName -replace "origin/", ""
if (Test-Path $file -PathType leaf) # Проверка существования файла
{
if($file -notmatch $excludetExtentions) # Проверка имени файла по регулярке
{
$content = Get-Content $file -Encoding UTF8
if ($null -ne $content -and "" -ne $content)
{
$match = $regex.Match($content)
if($match.Captures.Count -gt 0) # Быстрая проверка файла
{
Write-Host "##[error]Найдено в файле ${file}:" -ForegroundColor Red
Select-String -Path $file -Pattern $evilPattern -AllMatches -Encoding UTF8 | ForEach-Object {
$hex = ("{0:X}" -f [int]($_.Matches[0].Value.ToCharArray()[0]))
Write-Host "- Hex-символ: ${hex}, Line: $($_.LineNumber), Start: $($_.Matches[0].Index), End: $($_.Matches[0].Length)"
$lineEndColumnI = $($_.Matches[0].Index) + 1
Write-Host "Файл URI: ${instance}${project}/_git/${repo}?path=/${file}&version=GB${sourceBranchNameNoOrigin}&line=$($_.LineNumber)&lineEnd=$($_.LineNumber)&lineStartColumn=$($_.Matches[0].Index)&lineEndColumn=${lineEndColumnI}&lineStyle=plain&_a=contents"
}
$isBIDIexists = $true
}
}
else{ Write-Host "##[warning]Файл ${file} пуст, можно уведомить разработчиков о пустом файле в коде"}
}
}
}
}
if($isBIDIexists -eq $true)
{
# Тут реализуем выбранный способ реагирования, например,
# зафейлить сборку и не дать завершить pull request:
Write-Host "##vso[task.complete result=SucceededWithIssues;]"
# Или, пометить шаг сборки ошибкой:
Write-Host "##vso[task.complete result=Failed;]"
}
Результат анализа кода
В процессе проверки pull request выявленные BIDI символы будут отражены в журнале валидационной сборки:
Реагирование на сработки
При выявлении BIDI символов мы можем по разному реагировать на данную сработку:
Заблокировать pull request
Пропустить pull request и отправить уведомление AppSec-инженерам
Выполнить оба варианта, все таки, это не 0-day, выявляемая SAST, а возможно, реальная попытка внесения вредоносного кода в исходный код
Одним из вариантов уведомления является отправка письма:
Предполагается, что BIDI символы - это достаточно редкий кейс. В Интернете я не встретил реальных кейсов с применением подобных атак. Поэтому уведомление может содержать полезную информацию, чтобы освежить память AppSec-инженера и напомнить, что же такое невидимый unicode символ и чем он опасен. Подсказка может быть в виде ссылки на внутренний wiki с описанием опасности невидимых символов с примерами реализации.
Легитимные невидимые символы
Могут быть ошибочные сработки на примере символа врач:
Итог
По моему мнению:
Данная проверка проста в реализации и полезна
Данная проверка не отнимает значительных ресурсов CPU
Данная проверка может создавать ошибочные сработки если не была проведена предварительная проверка всего кода
Если же в ваш код будут внедрены невидимые символы во вредоносных целях нужно иметь план реагирования, нужно понять, ее внес внутренний или внешний нарушитель от имени легального разработчика
Рецензия кода лишь с минимальным шансом позволит рецензентам кода выявить подобные атаки, тот же Github и Azure DevOps попросту не отображают опасные символы
Разработчикам SAST стоит присмотреться к необходимости отображения невидимых символов в интерфейсах их SAST
Буду рад комментариям с вашим опытом выявления подобных символов.