<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Искусство. Код. ИИ?</title><generator>teletype.in</generator><description><![CDATA[Искусство. Код. ИИ?]]></description><image><url>https://img1.teletype.in/files/49/4c/494c7034-10b1-46f7-8253-36e6b8b1c09d.png</url><title>Искусство. Код. ИИ?</title><link>https://teletype.in/@art_code_ai</link></image><link>https://teletype.in/@art_code_ai?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=art_code_ai</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/art_code_ai?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/art_code_ai?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 11 Jun 2026 19:05:13 GMT</pubDate><lastBuildDate>Thu, 11 Jun 2026 19:05:13 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@art_code_ai/EVhImlQ4qsk</guid><link>https://teletype.in/@art_code_ai/EVhImlQ4qsk?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=art_code_ai</link><comments>https://teletype.in/@art_code_ai/EVhImlQ4qsk?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=art_code_ai#comments</comments><dc:creator>art_code_ai</dc:creator><title>Любую ли уязвимость можно устранить?</title><pubDate>Tue, 26 May 2026 07:26:19 GMT</pubDate><description><![CDATA[Спросили тут, какие мои доказательства, что любая уязвимость в коде поддается устранению. И, вроде бы очевидная хрень, но ведь «очевидная хрень» ≠ «доказательство». Да и уязвимости можно же по-разному трактовать. И вот тут — как бы уже не факт.]]></description><content:encoded><![CDATA[
  <p id="HOiL">Спросили тут, какие мои доказательства, что любая уязвимость приложения поддается устранению. И, вроде бы очевидная хрень, но ведь «очевидная хрень» ≠ «доказательство». Да и уязвимости можно же по-разному трактовать. Устранить ту же уязвимость бизнес-логики, не поломав при этом саму бизнес-логику, возможно далеко не всегда. Но, тогда, какие уязвимости поддаются устранению, а какие — нет? Хорошо бы в этом разобраться.</p>
  <p id="RNBE">А, значит, пора вспомнить теорию вычислений.</p>
  <p id="va09">Более формально: мы будем рассматривать утверждение, что «для любой программы, имеющей уязвимую функциональность, существует хотя бы одна такая, которая эквивалентна ей во всей функциональности, кроме уязвимой».</p>
  <h2 id="s8XT">1. Определения</h2>
  <p id="pToZ"><strong>Программа</strong> <code>P</code> — частично-рекурсивная функция <code>φ_P : Σ* ⇀ Σ*</code> над конечным алфавитом <code>Σ</code>. Расширенная модель (для анализа состояний и трасс): <code>P</code> порождает множество бесконечных последовательностей состояний <code>Executions(P) ⊆ Σ\_ω</code>.</p>
  <p id="z8Mo"><strong>Уязвимость (определение A, поточечное)</strong> есть разрешимый предикат <code>Bad ⊆ Σ* × Σ*</code>, где <code>Bad(x, y)</code> истинно, если выход <code>y</code> на входе <code>x</code> составляет «нежелательное поведение» в терминах модели угроз (аварийное завершение, выход за границы буфера, запуск вредоносного кода и т.д.). Предикат <code>Bad</code> фиксирован моделью угроз и не зависит от текста программы — он определяет, какие пары вход-выход считаются опасными, безотносительно того, какая именно программа этот выход произвела.</p>
  <p id="nqYO"><strong>Уязвимость (определение B, трассовое)</strong>: нарушение свойства безопасности (safety property) в смысле Алперна и Шнайдера: множество «плохих» конечных префиксов, таких что если префикс содержит «плохое событие», то любое продолжение также его содержит. Формально: свойство <code>Φ</code> является safety-свойством, если <code>∀σ ∉ Φ, ∃i &lt; |σ| : ∀τ, σ|\_i · τ ∉ Φ</code>.</p>
  <p id="wM5b"><strong>Уязвимость (определение C, семантическое)</strong>: нарушение произвольного нетривиального семантического свойства <code>S</code> программ (т.е. <code>S</code> зависит только от вычисляемой функции, а не от текста программы). По теореме Райса, такие свойства неразрешимы — не существует алгоритма, определяющего по тексту программы, обладает ли она свойством <code>S</code>. Однако неразрешимость распознавания свойства и невозможность конструирования программы с заданным свойством — это разные утверждения, и второе требует отдельного доказательства.</p>
  <p id="OttO"><strong>Устранение уязвимости</strong>. Говорим, что в программе <code>Q</code> устранена уязвимость программы <code>P</code>, если:</p>
  <ul id="ec2o">
    <li id="04XL"><code>∀x ∉ Vuln\_P : φ\_Q(x) ≃ φ\_P(x)</code> — идентичное поведение на безопасных входах (включая расхождение: если <code>φ_P(x)</code> не определено и <code>x ∉ Vuln_P</code>, то <code>φ_Q(x)</code> тоже не определено);</li>
    <li id="s2yU"><code>∀x ∈ Vuln_P : φ_Q(x) определено ∧ ¬Bad(x, φ_Q(x))</code> — на уязвимых входах <code>Q</code> останавливается с безопасным выходом;</li>
  </ul>
  <p id="hsYd">где <code>Vuln_P = {x | φ_P(x)</code> <code>определено ∧ Bad(x, φ_P(x))}</code>.</p>
  <p id="URlI">Символ ≃ означает совпадение частичных функций (по Клини): обе стороны одновременно определены и равны, или обе не определены.</p>
  <h2 id="fQCa">2. Теорема 1 (о поточечных уязвимостях)</h2>
  <blockquote id="OrQS">Поточечные уязвимости всегда устранимы</blockquote>
  <p id="1itN"><strong>Формулировка</strong>. Пусть уязвимость задана разрешимым предикатом <code>Bad(x, y</code>), и пусть <code>safe : Σ* → Σ*</code> — вычислимая тотальная функция «безопасного ответа», удовлетворяющая условию <code>∀x : ¬Bad(x, safe(x))</code>. Тогда существует вычислимая программа <code>Q</code> такая, что в <code>Q</code> устранена уязвимость <code>P</code>.</p>
  <p id="hUWE"><strong>Доказательство</strong>. Определим <code>Q</code> следующим алгоритмом:</p>
  <pre id="d02d" data-lang="python">Q(x):
  y ← P(x)
  if Bad(x, y):
    return safe(x)
  else:
    return y</pre>
  <p id="qPpb">Поскольку P вычислима (частично-рекурсивна) и <code>Bad</code> разрешим, композиция этих операций также вычислима. Покажем, что <code>Q</code> удовлетворяет определению устранения уязвимости:</p>
  <ul id="MN2i">
    <li id="apvS"><strong>Вычислимость</strong>: <code>Q</code> — частично-рекурсивная функция как композиция вычислимых операций.</li>
    <li id="7yBX"><strong>Совпадение с <code>P</code> на безопасных входах: </strong>если <code>x ∉ Vuln_P</code>, то либо <code>φ_P(x)</code> не определено (тогда <code>Q(x)</code> расходится на шаге <code>y ← P(x)</code>, т.е. <code>φ_Q(x)</code> тоже не определено), либо <code>φ_P(x) = y</code> и <code>¬Bad(x, y)</code> (тогда <code>Q</code> возвращает <code>y</code>). В обоих случаях <code>φ_Q(x) ≃ φ_P(x)</code>.</li>
    <li id="hA2U"><strong>Безопасность на уязвимых входах</strong>: если <code>x ∈ Vuln_P</code>, то <code>φ_P(x)</code> определено, значит <code>y ← P(x)</code> завершается. Далее <code>Bad(x, y)</code> истинно, и <code>Q</code> возвращает <code>safe(x)</code>. По условию на <code>safe</code>: <code>¬Bad(x, safe(x))</code>, значит <code>¬Bad(x, φ_Q(x))</code>. ∎</li>
  </ul>
  <p id="wPAf"><strong>Замечание о существовании safe</strong>. Условие <code>∀x : ¬Bad(x, safe(x))</code> не является ограничительным на практике: поскольку <code>Bad</code> разрешим и нетривиален (существуют безопасные выходы), для большинства моделей угроз такую <code>safe</code> можно предъявить явно — например, <code>safe(x) = ε</code> (пустая строка) или <code>safe(x) = сообщение об ошибке</code>, если модель угроз не считает аварийное завершение уязвимостью.</p>
  <h2 id="vJhu">3. Теорема 2 (о трассовых уязвимостях)</h2>
  <blockquote id="hSwp">Трассовые уязвимости устранимы через программу-монитор</blockquote>
  <p id="Puew"><strong>Формулировка</strong>. Пусть уязвимость определена как нарушение safety-свойства <code>Φ</code> (в смысле Алперна–Шнайдера) на трассах исполнения, и пусть автомат безопасности <code>A_Φ</code> для данного свойства имеет вычислимое отношение перехода. Тогда существует вычислимое преобразование программ <code>Rewrite : β → β</code> такое, что для любой программы <code>P</code>, <code>Rewrite(P)</code> удовлетворяет <code>Φ</code> и согласуется с <code>P</code> на всех трассах, где <code>P</code> не нарушает <code>Φ</code>.</p>
  <p id="FJgV"><strong>Автомат безопасности</strong>. Свойство безопасности <code>Φ</code> характеризуется автоматом безопасности — автоматом <code>A_Φ</code> со счётным (потенциально бесконечным) множеством состояний <code>Q</code>, алфавитом событий исполнения <code>Σ_ev</code> и вычислимой функцией перехода <code>δ : Q × Σ_ev → Q ∪ {reject}</code>. Автомат переходит в отвергающее состояние при наблюдении «плохого» события. Ключевое требование — вычислимость <code>δ</code> (по паре (состояние, событие) за конечное время получаем следующее состояние или reject), а не конечность <code>Q</code>.</p>
  <p id="TLr8">В отличие от классических DFA, автоматы безопасности (по Шнайдеру и Хамлену и др.) допускают счётно-бесконечное множество состояний. Это позволяет представлять safety-свойства, не являющиеся ω-регулярными (например, «количество открытых файлов никогда не превышает n» для произвольных n).</p>
  <p id="kcmC"><strong>Доказательство</strong>. Преобразование <code>Rewrite</code> работает следующим образом:</p>
  <ol id="mVZj">
    <li id="JHpN"><strong>Встраивание</strong>: в код <code>P</code> вставляется симуляция автомата <code>A_Φ</code> — переменная текущего состояния <code>q</code>, инициализированная начальным состоянием <code>q₀</code>.</li>
    <li id="zUNO"><strong>Перехват</strong>: перед каждым событием <code>e ∈ Σ_ev</code> (системный вызов, запись в память, и т.д.) вставляется проверка: вычисляется <code>δ(q, e)</code>.</li>
    <li id="BQuM"><strong>Ветвление</strong>: если <code>δ(q, e) = reject</code>, программа выполняет безопасную альтернативу (аварийное завершение или подмену действия). Если <code>δ(q, e) = q&#x27;</code>, то действие <code>e</code> исполняется и <code>q</code> обновляется на <code>q&#x27;</code>.</li>
  </ol>
  <p id="9Q0F">Это определение предполагает, что все события из <code>Σ_ev</code> наблюдаемы и перехватываемы на уровне инструментации. В модели, где программа — это последовательность наблюдаемых действий (что стандартно для анализа безопасности), данное предположение выполняется.</p>
  <p id="jiDc">Результирующая программа <code>Rewrite(P)</code>:</p>
  <ul id="xdTr">
    <li id="HnKa"><strong>Вычислима</strong>: <code>P</code> вычислима, <code>δ</code> вычислима, добавление конечного числа вычислимых операций перед каждым шагом сохраняет вычислимость.</li>
    <li id="qEAb"><strong>Удовлетворяет</strong> <code>Φ</code>: по построению, ни один «плохой» префикс не может возникнуть — автомат отвергает его до исполнения.</li>
    <li id="YRTQ"><strong>Согласуется с <code>P</code> на безопасных трассах</strong>: до первого момента, когда <code>P</code> нарушила бы <code>Φ</code>, программы <code>Rewrite(P)</code> и <code>P</code> производят одинаковую последовательность событий (в проекции на <code>Σ_ev</code>). Дополнительные вычисления (симуляция автомата) не влияют на наблюдаемое поведение, а лишь на внутреннее состояние. ∎</li>
  </ul>
  <p id="PrW4"><strong>Связь с EM-принудимостью</strong>. Шнайдер доказал: все EM-принудимые (enforceable via execution monitoring) свойства являются safety-свойствами. Однако обратное неверно: Базин и др. показали, что класс EM-принудимых свойств — собственное подмножество safety в обобщённой модели с различением контролируемых и наблюдаемых действий. Конструкция program rewriting покрывает более широкий класс, чем чистый EM, поскольку допускает не только прерывание исполнения, но и подмену действий. Хамлен, Моррисетт и Шнайдер показали, что RW-принудимые свойства включают некоторые <code>Π₂</code>-трудные свойства, выходящие за пределы coRE-класса EM.</p>
  <h2 id="ZCtO">4. Теорема 3 (о неустранимых уязвимостях)</h2>
  <blockquote id="2wmc">Существуют уязвимости, неустранимые вычислимыми средствами</blockquote>
  <p id="mNMX"><strong>Формулировка</strong>. Существует частично-рекурсивная функция <code>φ_P</code> и нетривиальное семантическое свойство <code>S</code>, такие что не существует вычислимой программы <code>Q</code>, одновременно удовлетворяющей <code>S</code> и согласующейся с <code>P</code> на всех входах из <code>dom(φ_P)</code>, где <code>P</code> не нарушает <code>S</code>.</p>
  <p id="ypG6"><strong>Доказательство</strong>. Пусть <code>S</code> = «тотальность»: программа <code>Q</code> удовлетворяет <code>S</code> тогда и только тогда, когда <code>φ_Q</code> — всюду определённая (тотальная) функция. Тотальность — нетривиальное семантическое свойство (существуют тотальные и нетотальные программы), неразрешимое по теореме Райса.</p>
  <p id="GHYf">Определим программу <code>P</code> следующим образом:</p>
  <pre id="0Th8" data-lang="python">
