Добавить в корзинуПозвонить
Найти в Дзене
Цифровая Переплавка

Доверие с дырами: как домен стал фишинговым сайтом на GitHub Pages

13 октября 2025 года, 16:22 по среднеевропейскому времени. Разработчик Кристофер Беш открывает свой сайт dev.chris-besch.com — и видит вместо знакомой страницы фишинговый клон. Никто не взламывал его DNS-провайдера, не угонял аккаунт, не подбирал пароль. Всё, что сделал злоумышленник — нашёл «брошенный» CNAME-указатель, создал свой репозиторий на GitHub и нажал кнопку. Получил в придачу валидный SSL от Let's Encrypt — бесплатно, из коробки, через инфраструктуру самого GitHub. История Беша — не уникальный казус, а демонстрация того, что в GitHub Pages дыра по дизайну. И эта дыра уже годами кочует по баг-репортам, документации и постам бьюрократов в discussions GitHub. А чинить её не торопятся. Беш хостит dev.chris-besch.com на GitHub Pages. Схема стандартная: на стороне DNS-провайдера стоит CNAME, указывающий на christopher-besch.github.io. Сам поддомен «принадлежит» репозиторию на GitHub — там в настройках указан custom domain. И вот в чём прикол. Беш случайно удалил git-ветку, с котор
Оглавление

13 октября 2025 года, 16:22 по среднеевропейскому времени. Разработчик Кристофер Беш открывает свой сайт dev.chris-besch.com — и видит вместо знакомой страницы фишинговый клон. Никто не взламывал его DNS-провайдера, не угонял аккаунт, не подбирал пароль. Всё, что сделал злоумышленник — нашёл «брошенный» CNAME-указатель, создал свой репозиторий на GitHub и нажал кнопку. Получил в придачу валидный SSL от Let's Encrypt — бесплатно, из коробки, через инфраструктуру самого GitHub.

История Беша — не уникальный казус, а демонстрация того, что в GitHub Pages дыра по дизайну. И эта дыра уже годами кочует по баг-репортам, документации и постам бьюрократов в discussions GitHub. А чинить её не торопятся.

Что вообще произошло

Беш хостит dev.chris-besch.com на GitHub Pages. Схема стандартная: на стороне DNS-провайдера стоит CNAME, указывающий на christopher-besch.github.io. Сам поддомен «принадлежит» репозиторию на GitHub — там в настройках указан custom domain.

И вот в чём прикол. Беш случайно удалил git-ветку, с которой Pages собирал статический сайт. Потом он её пересоздал — буквально через пару минут. Казалось бы, ничего не должно было сломаться. Но GitHub Pages деплой после удаления ветки окончательно отключился, даже когда ветка вернулась. Привязка домена к репозиторию де-факто перестала действовать — и поддомен превратился в «бесхозный» (dangling).

А дальше — классика жанра, описанная во всех учебниках по subdomain takeover ещё с 2014 года. Атакующий:

