Приглашаем на вебинар про монетизацию сайтов в 16:00 мск
Уже сегодня: вебинар «Как находить точки роста в монетизации сайта»
Расскажем, как оценивать эффективность монетизации сайта в интерфейсе РСЯ и Метрики. В прямом эфире посмотрим сайты участников, обсудим ошибки и решения, которые влияют на доход.
Аудит joke-php/templator на коммите eecfa4b7dec3897f24dafa30cd450abada15fa0b
Короткое резюме
Коммит eecfa4b7dec3897f24dafa30cd450abada15fa0b реализует шаблонизатор с компиляцией AST в PHP и последующим include кэшированного файла. Ключевой риск не в классическом eval , а в цепочке compileFile() → сырой TextNode → include $cache->path : при неограниченном пути к шаблону это даёт чтение произвольных файлов, а при попадании в шаблон текста с <?php ... ?> — исполнение PHP-кода из “текстового” шаблона.
Топ-5 критичных/высоких проблем в этом коммите:
LFI/path traversal через includeFile() / compileFile() : путь к шаблону никак не ограничен корнем шаблонов и читается напрямую через file_get_contents() .
RCE через “обычный текст” шаблона: TextNodeHandler возвращает сырой текст без экранирования/нейтрализации PHP-тегов, а кэш затем исполняется через include .
Расхождение compile vs render на отсутствующих ключах: compiled-mode генерирует прямой доступ вида $context['user']['name'] , тогда как render-mode идёт через resolveValue(..., $default) ; это ломает предсказуемость и приводит к предупреждениям PHP на пустых данных.
Потеря внешних локальных переменных в вложенных foreach при компиляции: внутренний цикл затирает список localVars , из-за чего обращения к переменным внешнего цикла компилируются уже как $context[...] .
{% csrf %} создаёт ложное ощущение защиты: код выводит только сырой токен, а не hidden-input; кроме того, compiled/render paths ведут себя по-разному при недоступных сервисах.
Нештатный путь к шаблону открывает чтение произвольных файлов.
TemplateEngine::includeFile() принимает $file, создаёт FileRelatedCache, а при отсутствии кэша зовёт compileFile($file, $context); compileFile() проверяет лишь file_exists($path) и затем делает file_get_contents($path). Ни template-root, ни realpath()-проверки, ни allowlist расширений тут нет.
Минимальный сценарий:
php
// Плохо: путь приходит извне
$engine->includeFile($_GET['tpl'], []);
// /page?tpl=../../../../etc/passwd
Короткая рекомендация: ограничить все template paths жёстким каталогом шаблонов, проверять realpath($path) на префикс корня, запрещать wrappers и принимать только ожидаемые расширения. Если библиотека сознательно низкоуровневая, это ограничение всё равно лучше реализовать в самом движке, а не делегировать приложению.
Сырый TextNode делает PHP-теги в шаблоне исполняемыми.
TextNodeHandler::compile() возвращает $node->content без изменений, а TemplateEngine::includeFile() затем исполняет кэшированный файл через include. Это означает, что шаблонный текст <?php ... ?> не выводится как текст, а реально выполняется в PHP-процессе.
Короткая рекомендация: при компиляции text nodes генерировать безопасный вывод, например <?php echo '...'; ?> через var_export($node->content, true), либо заранее отклонять любые PHP opening tags во входных шаблонах. В текущем виде это главный архитектурный дефект безопасности.
Compiled-mode и render-mode расходятся на отсутствующих ключах.
compileVarAccess() строит прямой код вида $context['user']['name'], а PrintNodeHandler слепо вставляет его в htmlspecialchars((string){$code}, ...); аналогично IfHandler делает (bool)($this->compileVarAccess(...)), а EachHandler компилирует foreach ({$arrayAccess} as ...). В интерпретаторе же везде используется resolveValue(..., $default), который возвращает default при отсутствии пути. В PHP 8.x чтение неопределённого ключа массива — это E_WARNING, а доступ к offset у null тоже даёт предупреждение.
Минимальный сценарий:
php
// Шаблон
{{ user.name }}
// Что генерирует компилятор по сути:
<?= htmlspecialchars((string)$context['user']['name'], ENT_QUOTES, 'UTF-8'); ?>
// Что делает render-mode:
$value = resolveValue($context, 'user.name', '', 'PrintNode'); // => ''
Короткая рекомендация: вынести общий helper safe-access и использовать его и в compiled-mode, и в render-mode. Иначе библиотека остаётся непредсказуемой: один и тот же шаблон на тех же данных ведёт себя по-разному в двух режимах.
Вложенный foreach теряет переменные внешнего цикла при компиляции.
В EachHandler::compile() после разбора аргументов список локальных переменных жёстко заменяется на [$valueVar] и, опционально, $keyVar; старые localVars не наследуются. В EachHandler::render() наоборот строится $iterationContext = $context и туда добавляются локалы; то есть интерпретатор внешнюю область видимости сохраняет, а компилятор — нет.
Минимальный сценарий:
twig
{% foreach user in users %}
{% foreach role in roles %}
{{ user }}
{% /foreach %}
{% /foreach %}
Почему ломается:
php
// Внутри inner-foreach localVars уже только ['role'],// поэтому {{ user }} компилируется не в $user, а в $context['user'].
Короткая рекомендация: вместо $localVars = [$valueVar]; использовать накопление области видимости, например array_values(array_unique([...$localVars, $valueVar, $keyVar])). Это исправит и compile/render parity, и вложенные циклы.
{% csrf %} не вставляет отправляемое поле формы и даёт ложную семантику безопасности.
Docblock обещает “генерацию скрытого поля или вывод токена”, но реализация compile() и render() фактически возвращает только строку токена: в compiled path делается echo $tokenManager->getServerToken($request);, а в render path возвращается сам токен. Браузер не отправляет “просто текст” из <form> как form field, поэтому такой API легко использовать неправильно и остаться без реальной CSRF-защиты.
Минимальный сценарий:
html
<form method="post">
{% csrf %}
<button>Save</button>
</form>
Фактический эффект:
html
<form method="post">
3c6f4b...
<button>Save</button>
</form>
Короткая рекомендация: либо сделать директиву полноценным hidden-input (<input type="hidden" name="_token" ...>), либо честно переименовать её в csrf_token и заставить разработчика явно оборачивать значение. Дополнительно стоит выровнять compile/render null-handling: сейчас compiled path сервисы проверяет, render path — уже нет.
Лексер имеет очевидную деградацию по производительности на больших шаблонах.
findNext() на каждой итерации перебирает все token descriptors и для каждого вызывает strpos($template, $descriptor->open, $pos); сам автор оставил TODO про производительность именно на этом месте. При двух маркерах проблема не катастрофична, но на больших шаблонах с длинными текстовыми сегментами это лишняя повторная работа.
Короткая рекомендация: заменить “много strpos на каждом шаге” на линейный сканер или единый regex/token automaton. Это не security issue, но для engine-level кода уже заметная structural cost.
Инструментальная картина и покрытие тестами
В коммите есть PHPUnit, PHP-CS-Fixer и PHPStan: это видно по require-dev и composer scripts (test, stan, fixer, check). PHPStan настроен на level 8, PHPUnit — с coverage-репортами по ./src, а тестовое дерево покрывает Lexer, Parser, Handler, Compiler, Render, TemplateEngine и config/provider слои.
Но есть три явных пробела. Первый: Psalm не подключён вообще — ни зависимостью, ни конфигом. Второй: .phpstan.neon.dist исключает tests, хотя script stan формально запускает анализ и по tests; это снижает реальный эффект “analyse src tests”. Третий: в корне этого SHA не видно .github/workflows, то есть автоматический CI на самом коммите не просматривается.
По имеющемуся списку тестов видно, что unit-тесты по слоям есть, включая CsrfHandlerTest.php, IfHandlerTest.php, EachHandlerTest.php, PrintNodeHandlerTest.php и TemplateEngineTest.php. Однако по самим именам/видимым точкам входа не просматривается отдельного end-to-end покрытия на самые опасные сценарии этого аудита: literal <?php ... ?> внутри text node, path traversal через template path, parity compiled/render на отсутствующих ключах и parity nested-foreach по внешним локальным переменным. Это не доказательство полного отсутствия кейсов, но как минимум они не выделены в явные отдельные test targets.
Команды, которые разумно запускать для воспроизведения на локальном checkout этого SHA:
text
composer install
composer stan
composer fixer
composer test
# Psalm в этом коммите не настроен; для него сначала нужно добавить зависимость и конфиг.
Команды stan, fixer и test подтверждены в composer.json; отсутствие Psalm — тоже.
Вывод
Главная проблема этого коммита — не синтаксис шаблонов как таковой, а то, что движок компилирует текст шаблона в исполняемый PHP-файл и затем делает include, одновременно позволяя читать шаблон по произвольному пути. В сочетании это даёт сначала LFI/чтение любых локальных файлов, а затем и RCE при наличии PHP-кода в source text. Остальные важные проблемы — несогласованность compiled/render semantics, сбой областей видимости в вложенных циклах и misleading CSRF API.
Если исправлять по приоритету, то порядок такой: закрыть template path, запретить/нейтрализовать PHP в text nodes, унифицировать safe-access для compile/render, починить наследование localVars в foreach, переопределить семантику {% csrf %}, а затем уже добирать CI/Psalm/perf-оптимизации лексера. Это даст наибольший выигрыш и по безопасности, и по корректности, и по сопровождению.
Здравствуйте. такая же ерунда. Сайт более 10 лет В РСЯ тематика - учеба. небольшой но стабильный трафик. В феврале решил попробовать автоматические рекламные блоки и через пару месяцев сайт блокируют.
Заблокировано Сайт или приложение не соответствует Правилам участия в РСЯ по иным причинам
В поддержку обращался но там боты отвечают.
Есть ли возможность узнать больше информации о причинах и непонятна сама ситуация.
Cайт 10 лет соответствовал и вдруг одномоментно перестал соответствовать и его тут же блокируют без каких либо возможностей разобраться в ситуации.
бот принял решение, скорее всего перемодерация вручную тоже ничего не даст положительного эффекта, но попробовать написать в личку стоит https://searchengines.guru/ru/users/2068158 что делать: снять РСЯ и метрику, привести в порядок свой сайт, через пару месяцев вернуть метрику и ждать от полугода письмо от РСЯ, что площадка может участвовать в программе)
Mik Foxi#: если это статейник, то 50к цена будет много.
нет, это бесплатный сервис, 50к он принесет за 5 месяцев) с того времени как я его пытался кому-то впарить за 200-300к (после ухода адсенса), он уже принес ~700-900к на РСЯ, то есть окупился на 200-300% и продолжает генерировать прибыль)
а чтоб облигации приносили ~100к в год, надо купить бумаг на 1кк
Тариф 5 GHz 1 ядро 2 ГБ у бегета за 1500 или у firstvds тариф 5 GHz 2 ядра 2 ГБ за 1599?
Жду отзывы, а то бывает так, что ядер больше, но работает медленнее
Интересно, появились ли ушлые вебмастера с сайтами про бензин? Где бензин и т.п.
У кого-нибудь наблюдается такая картина - договор на ИП начал работать с 1 июля.
Машин почти нет, дроги пустые )))
пробок нет
Спасибо!!! -50% трафика за месяц. На фоне блокировки моб. Трафика, хостинга, сайт вылетел с топа.
Состояние на сегодня на github
Аудит joke-php/templator на коммите eecfa4b7dec3897f24dafa30cd450abada15fa0b
Короткое резюме
Коммит eecfa4b7dec3897f24dafa30cd450abada15fa0b реализует шаблонизатор с компиляцией AST в PHP и последующим include кэшированного файла. Ключевой риск не в классическом eval , а в цепочке compileFile() → сырой TextNode → include $cache->path : при неограниченном пути к шаблону это даёт чтение произвольных файлов, а при попадании в шаблон текста с <?php ... ?> — исполнение PHP-кода из “текстового” шаблона.
Топ-5 критичных/высоких проблем в этом коммите:
twig {% foreach user in users %} {% foreach role in roles %} {{ user }} {% /foreach %} {% /foreach %}html <form method="post"> {% csrf %} <button>Save</button> </form>text composer install composer stan composer fixer composer test # Psalm в этом коммите не настроен; для него сначала нужно добавить зависимость и конфиг.Здравствуйте. такая же ерунда. Сайт более 10 лет В РСЯ тематика - учеба. небольшой но стабильный трафик.
В феврале решил попробовать автоматические рекламные блоки и через пару месяцев сайт блокируют.
Заблокировано Сайт или приложение не соответствует Правилам участия в РСЯ по иным причинам
В поддержку обращался но там боты отвечают.
Есть ли возможность узнать больше информации о причинах и непонятна сама ситуация.
Cайт 10 лет соответствовал и вдруг одномоментно перестал соответствовать и его тут же блокируют без каких либо возможностей разобраться в ситуации.
что делать:
снять РСЯ и метрику,
привести в порядок свой сайт,
через пару месяцев вернуть метрику и ждать от полугода письмо от РСЯ, что площадка может участвовать в программе)
если это статейник, то 50к цена будет много.
с того времени как я его пытался кому-то впарить за 200-300к (после ухода адсенса), он уже принес ~700-900к на РСЯ, то есть окупился на 200-300% и продолжает генерировать прибыль)
а чтоб облигации приносили ~100к в год, надо купить бумаг на 1кк
это называется не сайт, а готовый бизнес.