P(n):
  simulate TM_n on input n
  if TM_n(n) halts with output k then
    return k + 1
  else
    diverge</pre>
  <p id="0cyS">Здесь <code>{TM_i}</code> — стандартная гёделева нумерация всех машин Тьюринга. Программа <code>P</code> частично-рекурсивна: <code>φ_P(n) = φ_n(n) + 1</code>, если <code>φ_n(n) </code>определено; иначе <code>φ_P(n)</code> не определено.</p>
  <p id="MutK"><code>P</code> не удовлетворяет <code>S</code> (<code>P</code> нетотальна — расходится на входах <code>n</code>, где <code>TM_n(n)</code> не останавливается). «Уязвимость» здесь — нетотальность: программа обязана давать ответ на всех входах, но на некоторых расходится.</p>
  <p id="40Wm"><strong>Покажем, что не существует тотальной вычислимой <code>Q</code>, согласующейся с <code>P</code> на <code>dom(φ_P)</code>.</strong></p>
  <p id="X1u4">Предположим от противного, что такая <code>Q</code> существует. Тогда:</p>
  <p id="SGPW">1. <code>φ_Q</code> — тотальная вычислимая функция;<br />2. Для всех <code>n</code>, где <code>φ_P(n)</code> определено (т.е. где <code>TM_n(n)</code> останавливается): <code>φ_Q(n) = φ\_P(n) = φ\_n(n) + 1</code>.</p>
  <p id="TTL9">Поскольку <code>Q</code> — программа, она имеет номер в стандартной нумерации: <code>Q = TM_k</code> для некоторого <code>k</code>. Рассмотрим вход <code>n = k</code>:</p>
  <ul id="0mFL">
    <li id="xaGV"><code>TM_k</code> — тотальная функция (по предположению), значит <code>TM_k(k)</code> определено, значит <code>k ∈ dom(φ_P)</code>;</li>
    <li id="qUbA">Из согласованности: <code>φ_Q(k) = φ_P(k) = φ_k(k) + 1</code>;</li>
    <li id="8HCU">Но <code>φ_Q(k) = φ_k(k)</code> (поскольку <code>Q = TM_k</code>);</li>
    <li id="Rh0n">Получаем: <code>φ_k(k) = φ_k(k) + 1</code> — противоречие. ∎</li>
  </ul>
  <p id="oSri"><strong>Замечание о связи с теоремой Райса</strong>. Теорема Райса мотивирует постановку задачи: она говорит, что нельзя <code>алгоритмически распознать</code>, обладает ли программа нетривиальным семантическим свойством. Однако Теорема 3 — более сильное утверждение: она показывает, что для некоторых программ вообще <strong>не существует</strong> вычислимой замены с нужными свойствами, независимо от того, можем ли мы это распознать.</p>
  <p id="AJPb"><strong>Обобщение</strong>. Аргумент работает для любого нетривиального семантического свойства, которое требует расширения области определения частичной функции. В частности, если уязвимость формулируется как «программа должна быть определена и возвращать безопасный ответ на входах, где она ранее расходилась», то для некоторых программ такое расширение вычислимыми средствами невозможно.</p>
  <h2 id="Fayt">5. Заключение</h2>
  <p id="bgPZ">Доказано следующее:</p>
  <ul id="q3nU">
    <li id="ViAU"><strong>Теорема 1</strong>: уязвимости, заданные разрешимым предикатом на парах вход-выход, устранимы всегда — достаточно обернуть программу фильтром с безопасным ответом.</li>
    <li id="Ic3H"><strong>Теорема 2</strong>: нарушения safety-свойств с вычислимым автоматом безопасности устранимы через встраивание монитора (program rewriting). Это покрывает широкий класс практических уязвимостей: переполнения буфера, нарушения контроля доступа, нарушения протоколов и т.д.</li>
    <li id="Dzzb"><strong>Теорема 3:</strong> существуют семантические свойства, для которых устранение «уязвимости» (в смысле достижения этого свойства при сохранении согласованности) доказуемо невозможно.</li>
  </ul>
  <p id="jJWb">Разделительная линия проходит в том, устранима ли уязвимость <strong>ограничением</strong> поведения (отсечь плохое, оставив хорошее неизменным) или требует <strong>расширения</strong> области определения программы (заставить программу осмысленно отвечать там, где она раньше расходилась). Первое всегда возможно; второе — нет.</p>
  <p id="mOES">Иными словами: любая уязвимость, устранимая фильтрацией потоков данных, состояний, или мониторингом поведения программы, устранима вычислимыми средствами. В эту категорию входят практически все формализуемые уязвимости, чьи модели не имеют пересечений с моделью предметной области защищаемого приложения.</p>
  <p id="6fRS">Но, если устранение уязвимости требует, чтобы программа стала определена на входах, где она ранее расходилась, и при этом согласовывалась с исходной на всех остальных — это может быть доказуемо невозможно. В эту категорию входят уязвимости, чьи формальные модели имеют пересечения с моделью предметной области приложения. </p>
  <p id="Khku">Говоря ещё проще: существуют неустранимые узявимости, относящиеся к классу уязвимостей логики приложения.</p>
  <h2 id="SjEO">Литература</h2>
  <ol id="4QlP">
    <li id="qflm">B. Alpern, F. B. Schneider. <a href="https://cs.nyu.edu/~apanda/classes/sp26/papers/alpern85defining.pdf" target="_blank">Defining Liveness. Information Processing Letters</a>, 21(4), 1985.</li>
    <li id="GKVA">F. B. Schneider. <a href="https://dl.acm.org/doi/10.1145/353323.353382" target="_blank">Enforceable Security Policies</a>. ACM TISSEC, 3(1), 2000.</li>
    <li id="q9Yr">K. W. Hamlen, G. Morrisett, F. B. Schneider. <a href="https://dl.acm.org/doi/10.1145/1111596.1111601" target="_blank">Computability Classes for Enforcement Mechanisms</a>. ACM TOPLAS, 28(1), 2006.</li>
    <li id="Okxx">D. Basin, V. Jugé, F. Klaedtke, E. Zălinescu. <a href="https://dl.acm.org/doi/10.1145/2487222.2487225" target="_blank">Enforceable Security Policies Revisited</a>. ACM TISSEC, 16(1), 2013.</li>
    <li id="Mx2N">H. G. Rice. <a href="https://ww2.ams.org/journals/tran/1953-074-02/S0002-9947-1953-0053041-6/S0002-9947-1953-0053041-6.pdf" target="_blank">Classes of Recursively Enumerable Sets and Their Decision Problems</a>. Trans. AMS, 74, 1953.<br /></li>
  </ol>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@art_code_ai/NOtcUkxvC_7</guid><link>https://teletype.in/@art_code_ai/NOtcUkxvC_7?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=art_code_ai</link><comments>https://teletype.in/@art_code_ai/NOtcUkxvC_7?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=art_code_ai#comments</comments><dc:creator>art_code_ai</dc:creator><title>По следам простреленных ног в парсерах Go (и не только)</title><pubDate>Mon, 15 Dec 2025 14:19:33 GMT</pubDate><description><![CDATA[На днях Trail of Bits разродились в своём блоге статьей Unexpected security footguns in Go's parsers, посвященной не вполне очевидным проблемам, возникающим при парсинге структурированных форматов данных. Если одним предложением, то дизайн парсеров JSON, XML и YAML в Go содержит несколько подводных камней, хоть и упрощающих жизнь разработчика, но и открывающих возможности для атакующего. Статья действительно интересная, отбирать читателей у ребят из Trail of Bits желания нет, поэтому за подробностями — welcome по ссылке выше. Но, чтобы было понятно, о каких подводных камнях идёт речь, приведу несколько CVE, упомянутых в той статье:]]></description><content:encoded><![CDATA[
  <p id="tuG4">На днях Trail of Bits разродились в своём блоге статьей <a href="https://blog.trailofbits.com/2025/06/17/unexpected-security-footguns-in-gos-parsers/" target="_blank">Unexpected security footguns in Go&#x27;s parsers</a>, посвященной не вполне очевидным проблемам, возникающим при парсинге структурированных форматов данных. Если одним предложением, то дизайн парсеров JSON, XML и YAML в Go содержит несколько подводных камней, хоть и упрощающих жизнь разработчика, но и открывающих возможности для атакующего. Статья действительно интересная, отбирать читателей у ребят из Trail of Bits желания нет, поэтому за подробностями — welcome по ссылке выше. Но, чтобы было понятно, о каких подводных камнях идёт речь, приведу несколько CVE, упомянутых в той статье:</p>
  <p id="8Mpp">- <a href="https://nvd.nist.gov/vuln/detail/cve-2020-16250" target="_blank">CVE‑2020‑16250</a> описан обход аутентификации в HashiCorp Vault: злоумышленник заставил сервер разобрать JSON там, где ожидался XML. Поскольку XML-парсер Go чрезвычайно терпим к формату (он извлекает любые XML-подобные фрагменты), подмена значения заголовка <code>Accept</code> на <code>application/json</code> привела к получению доступа без должной проверки.</p>
  <p id="F3hs">- <a href="https://nvd.nist.gov/vuln/detail/CVE-2017-12635" target="_blank">CVE‑2017‑12635</a> в Apache CouchDB: различия JSON-парсеров Erlang и JavaScript позволяли создать пользователя с двумя полями <code>roles</code>, где второе содержало <code>&quot;_admin&quot;</code>. В итоге атакующий мог выдать себе админ-права (и далее ещё и провести RCE благодаря <a href="https://nvd.nist.gov/vuln/detail/CVE-2017-12636" target="_blank">CVE‑2017‑12636</a>).</p>
  <p id="9RPA">- <a href="https://nvd.nist.gov/vuln/detail/cve-2024-34155" target="_blank">CVE‑2024‑34155</a> показала, что парсер Go можно обрушить (DoS) чрезмерно вложенным вводом: отсутствие контроля глубины разбора вызывало переполнение стека и панику ядра.</p>
  <p id="ufT1">Но поговорить хотелось бы вот о чём... а почему собственно только Go? Другие языки — хуже, что-ли? Так, навскидку:</p>
  <p id="9NoN">- <strong>Python</strong>: библиотека PyYAML до версии 5.1 могла выполнить через десериализацию произвольный код при разборе YAML через функции <code>yaml.load</code> и <code>yaml.load_all</code> (<a href="https://nvd.nist.gov/vuln/detail/cve-2019-20477" target="_blank">CVE-2019-20477</a>)</p>
  <p id="63DV">- <strong>Java</strong>: парсер SnakeYAML до 1.31 не ограничивал глубину структуры, позволяя DoS через глубоко вложенный YAML (<a href="https://nvd.nist.gov/vuln/detail/cve-2022-25857" target="_blank">CVE‑2022‑25857</a>).</p>
  <p id="cAD2">- <strong>JavaScript</strong>: где он, там и Prototype Pollution. <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-38984" target="_blank">CVE‑2024‑38984</a> в модуле &#x60;json-override&#x60; через ключ &#x60;__proto__&#x60; позволяла «загрязнить» прототип объекта, приводя к выполнению кода.</p>
  <p id="XE3L">- <strong>C#:</strong> за прошлогодние DoS в Newtonsoft.Json (<a href="https://nvd.nist.gov/vuln/detail/CVE-2024-21907" target="_blank">CVE-2024-21907</a> и ах, если бы это была её единственная CVE...) и в стандартном System.Text.Json (<a href="https://nvd.nist.gov/vuln/detail/cve-2024-43485" target="_blank">CVE-2024-43485</a>) скромно промолчим.</p>
  <p id="uO9w">И это так, если по верхам брать. Но серьёзно, какие проблемы, аналогичные рассмотренным в оригинальной статье, присутствуют в других языках и их экосистемах?</p>
  <h3 id="Доступ-к-скрытым-или-игнорируемым-полям-при-(де)сериализации">Доступ к скрытым или игнорируемым полям при (де)сериализации</h3>
  <p id="7Wxz"><strong>Python + PyYAML:</strong> в Python «приватные атрибуты» (начинающиеся с <code>__</code>) не защищены от сериализации. Например, PyYAML при сериализации объекта включает даже «приватные» поля:</p>
  <pre id="BdCv" data-lang="python">import yaml