🔍 Сканирует GitHub Pages-инфраструктуру в поисках CNAME-записей, указывающих на *.github.io, у которых нет привязанного активного сайта (можно автоматизировать — фингерпринт «There isn't a GitHub Pages site here» однозначен)

📝 Создаёт свой репозиторий, кладёт в него файл CNAME с текстом dev.chris-besch.com

⚡ В настройках репозитория указывает этот же домен как custom domain — GitHub принимает его без вопросов, потому что предыдущая привязка нарушена

🔒 Получает валидный сертификат Let's Encrypt через автоматическую DV-валидацию GitHub — браузеры показывают зелёный замочек, как родному

🎣 Заливает фишинговую страницу или редирект на C2 — и сайт работает на том самом домене, который пользователи видели последние годы

Никаких 0day, никакого сложного эксплойта. Просто GitHub доверяет CNAME-записи и не проверяет, кто этот CNAME поставил.

Почему так вообще работает

Тут самый интересный архитектурный момент. Беш ведь в DNS прописал CNAME именно на christopher-besch.github.io— то есть на своё конкретное имя пользователя. По идее, GitHub мог бы проверить: «Эй, этот домен указывает на репозиторий пользователя christopher-besch, а ты, левый чувак, не он. Подвинься». Но GitHub этого не делает. Совсем.

Дело в том, что для GitHub Pages любой *.github.io — это всего лишь точка входа в общий слой балансировщиков. Реальная маршрутизация на конкретный сайт идёт через содержимое CNAME-файла внутри репозитория и настройки в UI. Поэтому, как точно подметили в community discussions, «не имеет значения, что стоит в DNS вместо <user> в <user>.github.io» — атакующий может прописать в свой репо любой домен, и Pages будет его обслуживать, лишь бы DNS на эту инфраструктуру указывал хоть как-нибудь.

То есть с точки зрения GitHub, единственное «доказательство владения» доменом — это специальный TXT-запись:

dig _github-pages-challenge-USERNAME.example.com TXT

Эта запись создаётся, когда пользователь явно идёт в Profile Settings → Pages → Add a domain и проходит процедуру верификации. Она называется «verified domains», и пока она не пройдена — твой домен на GitHub Pages защищён ровно ничем, кроме самого факта существования активного репозитория-привязки.

Wildcard — это пулемёт у виска

А теперь самая ядрёная часть. У многих компаний и разработчиков в DNS прописан wildcard:

*.example.com CNAME user.github.io

Это удобно: создал новый поддомен — он сразу работает, ничего перенастраивать не нужно. Но это же и катастрофа для безопасности. Wildcard означает, что любой поддомен — admin.example.com, payments.example.com, vpn.example.com — резолвится в GitHub Pages.

Атакующему даже не нужно ждать, пока кто-то удалит ветку. Он просто:

🎯 Берёт ваш домен из wildcard-записи

🆕 Создаёт репозиторий с CNAME-файлом admin-panel.example.com

✅ Указывает этот поддомен как custom domain в настройках репо

🚀 Получает рабочий фишинговый admin-panel.example.com с валидным SSL за пару минут

Причём GitHub сам в документации Pages открыто говорит: «We strongly recommend that you do not use wildcard DNS records». Но «не рекомендуем» и «технически блокируем» — это два разных мира. На дворе 2026 год, а архитектурно ничего не изменилось.

Хуже того: даже верификация доменов wildcard не лечит полностью. Если вы верифицируете example.com, защита распространяется только на непосредственные поддомены первого уровня (a.example.com), но не на b.a.example.com. А wildcard как раз резолвит всё подряд на любую глубину.

SSL-сертификат, который убивает доверие

Отдельно стоит остановиться на сертификатах. Когда вы заходите на фишинговый клон, браузер показывает замочек — и обычный пользователь думает «всё ок, HTTPS же». Но за этим замочком на самом деле скрывается Domain Validation сертификат, который Let's Encrypt выдаёт автоматически после ACME-челленджа. Никакой проверки, кто реально владеет компанией или брендом, там нет — есть только проверка «контролирует ли запрашивающий DNS/HTTP на этом домене прямо сейчас».

А контроль у атакующего есть — потому что DNS указывает на GitHub, а GitHub отдаёт ему этот домен. Замкнутый круг, в котором валидация не валидирует ничего важного. Если бы существовали OV- или EV-сертификаты с полноценной проверкой организации, это могло бы смягчить проблему. Но в типичном интерфейсе браузера уровни валидации сертификатов вообще никак не различаются. Замочек и замочек.

Моё мнение: это не баг, это политика

Я понимаю, почему GitHub не хочет переключаться на mandatory-верификацию. Это сломает миллионы существующих сайтов, добавит трения при onboarding и превратит схему “закинул CNAME — всё заработало” в “закинул CNAME, добавил TXT-запись, подождал propagation и нажал Verify”. UX пострадает, количество обращений в поддержку вырастет, а growth-команда будет недовольна..

Но давайте честно. Когда платформа с 150 миллионами пользователей и сотнями тысяч кастомных доменов выбирает между «удобно» и «безопасно по умолчанию» в пользу удобства — это не нейтральное решение. Это перекладывание риска на пользователя, который часто даже не знает, что такое dangling CNAME.

И речь не только о личных блогах а-ля Кристофера Беша. На GitHub Pages сидят:

🏢 Документации проектов с миллионами визитов (docs.somecompany.com)

📦 Лендинги OSS-библиотек, через которые ходят разработчики и забирают команды установки

🎓 Сайты университетских кафедр и исследовательских групп

⚙️ Внутренние корпоративные dashboards, особенно в стартапах

Если такой домен угоняют, фишинг внезапно становится не «спам с подозрительной ссылкой», а полноценная supply-chain атака: атакующий может поменять команды установки в README, подсунуть вредоносный npm-пакет под видом обновления, перехватить OAuth-флоу — варианты ограничены только фантазией.

Кстати, отличная подборка таких сценариев лежит в репозитории EdOverflow can-i-take-over-xyz — он много лет ведёт каталог сервисов, уязвимых к подобным атакам, с фингерпринтами для автоматического сканирования. GitHub Pages там фигурирует с пометкой «легко эксплуатируется». Годами.

Что делать прямо сейчас

Если у вас есть домен на GitHub Pages — отложите этот текст, откройте две вкладки и проверьте.

🔐 Идите в GitHub → Settings → Pages и верифицируйте все ваши кастомные домены через TXT-запись. Это закрывает базовый сценарий takeover

🚫 Удалите wildcard DNS-записи, если они есть. Создавайте поддомены явно, по одному. Да, неудобно. Зато никто чужой не повесит свой vpn.yourcompany.com

🧹 Проведите аудит DNS: пройдитесь по всем CNAME-записям и проверьте, что каждая указывает на работающий ресурс. Брошенные записи — это бомба замедленного действия

📡 Настройте мониторинг через Google Search Console — она шлёт алерты, если ваш домен начинают индексировать с подозрительным контентом. Именно так Беш в итоге узнал бы о захвате, если бы не открыл сайт случайно

🛠️ В CI/CD добавьте takeover-сканер. Утилиты типа subjack, nuclei с шаблонами takeover/ или ручные скрипты на базе can-i-take-over-xyz спокойно интегрируются в pipeline

📝 Если используете несколько провайдеров статики (GitHub Pages + Netlify + Vercel) — заведите единый реестр того, какой поддомен на чём живёт. Без этого аудит превращается в археологию

Отдельный совет для bug bounty охотников: subdomain takeover остаётся одной из самых стабильно платных категорий на HackerOne и Bugcrowd. Программы крупных компаний регулярно платят $500–$3000 за подтверждённый takeover, иногда больше — потому что репутационный ущерб от фишинга на корпоративном поддомене считается высоким. Стек инструментов — subfinder или amass для перечисления, httpx для проверки активности, потом fingerprint-сканер. Часовая работа может вернуться четырёхзначной выплатой.

Что в итоге

История с dev.chris-besch.com — это не «случай невезения», а закономерный исход модели «доверяй CNAME-записи». GitHub продолжает считать verified domains опциональной фичей, а не дефолтом, и пока это так — каждый новый сайт на Pages с кастомным доменом по умолчанию находится в зоне риска.

Мой прогноз: в течение года-двух мы увидим хотя бы один громкий инцидент с угоном популярного OSS-проекта через GitHub Pages takeover — и вот тогда, скорее всего, GitHub зашевелится. Возможно, верификация станет обязательной для новых доменов. Возможно, появится автоматическая проверка «привязка нарушилась — заморозить домен на N дней без возможности захвата». Возможно, наконец-то заработает hard-block для wildcard-записей.

Но пока этого нет — мы все остаёмся в позиции, которую Беш сформулировал лучше меня: любой DNS-форвард на чужую инфраструктуру — это акт доверия. И стоит периодически проверять, заслуживает ли это доверие тот, кому вы его выдали.

Источники

📰 Оригинальный пост Кристофера Беша «How my GitHub Pages got Hacked» — https://chris-besch.com/articles/github_pages_hack/

📄 Техническое расследование на meertens.dev — https://meertens.dev/blog/github-enables-domain-abuse/

📖 Telegraph-версия материала — https://telegra.ph/Doverie-s-dyrami-kak-moj-domen-stal-fishingovym-sajtom-na-GitHub-Pages-05-19

🔧 Официальная документация GitHub по верификации доменов — https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/verifying-your-custom-domain-for-github-pages

⚠️ Управление кастомным доменом и предупреждения о wildcard — https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site

💬 Feature Request в GitHub Community про защиту от takeover — https://github.com/orgs/community/discussions/69169

🎯 Каталог EdOverflow «Can I take over XYZ?» — https://github.com/EdOverflow/can-i-take-over-xyz

🧪 Разбор реального takeover на filipmikina.com — https://filipmikina.com/blog/stolen-github-page