| Translations Blog |

Transparent PNG

Обнаружение режима инкогнито в Chrome 76 с помощью временной атаки

Перевод статьи - Detecting incognito mode in Chrome 76 with a timing attack

Автор(ы) - Джесси Ли

Источник оригинальной статьи:

https://blog.jse.li/posts/chrome-76-incognito-filesystem-timing/

tl;dr: FileSystem API пишет значительно быстрее и менее шумно в режиме инкогнито, что позволяет веб-сайтам обнаруживать посетителей инкогнито, оценивая их скорость записи.

# Предыстория

Chrome 76 делает API файловой системы доступным в режиме инкогнито, не позволяя веб-сайтам обнаруживать пользователей инкогнито на основе наличия API.

В режиме инкогнито Chrome хранит данные, записанные в API, в памяти, а не сохраняет их на диске, как в обычном режиме. Когда мы решаем использовать память, мы идем на некоторые компромиссы: оперативная память-это временное хранилище, что делает ее привлекательной средой для инкогнито. Но побочные эффекты включают меньшее пространство и более высокую скорость, чем диск.

Недавно исследователь безопасности Викас Мишра обнаружил, что мы можем вывести состояние инкогнито на основе объема пространства, доступного API.

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

Метод

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

Код приведен в приложении. Просмотрите полный исходный код на GitHub или запустите его самостоятельно, если хотите воспроизвести мои результаты.

Результаты

Более 100 итераций бенчмарка каждая (что занимает несколько минут), мы можем видеть, что записи на диск значительно острее и занимают до 3-4 раз больше времени, чем записи в память.

Линейная диаграмма, показывающая нормальное и инкогнито время записи

 

Гистограмма таймингов рассказывает аналогичную историю. Скорость записи инкогнито плотно группируется слева, в то время как скорость записи в обычном режиме сильно варьируется. Вычисляя основные статистические показатели, такие как среднее и стандартное отклонение, можно с достаточной уверенностью определить, находится ли посетитель в инкогнито. По моим измерениям, среднее время бенчмаркинга в инкогнито составляет около 792 мс, по сравнению с 2281 мс в обычном режиме – в 2,8 раза больше. А стандартное отклонение составляет 67 мс в инкогнито, по сравнению с 1183 мс в нормальном режиме – 17,7 раза больше разброса.

Гистограмма нормального и инкогнито распределения времени записи

 

Полные данные доступны здесь.

Ограничения

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

Кроме того, эффективность атаки варьируется в зависимости от конфигурации оборудования. Компьютеры и смартфоны имеют разные скорости процессора, памяти и диска, что влияет на тайминги. Фоновые процессы, запущенные на устройстве, также могут создавать шум – копирование или загрузка файлов, воспроизведение видео на другой вкладке или запуск приложений-все это так или иначе искажает результаты.

Последнее ограничение заключается в том, что атака на самом деле не обнаруживает режим инкогнито – она обнаруживает резервное хранилище API файловой системы, которое оказывается достойным прокси для обнаружения режима инкогнито. Это может привести к ложным срабатываниям для ситуаций, в которых диск является памятью, например, живые USBS или профили Chrome, хранящиеся в tmpfs. Можно утверждать, что такие конфигурации являются попытками обойти отслеживание, что делает их эквивалентными инкогнито.

Суть в том, что этот метод медленнее и менее надежен, но его сложнее исправить, чем существующие методы, потому что он атакует лежащее в основе техническое решение хранить данные в памяти, а не на диске.

Смягчение последствий

Единственный способ предотвратить эту атаку-это использовать один и тот же носитель данных как в режиме инкогнито, так и в обычном режиме, чтобы API работал с одинаковой скоростью независимо от того, что происходит.

Разработчики Chrome предвидели это: в проектном документе от марта 2018года они определили риск атак на хронометраж и квоту и наметили альтернативную реализацию , которая предотвратила бы как мою атаку, так и атаку Мишры:

В качестве альтернативы мы могли бы хранить метаданные только в памяти и шифровать файлы на диске. Это позволит устранить риск использования сайтами синхронизации для разграничения хранения в памяти и на диске, а также устранить разницу в доступных типах квоты и файловой системы (временный и постоянный).

Однако такое решение имеет свои собственные компромиссы. Хотя он устойчив к нашим атакам, он оставляет после себя метаданные: даже если сами данные не могут быть расшифрованы, само их существование свидетельствует об использовании режима инкогнито и утечках, когда пользователь в последний раз использовал режим инкогнито и приблизительный размер данных, которые он записал на диск.

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

Режим Инкогнито Новая вкладка

Приложение

Мой PoC-код-это всего лишь несколько циклов записи случайно сгенерированных строк в API файловой системы. Полный исходный код здесь. PR, проблемы и советы приветствуются, если у вас есть опыт написания временных атак или бенчмаркинга диска/памяти.

const largeStrings = [
  // These strings are 5000 characters long. I generated them by running
  // base64 /dev/urandom -w 0 | head -c 5000
  'odE141SCRsNhfNBb95VhqRubp+fXTF1Dricc0G9wWrQcXRvu3uhGRh4t2TiUZF1BdSKLOrnG...',
  'pdfhLvvnkBGjbuR1/0WcCcM2li/cYOQ/wZGPAofjBXxo6PvhoEAWYtEMtTlbcLm+dPxwQFm8...',
  'Xfo5aKCHnIQc9zMtUWmGYiwzBJuDQLEVyg0t9ID2ZsCVMnVD7h8juo9Bmd+e2VdmofvGkFoa...',
  'jsYalJDnye4x5Vvl9w+F7aRrVx+WcJT5E7rzB9UNxb7iyY+mFAvsllN95ZDom50+GhhBuT+l...',
  'QcaZ/f91np7UkMvy4jrJks5Iogpgik0JZA0kCeXEPc2vdFYHKKIVT+nKmrva0qUee14LXh9Y...'
]
const SIZE = 6*1024*1024 // 6 MB
// Completely arbitrary numbers. Probably make them as high as you can tolerate:
const NUM_BENCHMARK_ITERATIONS = 200
const NUM_MEASUREMENTS = 100

const writeToFile = (fs, data) => {
  return new Promise((resolve) => {
    fs.root.getFile('data', { create: true }, (fileEntry) => {
      fileEntry.createWriter((fileWriter) => {
        fileWriter.onwriteend = resolve

        var blob = new Blob([data], { type: 'text/plain' });
        fileWriter.write(blob);
      })
    })
  })
}

const runBenchmark = async (fs) => {
  const time = new Date()
  for (let i = 0; i < NUM_BENCHMARK_ITERATIONS; i++) {
    for (let j = 0; j < largeStrings.length; j++) {
      await writeToFile(fs, largeStrings[j])
    }
  }
  return new Date() - time
}

const onInitFs = async (fs) => {
  const timings = []
  for (let i = 0; i < NUM_MEASUREMENTS; i++) {
    timings.push(await runBenchmark(fs))
  }

  console.log(timings)
}

window.webkitRequestFileSystem(window.TEMPORARY, SIZE, onInitFs)