class User:
  def __init__(self, username, password):
    self.username = username
    self.__password = password

u = User(&quot;alice&quot;, &quot;s3cr3t&quot;)
print(yaml.dump(u))
</pre>
  <p id="0tzH">Получаем:</p>
  <pre id="YOUC" data-lang="python">!!python/object:__main__.User
_User__password: s3cr3t
username: alice
</pre>
  <p id="ismP"><strong>Java + Gson:</strong> в Gson по умолчанию сериализуются все поля объекта, даже приватные. Например:</p>
  <pre id="0wF6" data-lang="java">import com.google.gson.Gson;

class User {
  public String login = &quot;admin&quot;;
  private String password = &quot;secret&quot;;
}

User user = new User();
String json = new Gson().toJson(user);
System.out.println(json);
</pre>
  <p id="qGbd">Получаем:</p>
  <pre id="dC98" data-lang="java">{&quot;login&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;secret&quot;}
</pre>
  <h3 id="Игнорирование-неизвестных-полей">Игнорирование неизвестных полей</h3>
  <p id="Mxvu"><strong>Java + Gson:</strong> при десериализации JSON, Gson по умолчанию пропускает неизвестные ключи без ошибок. Например, если есть класс:</p>
  <p id="rMg1"><code>class User { String name; }</code></p>
  <p id="2ZKX">и мы пытаемся распарсить в него JSON <code>{&quot;name&quot;: &quot;Bob&quot;, &quot;age&quot;: 30}</code>, лишнее поле <code>age</code> будет тихо проигнорировано:</p>
  <pre id="KZwQ" data-lang="java">User u = new Gson().fromJson(jsonString, User.class);
