<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Igor Plotnikov</title><link>https://iplotnikov.com/</link><description>Recent content on Igor Plotnikov</description><generator>Hugo -- gohugo.io</generator><language>ru-RU</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://iplotnikov.com/index.xml" rel="self" type="application/rss+xml"/><item><title>403 Forbidden длиной 310 байт: разбор взломанного WordPress</title><link>https://iplotnikov.com/p/wordpress-403-backdoor/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iplotnikov.com/p/wordpress-403-backdoor/</guid><description>&lt;img src="https://iplotnikov.com/p/wordpress-403-backdoor/4.jpg" alt="Featured image of post 403 Forbidden длиной 310 байт: разбор взломанного WordPress" /&gt;&lt;p&gt;Ко мне обратились с сайтом на WordPress, который отдавал 403 Forbidden на любой адрес, включая админку. Хостинг блокировку не ставил, разработчика у сайта нет. Из доступов только личный кабинет хостинга: файловый менеджер, phpMyAdmin и логи.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Всё, что видел посетитель сайта" class="gallery-image" data-flex-basis="875px" data-flex-grow="364" height="351" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/wordpress-403-backdoor/4.jpg" srcset="https://iplotnikov.com/p/wordpress-403-backdoor/4_hu_f84a12b2ddc7da6e.jpg 800w, https://iplotnikov.com/p/wordpress-403-backdoor/4.jpg 1280w" width="1280"&gt;&lt;/p&gt;&#10;&lt;p&gt;Оказалось что сайт взломали через уязвимость в ядре WordPress, поставили бэкдор в &lt;code&gt;mu-plugins&lt;/code&gt;, и этот бэкдор сам отдавал 403 всем, у кого нет пароля атакующего. То есть страница выше нарисована не веб-сервером, а самим взломщиком. Ниже как я до этого дошёл и что сделал.&lt;/p&gt;&#10;&lt;p&gt;Все пути, домены и префиксы таблиц в статье условные.&lt;/p&gt;&#10;&lt;h2 id="отсекаем-очевидное"&gt;Отсекаем очевидное&#10;&lt;/h2&gt;&lt;p&gt;403 на весь сайт это обычно права на файлы, правило в &lt;code&gt;.htaccess&lt;/code&gt; или блокировка хостингом. Права оказались нормальными (644 и 755), в &lt;code&gt;.htaccess&lt;/code&gt; только стандартные правила WordPress, хостинг блокировку не ставил.&lt;/p&gt;&#10;&lt;p&gt;Зацепка была в другом, 403 прилетал и на &lt;code&gt;robots.txt&lt;/code&gt;, и на картинки. Статику отдаёт веб-сервер напрямую, WordPress к ней отношения не имеет. Раз конфигурацию Apache я уже исключил, значит запрос перехватывает PHP до того, как дело доходит до отдачи файла.&lt;/p&gt;&#10;&lt;h2 id="профилирование-логов"&gt;Профилирование логов&#10;&lt;/h2&gt;&lt;p&gt;Логи в таких случаях читать подряд бессмысленно, сначала нужна структура, сколько записей, за какой период, с каких адресов, какие ошибки повторяются.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wc -l error_log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# кто стучался и сколько раз&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -oP &lt;span class="s1"&gt;&amp;#39;\[client \K[0-9.]+&amp;#39;&lt;/span&gt; error_log &lt;span class="p"&gt;|&lt;/span&gt; sort &lt;span class="p"&gt;|&lt;/span&gt; uniq -c &lt;span class="p"&gt;|&lt;/span&gt; sort -rn &lt;span class="p"&gt;|&lt;/span&gt; head -20&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# какие типы ошибок&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -oP &lt;span class="s1"&gt;&amp;#39;(?&amp;lt;=\] )(WordPress database error [^f]{0,60}|PHP [A-Za-z ]+error[^:]*)&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error_log &lt;span class="p"&gt;|&lt;/span&gt; cut -c1-90 &lt;span class="p"&gt;|&lt;/span&gt; sort &lt;span class="p"&gt;|&lt;/span&gt; uniq -c &lt;span class="p"&gt;|&lt;/span&gt; sort -rn &lt;span class="p"&gt;|&lt;/span&gt; head -20&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# распределение по часам&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -oP &lt;span class="s1"&gt;&amp;#39;\[Wed Aug 12 \d{2}&amp;#39;&lt;/span&gt; error_log &lt;span class="p"&gt;|&lt;/span&gt; sort &lt;span class="p"&gt;|&lt;/span&gt; uniq -c&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Больше 180 записей за сутки, из них 161 однотипная, три адреса, один сделал 155 запросов за десять минут ночью. Практически весь лог состоял из ошибок базы данных.&lt;/p&gt;&#10;&lt;h2 id="точка-входа"&gt;Точка входа&#10;&lt;/h2&gt;&lt;p&gt;У всех 161 записи один и тот же стек вызовов:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;rest_api_loaded&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;WP_REST_Server&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;serve_request&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;serve_batch_request_v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;serve_batch_request_v1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;вложенно&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;несколько&lt;/span&gt; &lt;span class="err"&gt;раз&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;WP_REST_Posts_Controller&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;get_items&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;WP_Query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;get_posts&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Бьют в REST API, в эндпоинт &lt;code&gt;/wp-json/batch/v1&lt;/code&gt;, внутрь которого вложены запросы к &lt;code&gt;/wp/v2/posts&lt;/code&gt;. Итоговый SQL выглядел так:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wp_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Инъекция сидит в параметре &lt;code&gt;author__not_in&lt;/code&gt;. Уязвимость оказалась в самом ядре WordPress. Это связка двух багов:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;SQL-инъекция в &lt;code&gt;author__not_in&lt;/code&gt;.&lt;/strong&gt; Если значение приходит строкой, а не массивом, проверка типа пропускается и значение уезжает прямо в SQL. Обычный REST-обработчик валидирует параметр &lt;code&gt;author_exclude&lt;/code&gt; как массив целых чисел, строка до &lt;code&gt;WP_Query&lt;/code&gt; не доходит.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Рассинхронизация в &lt;code&gt;/batch/v1&lt;/code&gt;.&lt;/strong&gt; Запрос валидируется по схеме одного эндпоинта, а выполняется обработчиком другого. Проверка проходит там, где параметр вроде как безобиден, а исполняется там, где он попадает в SQL.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Отсюда и вложенный &lt;code&gt;serve_batch_request_v1&lt;/code&gt; в стеке. Вместе два бага дают анонимному запросу выполнение кода.&lt;/p&gt;&#10;&lt;p&gt;Затронуты версии 6.9.0 до 6.9.4 и 7.0.0 до 7.0.1 (полная цепочка до RCE), исправлено в 7.0.2.&lt;/p&gt;&#10;&lt;h2 id="инъекция-не-отбита-она-работает"&gt;Инъекция не отбита, она работает&#10;&lt;/h2&gt;&lt;p&gt;&lt;img alt="Логи" class="gallery-image" data-flex-basis="708px" data-flex-grow="295" height="121" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/wordpress-403-backdoor/3.jpg" width="357"&gt;&lt;/p&gt;&#10;&lt;p&gt;Видно, что именно пытались подсунуть HTML с подписью &lt;code&gt;hacked by trenggalek6etar&lt;/code&gt;. Свою заглушку на главную он поставить не успел, но если загуглить ник trenggalek6etar, то гугл выдаст много сайтов, которые были взломаны и так и висят с надписью &lt;code&gt;hacked by trenggalek6etar&lt;/code&gt; на главной.&lt;/p&gt;&#10;&lt;p&gt;Другие попытки из лога: веб-шелл через &lt;code&gt;UNION SELECT UNHEX(...) INTO OUTFILE&lt;/code&gt; с перебором типовых путей вроде &lt;code&gt;/var/www/html/&lt;/code&gt; (мимо, реальный путь на шареде выглядит как &lt;code&gt;/home/c/userXXXX/site_ab12/public_html/&lt;/code&gt;, да и привилегия &lt;code&gt;FILE&lt;/code&gt; там обычно не выдана), запись в &lt;code&gt;customize_changeset&lt;/code&gt; для закрепления и запрос хеша из таблицы &lt;code&gt;wp_duplicator_packages&lt;/code&gt;, чтобы скачать архив с полной копией сайта и базы по прямой ссылке.&lt;/p&gt;&#10;&lt;p&gt;Сама по себе инъекция только читает данные, писать в базу через неё нельзя. Но она позволяет подсунуть WordPress поддельные объекты записей, и дальше он сохраняет их в базу уже сам. По той же цепочке он в какой-то момент повторно выполняет запрос на создание пользователя, который до этого отклонил, но уже с правами администратора. Так у атакующего и появляется свой админ.&lt;/p&gt;&#10;&lt;p&gt;Следы этого видно прямо в админке: девять пустых записей с заголовком &lt;code&gt;x&lt;/code&gt;, автором &lt;code&gt;admin&lt;/code&gt; и датой 01.01.2020.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Записи, оставшиеся после атаки" class="gallery-image" data-flex-basis="514px" data-flex-grow="214" height="806" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/wordpress-403-backdoor/2.png" srcset="https://iplotnikov.com/p/wordpress-403-backdoor/2_hu_d479f3b33b3a77c3.png 800w, https://iplotnikov.com/p/wordpress-403-backdoor/2_hu_734c4575e84443ca.png 1600w, https://iplotnikov.com/p/wordpress-403-backdoor/2.png 1729w" width="1729"&gt;&lt;/p&gt;&#10;&lt;h2 id="кто-и-когда-зашёл"&gt;Кто и когда зашёл&#10;&lt;/h2&gt;&lt;p&gt;Дальше я посмотрел таблицу пользователей.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Чужие администраторы в wp_users. Почта скрыта" class="gallery-image" data-flex-basis="167px" data-flex-grow="69" height="807" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/wordpress-403-backdoor/1.png" width="563"&gt;&lt;/p&gt;&#10;&lt;p&gt;Тридцать с лишним аккаунтов администратора, все со случайными суффиксами. Первый, &lt;code&gt;1ae9367587c9&lt;/code&gt;, зарегистрирован 22 июля, дальше по несколько штук в неделю вплоть до 11 августа. Настоящий владелец на сайте один, &lt;code&gt;admin&lt;/code&gt; от 2022 года.&lt;/p&gt;&#10;&lt;p&gt;Логины говорят сами за себя: &lt;code&gt;w2s_...&lt;/code&gt;, &lt;code&gt;wp2shell_...&lt;/code&gt;, &lt;code&gt;admins...&lt;/code&gt;. Тот же &lt;code&gt;wp2shell&lt;/code&gt; мне попался в access-логе как User-Agent.&lt;/p&gt;&#10;&lt;p&gt;Теперь понятно, почему сайт вообще попал под удар. Патч вышел 17 июля, первый чужой аккаунт появился 22 июля. Уязвимость опубликовали, PoC выложили на GitHub, начались массовые сканы и сайт нашли скриптом.&lt;/p&gt;&#10;&lt;p&gt;Причина, по которой обновление не приехало, нашлась в &lt;code&gt;wp-config.php&lt;/code&gt;: там стоял &lt;code&gt;DISABLE_WP_CRON&lt;/code&gt;, а системный крон вместо него никто не настроил. Планировщик отвечает в том числе за установку обновлений, и сайт просто перестал обновляться.&lt;/p&gt;&#10;&lt;h2 id="откуда-взялся-403"&gt;Откуда взялся 403&#10;&lt;/h2&gt;&lt;p&gt;В логе причины 403 не было. Записи &lt;code&gt;client denied by server configuration&lt;/code&gt; относились только к &lt;code&gt;xmlrpc.php&lt;/code&gt;, это старая защита самого владельца. Раз не Apache, остаётся PHP.&lt;/p&gt;&#10;&lt;p&gt;В &lt;code&gt;wp-content/mu-plugins/&lt;/code&gt; лежал файл с невнятным именем, датированный днём инцидента, must-use плагины WordPress подгружает при каждом запросе, раньше обычных плагинов, и отключить их через админку нельзя, они идут отдельной вкладкой без кнопки деактивации.&lt;/p&gt;&#10;&lt;p&gt;Внутри заголовок &lt;code&gt;LiteSpeed Cache - Purge Handler&lt;/code&gt; и строка &lt;code&gt;eval(gzinflate(base64_decode('...')))&lt;/code&gt;. Распаковал ту же конструкцию вручную, заменив &lt;code&gt;eval&lt;/code&gt; на &lt;code&gt;file_put_contents&lt;/code&gt;, и получил 875 строк PHP. В начале лежало это:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$_pw_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xxxxxxxxxxxx&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$inputPw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$inputPw&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$_pw_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;meta charset=&amp;#34;utf-8&amp;#34;&amp;gt;&amp;lt;title&amp;gt;403&amp;lt;/title&amp;gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;style&amp;gt;body{background:#0a0a0f;color:#ef5350;...}&amp;lt;/style&amp;gt;&amp;lt;/head&amp;gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;403 Forbidden&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;А в конце файла безусловный вызов &lt;code&gt;Purge::run();&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Нет параметра &lt;code&gt;?password=&lt;/code&gt;, значит 403 и &lt;code&gt;exit&lt;/code&gt;, запрос до WordPress не доходит вообще. Отсюда и 403 на статику. Атакующий со своим паролем при этом работал с сайтом спокойно.&lt;/p&gt;&#10;&lt;p&gt;Это та самая страница, с которой начинается статья, длина заглушки ровно 310 байт, и во всех 403-х в access-логе в поле размера ответа стоит 310.&lt;/p&gt;&#10;&lt;h2 id="устранение"&gt;Устранение&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Снять сайт с публики&lt;/strong&gt; заглушкой на уровне хостинга, чтобы не работать по живому.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Найти всех администраторов.&lt;/strong&gt; Тут есть грабля. ключ &lt;code&gt;wp_capabilities&lt;/code&gt; содержит префикс таблиц конкретного сайта, и на нестандартном префиксе запрос по точному имени вернёт пустой результат.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_registered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta_value&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wp_users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wp_usermeta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%capabilities&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta_value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%administrator%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Удалять точечно&lt;/strong&gt; &lt;code&gt;DELETE FROM wp_users WHERE ID != 1&lt;/code&gt; снесёт покупателей, редакторов и второго настоящего админа вместе с привязкой заказов. Сначала разбивка по ролям, потом удаление по конкретным ID:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wp_usermeta&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%capabilities&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Вычистить файлы и контент:&lt;/strong&gt; бэкдор из &lt;code&gt;mu-plugins&lt;/code&gt;, записи с заголовком &lt;code&gt;x&lt;/code&gt;, проверка &lt;code&gt;wp-content/uploads&lt;/code&gt; на PHP (его там быть не должно), сверка контрольных сумм ядра, осмотр &lt;code&gt;wp-config.php&lt;/code&gt; и &lt;code&gt;.htaccess&lt;/code&gt; на посторонние вставки.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Обновить ядро до возврата сайта в онлайн.&lt;/strong&gt; Иначе всё вернётся тем же путём, проверял явно через &lt;code&gt;wp core version&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Сменить всё:&lt;/strong&gt; пароли администраторов, пароль базы, доступы FTP и панели, соли в &lt;code&gt;wp-config.php&lt;/code&gt; (это разлогинит всех, включая атакующего).&lt;/p&gt;&#10;&lt;h2 id="что-настроили-чтобы-не-повторилось"&gt;Что настроили, чтобы не повторилось&#10;&lt;/h2&gt;&lt;p&gt;Список начинается не с паролей, потому что вход был анонимный, через дыру в ядре.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Автообновления.&lt;/strong&gt; В &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;WP_AUTO_UPDATE_CORE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;И системный крон на &lt;code&gt;wp-cron.php&lt;/code&gt; в панели хостинга.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Запрет PHP в загрузках.&lt;/strong&gt; Файл &lt;code&gt;wp-content/uploads/.htaccess&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-apache" data-lang="apache"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;FilesMatch&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;\.(?i:php|phtml|php[0-9]|phar|shtml)$&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Require&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; denied&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/FilesMatch&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Редактор кода из админки убрать:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DISALLOW_FILE_EDIT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Даже с угнанной админкой нельзя будет вписать код в тему через браузер.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Закрыть batch-эндпоинт&lt;/strong&gt; как временную меру, пока не обновились. Отдельным файлом в &lt;code&gt;mu-plugins&lt;/code&gt;, чтобы не отключили из админки:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;span class="lnt"&gt;7&#10;&lt;/span&gt;&lt;span class="lnt"&gt;8&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rest_endpoints&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$endpoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$endpoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/batch/v1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;unset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$endpoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$endpoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Удалить неиспользуемые плагины и темы.&lt;/strong&gt; Неактивный плагин лежит на диске и уязвим ровно так же, многие атаки бьют прямо в его файл, минуя WordPress.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Проверить соседние сайты&lt;/strong&gt; на том же аккаунте хостинга по тому же чеклисту, начиная с версии ядра.&lt;/p&gt;&#10;&lt;h2 id="итог"&gt;Итог&#10;&lt;/h2&gt;&lt;p&gt;Сайт вернули в работу тем же вечером. На разбор ушло несколько часов, из них большая часть на логи.&lt;/p&gt;&#10;&lt;h2 id="ссылки"&gt;Ссылки&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2026-63030" target="_blank" rel="noopener"&#10; &gt;CVE-2026-63030&lt;/a&gt;, путаница маршрутов в batch-эндпоинте&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2026-60137" target="_blank" rel="noopener"&#10; &gt;CVE-2026-60137&lt;/a&gt;, SQL-инъекция в &lt;code&gt;author__not_in&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/" target="_blank" rel="noopener"&#10; &gt;Разбор от нашедших цепочку&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://patchstack.com/articles/ninety-minutes-watching-attackers-weaponize-the-wordpress-core-rce/" target="_blank" rel="noopener"&#10; &gt;Хронология эксплуатации после выхода патча, Patchstack&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Как устроен этот сайт</title><link>https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/</guid><description>&lt;img src="https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/cover.jpg" alt="Featured image of post Как устроен этот сайт" /&gt;&lt;p&gt;Небольшая статья о том как устроен этот блог под капотом. Есть приватный git-репозиторий с текстовыми файлами и бакет в Яндекс облаке. Сайт статический, то есть каждая страница лежит в хранилище готовым HTML-файлом, и когда читатель её открывает, хранилище просто отдаёт то, что там уже есть.&lt;/p&gt;&#10;&lt;p&gt;Собирает страницы &lt;a class="link" href="https://github.com/gohugoio/hugo" target="_blank" rel="noopener"&#10; &gt;Hugo&lt;/a&gt;, генератор статических сайтов на Go.&lt;/p&gt;&#10;&lt;h2 id="что-происходит-по-шагам"&gt;Что происходит по шагам&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Шаг 1. Я пишу статью.&lt;/strong&gt; Создаю папку в &lt;code&gt;content/post&lt;/code&gt;, кладу туда &lt;code&gt;index.md&lt;/code&gt; и картинки.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Шаг 2. Смотрю, что получилось.&lt;/strong&gt; Команда &lt;code&gt;hugo server -D&lt;/code&gt; поднимает копию сайта на localhost с горячей перезагрузкой.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Шаг 3. Делаю коммит и пуш в &lt;code&gt;main&lt;/code&gt;.&lt;/strong&gt; На этом моё ручное участие заканчивается.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Шаг 4. GitHub Actions поднимает чистую виртуалку&lt;/strong&gt; и выкачивает на неё репозиторий вместе с темой.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Шаг 5. Ставится Hugo и собирается сайт.&lt;/strong&gt; Команда &lt;code&gt;hugo --minify&lt;/code&gt; превращает markdown в HTML и по дороге сжимает разметку, результат складывается в папку &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Шаг 6. Результат синхронизируется с бакетом.&lt;/strong&gt; Заливаются только изменившиеся файлы, а флаг &lt;code&gt;--delete&lt;/code&gt; подчищает в хранилище то, чего больше нет в сборке, иначе удалённая статья продолжала бы открываться по старому адресу.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Шаг 7. Статья размещена на сайте.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="пайплайн"&gt;Пайплайн&#10;&lt;/h2&gt;&lt;p&gt;Всё описано в одном файле &lt;code&gt;.github/workflows/deploy.yaml&lt;/code&gt;, триггером служит любой пуш в ветку &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;submodules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;recursive&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peaceiris/actions-hugo@v3&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hugo-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latest&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo --minify&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ru-central1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws s3 sync ./public s3://iplotnikov.com \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --endpoint-url=https://storage.yandexcloud.net --delete&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;img alt="Прогон пайплайна в GitHub Actions" class="gallery-image" data-flex-basis="302px" data-flex-grow="126" height="319" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/pipe.png" width="402"&gt;&lt;/p&gt;&#10;&lt;p&gt;Весь прогон занимает около десяти секунд, ключи доступа лежат в секретах GitHub и принадлежат в Яндекс облаке сервисному аккаунту с ролью &lt;code&gt;storage.editor&lt;/code&gt;, который умеет только читать и писать объекты в этом хранилище и больше ничего, так что при утечке ключа ущерб ограничен содержимым одного бакета.&lt;/p&gt;&#10;&lt;h2 id="домен-и-сертификат"&gt;Домен и сертификат&#10;&lt;/h2&gt;&lt;p&gt;Доменом управляет Yandex Cloud DNS, то есть зона &lt;code&gt;iplotnikov.com&lt;/code&gt; целиком живёт в облаке и обслуживается серверами &lt;code&gt;ns1.yandexcloud.net&lt;/code&gt; и &lt;code&gt;ns2.yandexcloud.net&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;HTTPS обеспечивает сертификат от Let&amp;rsquo;s Encrypt, выпущенный через Yandex Certificate Manager и привязанный к бакету. Сертификаты Let&amp;rsquo;s Encrypt живут 90 дней, Certificate Manager запрашивает обновление за 30 дней до истечения и раскатывает новую версию на все привязанные ресурсы.&lt;/p&gt;&#10;&lt;h2 id="из-чего-складывается-счёт"&gt;Из чего складывается счёт&#10;&lt;/h2&gt;&lt;p&gt;В Object Storage никакой абонентской платы за сам факт существования бакета нет, счётчик крутится только тогда, когда данные лежат или их кто-то читает. Хранение считается пропорционально времени, вплоть до часов и мегабайт, операции делятся на типы по методам S3 API (загрузка объекта PUT, скачивание GET, получение метаданных HEAD), и оплачивается фактическое их количество, а удаление объектов не тарифицируется вообще. Входящий трафик тоже бесплатен, так что деплой ничего не стоит сам по себе.&lt;/p&gt;&#10;&lt;p&gt;Поверх этого работает бесплатный лимит, не тарифицируются первый гигабайт хранения, первые десять тысяч операций записи и сто тысяч операций чтения, а также первые сто гигабайт исходящего трафика в месяц. Блог с несколькими десятками мегабайт статики и парой тысяч посещений в такие рамки укладывается целиком, поэтому в детализации напротив всех строк Object Storage стоит ноль: и хранение, и одиннадцать тысяч операций GET, и сто двадцать операций PUT от последних деплоев, и весь исходящий трафик.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Детализация потребления за месяц" class="gallery-image" data-flex-basis="463px" data-flex-grow="193" height="658" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/detail.png" srcset="https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/detail_hu_6e619aaeaafd855e.png 800w, https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/detail.png 1270w" width="1270"&gt;&lt;/p&gt;&#10;&lt;p&gt;И получается что хостинг сайта раскатанный таким образом не стоит ничего. Все 44 рубля в счёте это Cloud DNS, где 41,95 рубля приходится на саму DNS-зону, которая тарифицируется по часам её существования, и ещё 82 копейки на публичные запросы к ней. Сертификат от Let&amp;rsquo;s Encrypt в Certificate Manager отдельных денег тоже не стоит.&lt;/p&gt;&#10;&lt;p&gt;Интересно посмотреть, что будет при росте. Ставки в стандартном хранилище такие: примерно 2,4 рубля за гигабайт хранения в месяц, 46 копеек за десять тысяч операций GET и около 1,7 рубля за гигабайт исходящего трафика сверх бесплатных ста. Если читателей станет в десять раз больше, операции вылезут за лимит на одиннадцать тысяч штук, и счёт вырастет на пятьдесят копеек. При стократном росте, то есть больше миллиона запросов в месяц, добавится сорок шесть рублей.&lt;/p&gt;&#10;&lt;h2 id="сколько-стоила-бы-виртуалка"&gt;Сколько стоила бы виртуалка&#10;&lt;/h2&gt;&lt;p&gt;Для сравнения я собрал в калькуляторе Yandex Cloud самую минимальную конфигурацию, два ядра с гарантированной долей 5%, 0.5 гб памяти, 5 гб диска и публичный IP-адрес. Вышло 612 рублей в месяц, и это сверх тех же 42 рублей за DNS, которые никуда не денутся. Почти 200 рублей из этой суммы приходится не на вычислительные ресурсы, а на публичный IP-адрес, ещё 17 на диск. Если довести машину до вменяемого состояния, скажем до 20% доли ядра и двух гигабайт памяти, счёт уходит в район полутора тысяч.&lt;/p&gt;&#10;&lt;p&gt;Разница тут не в том, что виртуалки у Яндекса такие дорогие, а в модели оплаты, в случае с Compute Cloud придётся платить за зарезервированные ресурсы. Ядра, память, диск и адрес будут капать в счёт круглые сутки независимо от того, зашёл на сайт хоть кто-нибудь или нет. Больше того, диск и IP тарифицируются даже когда машина выключена, так что 200 рублей в месяц набегают на полностью простаивающем сервере. Блог, который читают полтора человека в день, простаивает практически всё время, но платить приходится за все двадцать четыре часа.&lt;/p&gt;&#10;&lt;p&gt;Пришёл читатель и скачал 100 файлов, значит в счёт попали 100 операций GET и несколько сотен килобайт трафика. Не пришёл никто, значит и потребления нет. А поскольку блог такого размера целиком помещается в бесплатный лимит, фактический счёт за хостинг равен нулю, тогда как виртуалка выставляла бы свои шестьсот рублей каждый месяц независимо ни от чего.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Расчёт минимальной виртуальной машины в калькуляторе Yandex Cloud" class="gallery-image" data-flex-basis="350px" data-flex-grow="145" height="733" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/calc.png" srcset="https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/calc_hu_389a3f9b6607e041.png 800w, https://iplotnikov.com/p/%D0%BA%D0%B0%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD-%D1%8D%D1%82%D0%BE%D1%82-%D1%81%D0%B0%D0%B9%D1%82/calc.png 1069w" width="1069"&gt;&lt;/p&gt;&#10;&lt;p&gt;Плюс к деньгам сюда добавляется всё, за что не приходится платить временем: не надо накатывать обновления безопасности, следить за сертификатами, чинить упавший nginx и держать в голове, что на машине вообще-то крутится веб-сервер, который по всем законам подлости обязательно рано или поздно отвалится.&lt;/p&gt;&#10;</description></item></channel></rss>