Что такое BIDI?

Двое специалистов в своей довольно старой публикации Trojan Source: Invisible Vulnerabilities описали одну из интересных атак, суть которой заключается в следующем: при просмотре исходного кода вы видите одно, но при компиляции в конечном приложении будет реализована совсем другая логика. Суть атаки проста: не все редакторы кода отображают unicode символы и рецензенты кода их попросту не увидят. Для реализации атаки необходимо использовать определенные символы в кодировке unicode, которые заставляют компилятор читать исходный код в другом направлении либо вызывать совсем другие функции.

Описание символов BIDI из публикации
Описание символов BIDI из публикации

Для подтверждения своей идеи авторы описывают различные способы атак с применением невидимых unicod символов. Для понимания сути атак, рассмотрим самый простой способ реализации атаки с применением невидимых unicode символов на примере invisible function.

Пример с символом 0x200B

Рассмотрим пример, как отображается символ 0x200B (zero width space) в Github:

Пример атаки invisible-function
Пример атаки invisible-function

Что мы видим? Мы видим две визуально одинаковых функции isAdmin в строках 3 и 7, но в имени одной из них спрятан символ 0x200B. Он же при просмотре в другом редакторе текста:

Пример применения 0x200B
Пример применения 0x200B

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

Суть других атак заключается в смене направления чтения кода компилятором. Далее рассмотрим способы выявления подобных атак.

Правило SAST

Самым простым способом реализации было бы написание правила для используемого вами SAST.

Пример отображения сработки SAST при поиске невидимого символа:

Сработка на символ 0x200B
Сработка на символ 0x200B

Пример отображения символа 0x200B в интерфейсе SAST при просмотре кода:

Сработка на символ 0x200B
Сработка на символ 0x200B

Слабостью SAST, как видим, также является отсутствие отображения невидимых символов. Сходу AppSec-инженер может и не понять в чем подвох и пропустить данную сработку, особенно, если данные функции были бы разнесены друг от друга далеко по коду.

Аналогично, Azure DevOps также не отображает невидимые символы:

Уязвимость Azure DevOps
Уязвимость Azure DevOps

Пример отображения кода с другими невидимыми символами в интерфейсе SAST:

Содержимое файла средствами SAST
Содержимое файла средствами SAST

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

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

Методика поиска невидимых символов

Отталкиваясь от основ, описанных в указанной ранее статье, достаточно придерживаться следующей последовательности действий:

  1. Первично просканировать все репозитории для выявления всех имеющихся невидимых символов:

    1. Провести их разбор и при необходимости занести в базу исключений

    2. Определить расширения файлов, которые необходимо пропускать при анализе, это могут быть некоторые бинарные форматы, например, JPG, MP4 и другие

  2. Вычислить список файлов измененных в pull request при помощи команды git diff

  3. При автоматизированной проверке pull request валидационной сборкой пробежаться по содержимому измененных файлов регулярным выражением, на языке PowerShell это [\u200B\u202A\u202B\u202C\u202D\u202E\u2066\u2067\u2068\u2069], пропуская ранее определенные списки исключений в файлах и файлы целиком по их расширению

  4. Вывести историю сработок в журнал валидационной сборки

  5. Принять решение по результату выявления не менее чем одного или двух символов в одном файле

Трудность разбора заключается в том, что для того чтобы увидеть невидимые символы:

  1. Подозрительный файл необходимо скачать

  2. Открыть файл в редакторе

  3. Включить отображение всех символов, включая unicode символы

Некоторые онлайн сервисы также позволяют увидеть информацию о подозрительном символе:

Информация о символе 0x2066 в одном из онлайн сервисов
Информация о символе 0x2066 в одном из онлайн сервисов

Валидационная сборка

Каждый 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 символов мы можем по разному реагировать на данную сработку:

  1. Заблокировать pull request

  2. Пропустить pull request и отправить уведомление AppSec-инженерам

  3. Выполнить оба варианта, все таки, это не 0-day, выявляемая SAST, а возможно, реальная попытка внесения вредоносного кода в исходный код

Одним из вариантов уведомления является отправка письма:

Пример письма с информацией о сработке правила
Пример письма с информацией о сработке правила

Предполагается, что BIDI символы - это достаточно редкий кейс. В Интернете я не встретил реальных кейсов с применением подобных атак. Поэтому уведомление может содержать полезную информацию, чтобы освежить память AppSec-инженера и напомнить, что же такое невидимый unicode символ и чем он опасен. Подсказка может быть в виде ссылки на внутренний wiki с описанием опасности невидимых символов с примерами реализации.

Легитимные невидимые символы

Могут быть ошибочные сработки на примере символа врач:

Вариации символа
Вариации символа
Вариации символа при просмотре в другом редакторе
Вариации символа при просмотре в другом редакторе

Итог

По моему мнению:

  • Данная проверка проста в реализации и полезна

  • Данная проверка не отнимает значительных ресурсов CPU

  • Данная проверка может создавать ошибочные сработки если не была проведена предварительная проверка всего кода

  • Если же в ваш код будут внедрены невидимые символы во вредоносных целях нужно иметь план реагирования, нужно понять, ее внес внутренний или внешний нарушитель от имени легального разработчика

  • Рецензия кода лишь с минимальным шансом позволит рецензентам кода выявить подобные атаки, тот же Github и Azure DevOps попросту не отображают опасные символы

  • Разработчикам SAST стоит присмотреться к необходимости отображения невидимых символов в интерфейсах их SAST

Буду рад комментариям с вашим опытом выявления подобных символов.

Комментарии (21)