System.out.println(u.name); 
</pre>
  <p id="iESR">Получаем: <code>&quot;Bob&quot;</code>, без каких-либо ошибок / исключений</p>
  <p id="mZOz"><strong>Python + Pydantic:</strong> библиотека Pydantic по умолчанию тоже игнорирует лишние поля во входных данных. Например:</p>
  <pre id="jWUx" data-lang="python">from pydantic import BaseModel

class User(BaseModel):
  name: str

data = User.parse_obj({&quot;name&quot;: &quot;Bob&quot;, &quot;admin&quot;: True})
print(data.dict())
</pre>
  <p id="8TS2">Так же, по-тихому, получаем: <code>{&#x27;name&#x27;: &#x27;Bob&#x27;}</code>.</p>
  <h3 id="Дублирующиеся-ключи-в-объектах">Дублирующиеся ключи в объектах</h3>
  <p id="fkgI"><em>Здесь прямая аналогия с <a href="https://portswigger.net/web-security/api-testing/server-side-parameter-pollution" target="_blank">HTTP Parameter Pollution</a>, и с примерно теми же последствиями при неправильно реализованной или отсутствующей семантической валидации.</em></p>
  <p id="Hug2"><strong>JavaScript + JSON.parse: </strong>стандартный парсер JSON в JavaScript при дублировании полей оставляет последнее встреченное значение. Например:</p>
  <pre id="hted" data-lang="javascript">let obj = JSON.parse(&#x27;{&quot;role&quot;: &quot;user&quot;, &quot;role&quot;: &quot;admin&quot;}&#x27;);
console.log(obj); 
</pre>
  <p id="fXMV">Получаем: <code>{ role: &quot;admin&quot; }</code></p>
  <p id="tS5t"><strong>Python + &#x60;json&#x60;/PyYAML</strong>: аналогично, Python-парсер JSON и PyYAML берут последнее значение при повторе ключа:</p>
  <pre id="Qw01" data-lang="python">import json, yaml
print(json.loads(&#x27;{&quot;x&quot;: 1, &quot;x&quot;: 2}&#x27;))  # {&#x27;x&#x27;: 2}
print(yaml.safe_load(&quot;x: 1\nx: 2\n&quot;))  # {&#x27;x&#x27;: 2}
</pre>
  <p id="9vez">Оба возвращают <code>{&#x27;x&#x27;: 2}</code> без каких-либо ошибок.</p>
  <h3 id="Нечувствительность-парсера-к-регистру-ключей">Нечувствительность парсера к регистру ключей</h3>
  <p id="AyKx"><em>А вот это — страх и ненависть уже синтаксической валидации.</em></p>
  <p id="gu6o"><strong>.NET + Newtonsoft.Json:</strong> по умолчанию десериализует свойства класса независимо от регистра имени. Например, C# класс:</p>
  <p id="wLw3"><code>class User { public string Name {get;set;} }</code></p>
  <p id="0lQ7">и JSON: <code>{&quot;name&quot;: &quot;Alice&quot;}</code> – будет корректно десериализован в <code>User.Name = &quot;Alice&quot;</code>, хотя регистр не совпадает. Причем тут умножаем на вариацию предыдущей проблемы: если в JSON будет и <code>&quot;Name&quot;: &quot;Eve&quot;</code>, и <code>&quot;name&quot;: &quot;Alice&quot;</code>, то первая из них может быть перезаписана второй, т.к. парсер считает их одним свойством, а вот валидатор — далеко не факт.</p>
  <h3 id="Вложенные-и-«полиглотные»-форматы-данных">Вложенные и «полиглотные» форматы данных</h3>
  <p id="nEJQ"><em>Самое понравившееся, поскольку частично является проблемой формата YAML, а не конкретной реализации его парсера 😊 Рассмотрим её подробнее.</em></p>
  <p id="TVHO"><strong>JSON внутри YAML:</strong> YAML является супермножеством JSON, поэтому любой JSON-документ валиден как YAML. Многие YAML-парсеры (PyYAML, js-yaml и др.) спокойно примут строку в формате JSON:</p>
  <pre id="hlQc" data-lang="python">import yaml
yaml_data = yaml.safe_load(&#x27;{&quot;flag&quot;: false, &quot;value&quot;: 42}&#x27;)
print(yaml_data)
</pre>
  <p id="m9Ii">Получаем:</p>
  <pre id="2mzk" data-lang="python">{&#x27;flag&#x27;: False, &#x27;value&#x27;: 42}
</pre>
  <p id="FDlQ">Это значит, что если сервис ожидает YAML, то злоумышленник может отправить JSON, и парсер обработает его без ошибок. Если при этом в YAML-обработчике включены какие-то специфичные для YAML особенности (например, типизация строк), они не сработают на JSON-вводе, что может быть использовано для обхода валидации.</p>
  <p id="W8Ch">Ребята из Trail of Bits показали, как составить вход, который одновременно распознаётся и JSON-, и YAML-, и XML-парсером, но дает разные значения. Идея в том, чтобы использовать вышеперечисленные особенности:</p>
  <p id="QHtI">– <strong>JSON-парсер</strong> проигнорирует незнакомые поля и допустит дубликаты, сопоставляя поля без учета регистра – поэтому из нескольких вариантов ключей он возьмет последний подходящий.</p>
  <p id="dBLr">– <strong>YAML-парсер</strong> (работающий в строгом регистре) проигнорирует ключ, не совпадающий точно с именем поля структуры, и возьмет другое значение.</p>
  <p id="ovwz">– <strong>XML-парсер</strong> может найти спрятанный XML-тег внутри строки JSON.</p>
  <p id="rIx7">В примере Go-полиглота из статьи, JSON содержал значение поля, начинающееся с <code>&lt;Action3&gt;...&lt;/Action3&gt;</code>. Стандартный парсер XML пропустил весь окружающий текст, и извлек содержимое этого XML-тега. В результате один и тот же байтовый поток интерпретировался как действие 2 для JSON, действие 1 для YAML и действие 3 для XML. Такой трюк применим и в других экосистемах: например, если одно приложение читает конфиг как JSON, а другое ошибочно как YAML, или когда данные передаются через несколько сервисов с разными форматами. Злоумышленник может создать двойной формат – например, завернуть JSON в комментарий XML, либо вставить валидный XML-фрагмент в значение JSON – чтобы обмануть систему. Реальный пример – упомянутая уязвимость в HashiCorp Vault, где запрос, содержащий одновременно JSON и XML, обходил аутентификацию из-за разных трактовок на разных этапах.</p>
  <h3 id="Чрезмерно-толерантный-парсинг-нестрогого-формата">Чрезмерно толерантный парсинг нестрогого формата</h3>
  <p id="hRVV"><em>Отличная иллюстрация того, что бывает, когда пытаешься отыскать корректные данные внутри некорректных вместо выброса исключения или сообщения об ошибке.</em></p>
  <p id="HUXu"><strong>Trailing/leading garbage</strong> (лишние данные вне структуры): Некоторые парсеры принимают «мусор» до или после основного документа. Старые версии PHP json\_decode ранее допускали трailing символы после JSON, а браузеры принимали JSON с завершающим &#x60;)&#x60; или &#x60;;&#x60; как часть JSONP. Известный трюк с CSRF через JSON эксплуатировал толерантность парсеров: отправлялся JSON с лишним символом <code>=</code> в конце и заголовком <code>Content-Type: application/x-www-form-urlencoded</code> – некоторые серверы игнорировали <code>Content-Type</code> и разбирали тело как JSON, не замечая лишний знак <code>=</code>, что обходило ограничения простых запросов SOP.</p>
  <p id="FbEn"><strong>Java + Jackson:</strong> Библиотека Jackson в стандартном режиме читает JSON-поток и может не ругаться, если после корректного JSON-объекта в потоке идут другие данные, пока не включён режим строгости.</p>
  <p id="4kp9">Почему это является проблемой? Избыточно «всеядный» парсер открывает возможности для атак в обход протокола. Лишние данные в конце могут быть использованы для скрытия второго запроса или полезной нагрузки (например, JSON плюс SQL-инъекция после него, где JSON-парсер съест своё, а SQL – своё, если данные проходят через разные слои). Лишние данные в начале (пробелы, BOM, комментарии – что не по стандарту JSON) тоже могут игнорироваться некоторыми реализациями JSON5/YAML, что приводит к расхождениям. Кроме того, сверхтолерантность к формату зачастую сопровождается ещё и ошибками реализации. Парсеры на неуправляемых языках вроде C/C++ с подобной гибкостью также могут содержать off-by-one баги или переполнения, приводящие к DoS или RCE при специально сформированном неверном JSON.</p>
  <p id="BfYN">Правило простое: если вход не соответствует ожидаемому (и строгому) формату – лучше немедленно отвергнуть его, чем пытаться корректировать или отбрасывать мусор.</p>
  <h3 id="А-разработчикам-делать-то-что?">А разработчикам делать-то что?</h3>
  <p id="VeCx">Авторы приводят в конце статьи дельный набор советов, немного его дополню:</p>
  <p id="zLd1"><strong>1. Включать строгую проверку (strict mode) там, где это возможно</strong></p>
  <p id="AxnQ">– <strong>JSON</strong>: явно запрещать все неизвестные поля (<code>DisallowUnknownFields</code> в Go, <code>FAIL_ON_UNKNOWN_PROPERTIES</code> в Jackson, <code>strict=True</code> в Pydantic).</p>
  <p id="m3sN">– <strong>YAML</strong>: включать режим строгого сопоставления (<code>KnownFields(true)</code> в Go, <code>safe_load</code> в PyYAML), запрещать автозагрузку кастомных объектов.</p>
  <p id="UxAC">– <strong>XML</strong>: валидировать по схеме (XSD/DTD).</p>
  <p id="szuu"><strong>2. Контролировать глубину и размер</strong></p>
  <p id="z0px">Устанавливать лимиты на глубину вложенности, размер документа и число одновременно выделяемых сущностей. Многие парсеры позволяют отключить entity expansion либо настроить таймауты/лимиты. Btw, в Go (вроде и в некоторых других языках) YAML-парсеры уже начали отдавать ошибку при чрезмерном разворачивании ссылок.</p>
  <p id="vmVW"><strong>3. Обеспечивать консистентность на всех интерфейсах</strong></p>
  <p id="FCIq">– Должен использоваться одинаковый парсер на разных компонентах системы (сервер, клиент, промежуточные сервисы), или хотя бы парсеры, строго реализующую одну и ту же спецификацию, чтобы не было рассогласования в их поведении.</p>
  <p id="zOQE">– Если сервис принимает JSON и YAML — необходимо четко определять ожидания, и не позволять смешивать форматы без необходимости и адекватной валидации. Да банально: если на входе ожидается YAML, проверять, не парсится ли он без ошибок JSON-парсером, и бить тревогу, если это оказалось так.</p>
  <p id="GacZ"><strong>4. Валидировать результаты парсинга</strong></p>
  <p id="qRlj">– Не доверять структуре после <code>parse()</code>: объект не является безопасным только потому, что был создан в результате парсинга входных данных. Необходимо подвергать ео семантической валидации в соответствии с правилами бизнес-логики, а также проверять, что отсутствуют лишние ключи, пересечения с <code>__proto__</code>, значение <code>null</code> и т.п. там, где это не ожидается.</p>
  <p id="QpvX">– В JavaScript проверять наличие свойств через <code>Object.prototype.hasOwnProperty.call(...)</code>, чтобы избежать Prototype Pollution.</p>
  <p id="Xy4x"><strong>5. Отказывать на любой мусор в данных</strong></p>
  <p id="yUXa">– Парсер должен разобрать весь вход: не следует допускать trailing/leading garbage, комментариев в JSON, незакрытых структур и т.д.</p>
  <p id="ApNR">– Любое несоответствие данных ожидаемому формату — выброшенное исключение или сообщение об ошибке.</p>
  <p id="1u5O"><strong>6. Избегать автосериализации приватных/скрытых полей</strong></p>
  <p id="WOQe">– В Java/Gson, C#/Newtonsoft, Python/PyYAML отключать сериализацию приватных полей по умолчанию.</p>
  <p id="CE0H">– Использовать явное указание разрешённых полей (<code>@Expose</code>, <code>JsonProperty</code>, <code>IncludeFields</code>, <code>Config.allowed_fields</code>) и не доверять <code>ObjectMapper</code>.</p>
  <p id="icaL"><strong>7. Использовать статический анализ и автоматические проверки</strong></p>
  <p id="s5y3">– Подключить Semgrep и другие SAST‑инструменты, правилом блокирующие <code>omitempty</code>, <code>-</code>‑теги, permissive parsing и ненужные entity в XML/YAML (пример такого правила приведен в оригинальной статье).</p>
  <p id="LG0V">– Проводить fuzz‑тестирование используемых парсеров (даже сторонних) с глубокими и полиглотными входами.</p>
  <p id="KTe7"><strong>8. Документировать форматы и внедрять схемы</strong></p>
  <p id="vGGp">– Для JSON использовать JSON Schema, для YAML – JSON Schema совместимую с YAML, для XML – XSD/DTD.</p>
  <p id="x1PS">– Встроить автоматическую актуализацию используемых схем в CI/CD пайплайн.</p>
  <h3 id="На-правах-эпилога...">На правах эпилога...</h3>
  <p id="N1EN">Кто-то сказал «статический анализ»?! 🤩 Но серьезно, что здесь может предложить SAST? Проверить корректность конфигурации парсера? Да, если в его правилах есть экспертиза по конкретной реализации конкретной версии. Но это дело наживное, ок. Убедиться, что контроль грамматики входных данных не позволяет пробросить XML внутри JSON под видом YAML? Ну, смогут (чисто теоретически), но уже далеко не все анализаторы. Зато поискать сомнительные теги в декларациях структуры, как предлагаемое авторами правило Semgrep – вообще легко.</p>
  <p id="BeN0">Но почему тогда перед этим правилом авторы делают такую жирную оговорку о false-positive&#x27;ах? Да потому, что ни один анализатор не выведет по коду, что именно хотел реализовать разработчик: какова ожидаемая схема разбираемого документа и каким требованиям (и какой) бизнес логики должны соответствовать его сущности. Здесь нужны четкие спецификации того «как должно быть», которые не любят писать разработчики и не умеют читать классические анализаторы. И тут сама собой напрашивается мысль: «но ведь LLM-то уже – ого-го! Наверняка же смогёт, догадается из общего кодового контекста, ведь там AGI уже, ну... почти?».</p>
  <p id="OIFS">И вот об этом – мы обязательно скоро поговорим...</p>

]]></content:encoded></item></channel></rss>