<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Заметки Андрея Гейна: posts tagged CTF</title>
<link>https://andgein.ru/blog/tags/ctf-2/</link>
<description>Соревнования по компьютерной безопасности Capture The Flag</description>
<author></author>
<language>en</language>
<generator>Aegea 11.3 (v4134)</generator>

<itunes:subtitle>Соревнования по компьютерной безопасности Capture The Flag</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>Заметка восемнадцатая. Разбор задания Wired CSV с Google CTF 2018</title>
<guid isPermaLink="false">19</guid>
<link>https://andgein.ru/blog/all/18-wired-csv-writeup/</link>
<pubDate>Fri, 29 Jun 2018 19:37:37 +0100</pubDate>
<author></author>
<comments>https://andgein.ru/blog/all/18-wired-csv-writeup/</comments>
<description>
&lt;p&gt;Год назад &lt;a href="/blog/all/5-crypto-backdoor-writeup/"&gt;мы играли в Google CTF&lt;/a&gt;. В этом году решили не отставать и снова размяться командой старичков. Вообще мы стали чаще играть в CTF, это меня очень радует.&lt;/p&gt;
&lt;p&gt;Вообще эта заметка не совсем о таске. Ну то есть о нём, да, но ещё и об одной очень важно и простой мысли. Об этом точно знает любой человек, когда-либо игравший в CTF. CTF — это в первую очередь такой способ узнавать о технологиях, окружающих нас, и о том, как они устроены. Студентам я всегда рассказываю, что выходя с соревнования вы должны ощущать, как много нового вы сегодня узнали. Возможно, ничего не решили, да, но зато узнали-то ого-го!&lt;/p&gt;
&lt;p&gt;Так вот о чём это я. Я всегда обходил стороной таски с фотографией каких-нибудь плат и проводов. Я далёк от схемотехники, электричества, да и вообще от физики, так что всегда предпочитал им какие-нибудь более высокоуровневые вещи: типа программирования, веб-безопасности или криптографии. Но рано или поздно это должно было случиться.&lt;/p&gt;
&lt;p&gt;Итак, нам дали картинку подключения... эм... какого-то чипа на... эм... какой-то плате к... эм... какому-то устройству (не очень информативно, да, но вы же помните, что я в этом ничего не понимаю?)&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/wires.jpg" width="1906" height="1164" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Кроме фотографии нам дали 222-мегабайтную CSV-шку: &lt;a href="/files/blog/18/data.7z"&gt;data.7z&lt;/a&gt;. Начало у этой CSV-шки примерно такое:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Time [s],Wire6-Analog,Wire7-Analog, Time [s],Wire0-Digital, Time [s],Wire1-Digital, Time [s],Wire2-Digital, Time [s],Wire3-Digital, Time [s],Wire4-Digital, Time [s],Wire5-Digital, Time [s],Wire6-Digital, Time [s],Wire7-Digital
0.000000000000000, 4.768121242523193, 4.773899555206299, 0.000000000000000, 0, 0.000000000000000, 0, 0.000000000000000, 0, 0.000000000000000, 1, 0.000000000000000, 0, 0.000000000000000, 0, 0.000000000000000, 1, 0.000000000000000, 1
0.000008000000000, 4.768121242523193, 4.773899555206299, 0.000000990000000, 1, 0.000065560000000, 1, 0.000194380000000, 1, 0.000451750000000, 0, 0.000452070000000, 1, 0.001480790000000, 1, 1.468471380000000, 0, 2.503182740000000, 0
0.000016000000000, 4.773141384124756, 4.778934478759766, 0.000065230000000, 0, 0.000194070000000, 0, 0.000451450000000, 0, 0.000965990000000, 1, 0.001480440000000, 0, 0.003537600000000, 0, 1.468535670000000, 1, 2.503689840000000, 1
0.000024000000000, 4.773141384124756, 4.773899555206299, 0.000129540000000, 1, 0.000322660000000, 1, 0.000708600000000, 1, 0.001480170000000, 0, 0.002508920000000, 1, 0.005594510000000, 1, 1.472585100000000, 0, 2.507288860000000, 0
0.000032000000000, 4.773141384124756, 4.773899555206299, 0.000193780000000, 0, 0.000451180000000, 0, 0.000965660000000, 0, 0.001994420000000, 1, 0.003537300000000, 0, 0.007651320000000, 0, 1.472649390000000, 1, 2.507799430000000, 1
0.000040000000000, 4.773141384124756, 4.773899555206299, 0.000258100000000, 1, 0.000579770000000, 1, 0.001222810000000, 1, 0.002508600000000, 0, 0.004565780000000, 1, 0.009708230000000, 1, 1.476698830000000, 0, 2.511395640000000, 0
0.000048000000000, 4.778161048889160, 4.778934478759766, 0.000322340000000, 0, 0.000708280000000, 0, 0.001479880000000, 0, 0.003022850000000, 1, 0.005594170000000, 0, 0.011765040000000, 0, 1.476763110000000, 1, 2.511904320000000, 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Первые полчаса мы занимались двумя вещами: пытались понять, зачем в таблице 9 столбцов со временем, когда можно было сделать один, и гуглили надпись на чипе, к которому подключены клеммы (они же клеммами называются, да?).&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_21-44-58_edited.jpg" width="1181" height="634" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Строчки с маркировки &lt;a href="https://www.google.ru/search?q=ami+8327vt"&gt;гуглятся&lt;/a&gt; &lt;a href="https://www.google.ru/search?q=COI2294b"&gt;плохо&lt;/a&gt;. Спасает запрос «&lt;a href="https://www.google.ru/search?q=chip+ami+8327"&gt;chip ami 8327&lt;/a&gt;», по которому на первом месте выводится страничка «Atari chips». Заодно выясняется, что на чипе написано «CO1», а не «COI», и именно поэтому вторая строчка не гуглилась.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_21-49-26.png" width="929" height="754" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Искомое «CO12294B» на этой странице встречается аж шесть раз, и каждый раз около слова «pokey». Наконец-то можно пойти почитать &lt;a href="https://ru.wikipedia.org/wiki/Atari_POKEY"&gt;википедию&lt;/a&gt; (это мой любимый момент в каждом таске, ведь именно здесь узнаётся столько нового):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Atari POKEY (Pot Keyboard Integrated Circuit) — электронный компонент, специально разработанная фирмой Atari микросхема генерации звука и интерфейса с устройствами управления. Использовалась в 1980-х годах в ряде игровых систем от Atari — бытовых компьютерах, игровых консолях и аркадных игровых автоматах. Название микросхемы составлено из начальных слогов английских слов POtentiometer и KEYboard, так как эта микросхема часто использовалась для опроса клавиатуры и аналоговых устройств управления (типа paddle). Но в основном, POKEY стала известна благодаря своими возможностями генерации звуковых эффектов и музыки, получив своих поклонников, аналогично микросхемам MOS Technology SID и General Instruments AY-3-8910.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Окей, значит, перед нами либо что-то связанное с клавиатурой, либо что-то, издающее звуки. Поехали на английскую википедию: &lt;a href="https://en.wikipedia.org/wiki/POKEY"&gt;https://en.wikipedia.org/wiki/POKEY&lt;/a&gt; (кстати, картинки в обеих википедиях подсказывают, что мы на правильном пути: там нарисованы именно такие чипы, как у нас). В английской википедии есть распиновка чипа, что для нас очень важно, ведь клеммы подсоединены к конкретным выходам, а не ко всем:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_21-54-24.png" width="771" height="546" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Итак, две клеммы подсоединены к Vss, то есть к земле (по крайней мере, так написано в левой табличке). Остальные восемь — к линиям KR1, KR2, K0, K1, K2, K3, K4 и K5. Первые две, как написано всё в той же табличке, отвечают за «Keyboard Row strobe Input», а остальные шесть — за «Keyboard Scan Output». Ага, всё-таки клавиатура! Про линии K0–K5, вроде, понятно: в статье написано, что поддерживаются клавиатуры до 64 клавиш (+ два модификатора: шифт и контрол), а 64 клавиши — это как раз 6 битов. Вот только что такое остальные две линии и что такое Keyboard Row strobe Input?&lt;/p&gt;
&lt;p&gt;Тут мы снова почитали википедию (не стыдно не знать! стыдно не хотеть узнать!): &lt;a href="https://en.wikipedia.org/wiki/Data_strobe_encoding."&gt;https://en.wikipedia.org/wiki/Data_strobe_encoding.&lt;/a&gt; Там написано, что strobe encoding добавляет к линии данных ещё одну линию, причём делает так, чтобы XOR значений на двух линиях менялся каждый такт. Это позволяет синхронизировать такты, а также обнаруживать поломки линии. Ну круто, чо. А почему у нас на шесть линий с данными только две линии с этим strobe? Или это не о том?..&lt;/p&gt;
&lt;p&gt;В этом месте мы немного подзастряли, если честно. У нас ведь кроме картинки был ещё 200-мегабайтный CSV-файл с кучей отметок времени, а мы пока за него даже не брались. Поизучаем-ка его более внимательно. Во-первых, кажется, что он состоит из девяти независимых логических колонок: в первой задаётся время и значение (вольтажа?) на двух аналоговых линиях, а в каждой из следующих восьми снова задаётся какое-то время и логическое значение на одной из цифровых линий — нолик или единичка. Вот только почему-то чем дальше, тем логические колонки становятся короче. В первой, например, сотни тысяч записей, сделанных суммарно за 20 секунд, а в последней — только 11. Не 11 тысяч, нет. Просто 11.&lt;/p&gt;
&lt;p&gt;Начали смотреть файл ещё внимательнее. Замечаем, что значение каждой логической линии всё время чередуется — то ноль, то единица, потом снова ноль. Предполагаем сразу, конечно, что записывали только изменения значения. Это объясняет и то, почему значений для последней линии так мало: видимо, значение на ней редко менялось. Но почему значение на первой линии так часто меняется? Посчитали — получается около 15 000 раз в секунду. Не может же быть, что кто-то так быстро нажимал на клавиатуре кнопки? Хм, а, может, это тот самый strobe encoding?..&lt;/p&gt;
&lt;p&gt;Окей, пока все равно почти ничего не понятно, изучаем файл дальше. Замечаем, что если первая цифровая линия меняет своё значение 15 000 раз в секунду, то вторая — ровно в два раза реже. Удивительно, но третья — ещё в два раза реже! Подозрительно, но всё ещё непонятно :)&lt;/p&gt;
&lt;p&gt;Чтоб вы понимали накал страстей: в этот момент мы даже покушали — настолько всё было непонятно!&lt;/p&gt;
&lt;p&gt;А после обеда наткнулись на &lt;a href="http://visual6502.org/images/C012294_Pokey/pokey.pdf"&gt;отсканированную версию документации&lt;/a&gt; по этим чипам от самой Atari (с грифом CONFIDENTIAL!). Обязательно перейдите по ссылке и полистайте её. С высоты сегодняшних технологий это кажется невероятным трудом:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="804" data-ratio="0.92096219931271"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-11-54.png" width="804" height="873" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-11-16.png" width="791" height="814" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-11-26.png" width="804" height="767" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-11-36.png" width="782" height="825" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Ох уж эти написанные на печатной машинке тексты и нарисованные от руки схемы... От них прямо пахнет чем-то волшебным.&lt;/p&gt;
&lt;p&gt;В общем, погрузились мы в эту пдфку. А так как в схемотехнике ничего не понимаем, погружаться пришлось очень медленно. Раздел про аудио аккуратно пропустили, перешли сразу к клавиатуре.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-14-02.png" width="800" height="780" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Что тут написано: во-первых, линии K0–K5 действительно передают скан-код нажатой клавиши. Брать эти биты надо с отрицанием. В википедии тоже что-то подобное было написано, правда, почему-то отрицание навешано только на K0, K1, K2 и K5:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-15-25.png" width="204" height="110" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Интересно, как на самом деле?&lt;/p&gt;
&lt;p&gt;Дальше написано, что внутри чипа POKEY, который уже успел стать нам родным, есть 6-битный счётчик, 6-битный регистр для сравнения, и 8-битный регистр для итогового скан-кода. А дальше идёт абзац, который мы читали (и страдали!) минут двадцать:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-17-21.png" width="768" height="298" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Итак, есть некоторый алгоритм для определения, в какой момент сигналы, выставленные на линиях K0–K5, всё-таки являются кодом нажатой клавиши, а когда их не надо слушать. Если отрицание KR1 становится нулём («low» в терминах текста), то значение из счётчика копируется в регистр для сравнения. Дальше происходит некоторая магия для определения того, не было ли это случайностью, и если нажатие клавиши подтверждается на следующем тике, то процессору посылается прерывание, а скан-код нажатой клавиши попадает в специальный регистр. После этого запускается парный алгоритм, не позволяющий создавать прерывания очень-очень часто, пока человек держит кнопку нажатой. На следующей странице пдфки даже есть блок-схема для этого процесса. Мы потратили на неё ещё минут десять:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-22-49.png" width="673" height="860" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Окей, но это всё не объясняет, почему у нас значение на линии меняется туда-сюда 15 000 раз за секунду. Ну не нажимают люди так быстро клавиши! Помогла как всегда случайность. Мы нашли ещё одну версию этой документации, причём не только в отсканированном, но и оцифрованном виде: &lt;a href="http://krap.pl/mirrorz/atari/homepage.ntlworld.com/kryten_droid/Atari/800XL/atari_hw/pokey.htm."&gt;http://krap.pl/mirrorz/atari/homepage.ntlworld.com/kryten_droid/Atari/800XL/atari_hw/pokey.htm.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Там был весь тот же текст, что мы уже читали, но вдобавок ещё и интересная картинка:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/pokey_kbd_scan_timing.gif" width="766" height="334" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Во-первых, каждая следующая линия меняет своё значение в два раза реже, чем предыдущая. Прямо, как у нас! Во-вторых, минимальный такт равен 1 / 15.7 kHz, то есть такты тикают 15 700 раз в секунду. Прямо, как у нас [2]!&lt;/p&gt;
&lt;p&gt;Благодаря этой картинке появилась гипотеза: значения на каналах K0–K5 меняются вне зависимости от того, нажимал ли человек кнопки, причём меняются именно так, как нарисовано на картинке: каждое следующее меняется в два раза реже, чем предыдущее. И именно это — тот самый strobe encoding, а вовсе не линии KR1 и KR2, как было написано в википедии. А вот когда человек нажимает кнопку на клавиатуре, линии K0–K5 выставляются в правильное положение, а на отрицание KR1 подаётся единица (то есть на самом деле на KR1 подаётся ноль).&lt;/p&gt;
&lt;p&gt;Ну что ж, осталось написать код:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;def run():
    time = 0
    last_kr1 = 0
    last_scancode = 0
    answer = &amp;#039;&amp;#039;
    while time &amp;lt; 20:
        values = get_values(time)
        kr1, kr2, scancode = parse_values(values)
        if last_kr1 == 0 and kr1 == 1:
            if last_scancode != scancode:
                print(f&amp;#039;Current time is {time}, values are {values}&amp;#039;)
                print(f&amp;#039;kr1 = {kr1}, kr2 = {kr2}, scancode = {scancode}, char = {SCANCODES[scancode]}&amp;#039;)
                answer += SCANCODES[scancode]
            last_scancode = scancode
        last_kr1 = kr1
        time += HSYNC
    print(answer)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В этом коде мы ловим момент, когда отрицание KR1 изменилось с нуля на единицу и выхватываем скан-код, посчитанный из значений K0–K5. Некоторые символы дублировались, потому что по-хорошему надо было реализовать ту самую логику, описанную в пдфке, но это слишком сложно :) Так что просто запоминаем последний скан-код и не повторяем его, если он случился снова.&lt;/p&gt;
&lt;p&gt;Вот только где взять таблицу скан-кодов и символов (словарь &lt;i&gt;SCANCODES&lt;/i&gt; в коде)? В пдфке её почему-то не было. Зато нужная табличка (как всегда) нашлась где-то в закромах интернета: &lt;a href="https://atariwiki.org/wiki/Wiki.jsp?page=KBCODE"&gt;https://atariwiki.org/wiki/Wiki.jsp?page=KBCODE&lt;/a&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2018-06-29_22-46-05.png" width="713" height="543" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Остаётся только перебить его в наше решение, запустить и получить ответ: «FLAG; 8-BIT-HARDWARE-KEYLOGER{CR}». Отправляем, и-и-и, ..., неправильно :( Да ладно, не может быть, фраза-то читаемая получилась. Замечаем одну G в KEYLOGGER и понимаем, что наш алгоритм в данном случае зря выкинул повторение :) Возвращаем вторую G и сдаём ответ.&lt;/p&gt;
&lt;p&gt;Полный код решения можно найти &lt;a href="https://gist.github.com/andgein/a99fcafade86a9921b870653b81df371"&gt;на гитхабе&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;P.S. Всё-таки оригинальная пдфка была права: надо брать отрицания ко всем линиям, в том числе и к K3, и к K4. А википедия не права.&lt;/p&gt;
</description>
</item>

<item>
<title>Заметка пятая. Разбор задания Crypto Backdoor с Google CTF 2017</title>
<guid isPermaLink="false">5</guid>
<link>https://andgein.ru/blog/all/5-crypto-backdoor-writeup/</link>
<pubDate>Sat, 24 Jun 2017 13:10:53 +0100</pubDate>
<author></author>
<comments>https://andgein.ru/blog/all/5-crypto-backdoor-writeup/</comments>
<description>
&lt;p&gt;Это заметка о том, как мы с Костей Плотниковым решали задание на ассиметричную криптографию с Google CTF, который прошёл неделю назад. Она кишит кодом на питоне, несложными терминами из теории групп и математическими выкладками. Смело пропускайте, если боитесь чего-нибудь из этого.&lt;/p&gt;
&lt;script type="text/x-mathjax-config"&gt;
MathJax.Hub.Config({
  tex2jax: {
     inlineMath: [['$','$'], ['\\(','\\)']]
  },
  "HTML-CSS": {
     linebreaks: { automatic: true, width: "75% container" }
   }, 
  "SVG": {
     linebreaks: { automatic: true, width: "75% container" }
   } 
});
&lt;/script&gt;
&lt;script async src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_CHTML'&gt;&lt;/script&gt;
&lt;p&gt;Формулировка как всегда бесконечно расплывчата:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This public-key cryptosystem has a flaw. Can you exploit it? &lt;a href="/files/blog/5/crypto_backdoor.py"&gt;crypto_backdoor.py&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Костя попробовал меня ввести в курс дела, когда я присоединился к нему. Он рассказал, что в скрипте реализовано что-то вроде &lt;a href="https://ru.wikipedia.org/wiki/%D0%AD%D0%BB%D0%BB%D0%B8%D0%BF%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F"&gt;эллиптической криптографии&lt;/a&gt;. Работает она так: сначала на плоскости рисуется специальная &lt;a href="https://ru.wikipedia.org/wiki/%D0%AD%D0%BB%D0%BB%D0%B8%D0%BF%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BA%D1%80%D0%B8%D0%B2%D0%B0%D1%8F"&gt;кривая&lt;/a&gt;, а затем для каждой пары точек на ней определяется операция сложения. Сумма двух точек с кривой тоже лежит на кривой за исключением случая, когда получается особое значение — ноль. После этого естественным образом определяется умножение точки на натуральное число.&lt;/p&gt;
&lt;p&gt;В эллиптической криптографии Боб, желающий получать зашифрованные сообщения от Алисы, выбирает секретное число $N$ и точку на эллиптической кривой $g$. Последняя называется генератором и сообщается всем желающим, а вот секретное число становится закрытым ключом Боба. Открытым ключом в этом случае называется произведение генератора и закрытого ключа; $B = Ng$.&lt;/p&gt;
&lt;p&gt;Собственно, на мысли об эллиптических кривых Костю натолкнули наличие в скрипте точки-генератора и двух открытых ключей:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;# Modulus
p = 606341371901192354470259703076328716992246317693812238045286463
# g is the generator point.
g = (160057538006753370699321703048317480466874572114764155861735009, 255466303302648575056527135374882065819706963269525464635673824)
# Alice&amp;#039;s public key A:
A = (460868776123995205521652669050817772789692922946697572502806062, 263320455545743566732526866838203345604600592515673506653173727)
# Bob&amp;#039;s public key B:
B = (270400597838364567126384881699673470955074338456296574231734133, 526337866156590745463188427547342121612334530789375115287956485)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Модуль $p$ здесь тоже неслучаен — в эллиптической криптографии как раз рассматриваются кривые над полем $\mathbb{Z}_p$. Позже, правда, мы пришли к выводу, что эллиптическая криптография тут совсем ни при чём... Но сначала мы внимательно изучили операцию сложения двух точек:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;def add(a, b, p):
  if a == -1:
    return b
  if b == -1:
    return a
  x1, y1 = a
  x2, y2 = b
  x3 = ((x1*x2 - x1*y2 - x2*y1 + 2*y1*y2) * modinv(x1 + x2 - y1 - y2 - 1, p)) % p
  y3 = ((y1*y2) * modinv(x1 + x2 - y1 - y2 - 1, p)) % p
  return (x3, y3)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Нейтральный элемент (он же &lt;i&gt;ноль&lt;/i&gt;) обозначен как $-1$, поэтому первые четыре строки функции очевидны. Функция $modinv(x, p)$ находит обратный элемент к $x$ в кольце $\mathbb{Z}_p$ с помощью расширенного алгоритма Евклида. Значит, сумма точек $(x_1, y_1)$ и $(x_2, y_2)$ это точка&lt;br /&gt;
$$\left(\frac{x_1x_2 – x_1y_2 – x_2y_1 + 2y_1y_2}{x_1+x_2-y_1-y_2-1}, \frac{y_1y_2}{x_1+x_2-y_1-y_2-1}\right)$$&lt;/p&gt;
&lt;p&gt;Дробь означает целочисленное деление в кольце $\mathbb{Z}_p$.&lt;/p&gt;
&lt;h2&gt;Упрощаем сложение&lt;/h2&gt;
&lt;p&gt;Математик во мне захотел разобраться с этой операцией. Больше всего смущали одинаковые знаменатели и сложный числитель первой координаты. Стало проще, когда я переписал числитель как $(x_1-y_1)(x_2-y_2)+y_1y_2$, а знаменатель как $(x_1-y_1) + (x_2-y_2)$. Давайте перейдём из системы координат $(x, y)$ в систему $(t, y)$, где $t = x-y$. Обратный переход тоже очень простой: $x = t + y$. Если переписать операцию сложения в новой системе координат, то получится&lt;/p&gt;
&lt;p&gt;$$(t_1, y_1) + (t_2, y_2) = \left(\frac{t_1t_2+y_1y_2}{t_1+t_2-1} – \frac{y_1y_2}{t_1+t_2-1}, \frac{y_1y_2}{t_1+t_2-1}\right) = \left(\frac{t_1t_2}{t_1+t_2-1}, \frac{y_1y_2}{t_1+t_2-1}\right)$$&lt;/p&gt;
&lt;p&gt;Получившаяся формула в некотором смысле даже симметрична. Это дало нам оптимизм на несколько следующих часов.&lt;/p&gt;
&lt;h2&gt;Мастер-секрет&lt;/h2&gt;
&lt;p&gt;Однако в скрипте само по себе сложение нигде не используются. Зато используется умножение точки на число:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;from secret_data import aliceSecret, bobSecret, flag

assert A == mul(aliceSecret, g, p)
assert B == mul(bobSecret, g, p)

aliceMS = mul(aliceSecret, B, p)
bobMS = mul(bobSecret, A, p)
assert aliceMS == bobMS
masterSecret = aliceMS[0]*aliceMS[1]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Настало время разобраться, что вообще происходит в скрипте и что нужно найти.&lt;/p&gt;
&lt;p&gt;$aliceSecret$ — это натуральное число, закрытый ключ Алисы, он умножается на известный нам генератор, и в результате получается известный нам открытый ключ Алисы. Аналогичное проделывается с ключами Боба. После этого открытый ключ Боба умножается на закрытый ключ Алисы, получая точку $aliceMS = aliceSecret \times bobSecret \times g$. Симметричная операция вычисляет $bobMS = bobSecret \times aliceSecret \times g$. Очевидно, что эти две точки должны совпасть, что и проверяется последним assert’ом. Мастер-секретом объявляется произведение координат этой общей точки. Этот мастер-секрет и надо найти, так как в конце скрипта он ксорится с флагом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;def encrypt(message, key):
  return message ^ key

length = len(flag)
encrypted_message = encrypt(I(flag), masterSecret)
print &amp;quot;length = %d, encrypted_message = %d&amp;quot; % (length, encrypted_message)
# length = 31, encrypted_message = 137737300119926924583874978524079282469973134128061924568175107915062758827931077214500356470551826348226759580545095568667325&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Умножение в новых координатах&lt;/h2&gt;
&lt;p&gt;Раз для подсчёта мастер-секрета используется умножение, то вернёмся к нашим формулам и выясним, как точка в новых координатах $(t, y)$ умножается на число $k$. Переберём несколько маленьких коэффициентов:&lt;/p&gt;
&lt;p&gt;$$2(t, y) = (t, y) + (t, y) = \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right)$$&lt;/p&gt;
&lt;p&gt;$$3(t, y) = 2(t, y) + (t, y) = \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right) + (t, y) =&lt;br /&gt;
\left(\frac{\frac{t^3}{2t-1}}{\frac{t^2}{2t-1} + t – 1}, \frac{{\frac{y^3}{2t-1}}}{\frac{t^2}{2t-1} + t – 1}\right) =&lt;br /&gt;
\left(\frac{t^3}{3t^2-3t+1}, \frac{y^3}{3t^2-3t+1}\right)$$&lt;/p&gt;
&lt;p&gt;$$4(t, y) = 2(t, y) + 2(t, y) = \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right) + \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right)  =&lt;br /&gt;
\left(\frac{\frac{t^4}{(2t-1)^2}}{\frac{2t^2}{2t-1} – 1}, \frac{\frac{y^4}{(2t-1)^2}}{\frac{2t^2}{2t-1} – 1}\right) =&lt;br /&gt;
\left(\frac{\frac{t^4}{2t-1}}{2t^2-2t+1}, \frac{\frac{y^4}{2t-1}}{2t^2-2t+1}\right) =&lt;br /&gt;
\left(\frac{t^4}{4t^3-6t^2+4t-1}, \frac{y^4}{4t^3-6t^2+4t-1}\right) $$&lt;/p&gt;
&lt;p&gt;Умножать дальше становилось всё сложнее, поэтому пора найти закономерность. В числителях всё время получается $t^k$ и $y^k$, а в знаменателях видны биномиальные коэффициенты. Смотрите, $4t^3-6t^2+4t-1$ похоже на $t^4 + (4t^3-6t^2+4t-1)$, что в свою очередь равно $(t + 1)^4$. Так у нас появилась гипотеза, что знаменатель равен $(t+1)^k – t^k$.&lt;/p&gt;
&lt;p&gt;Итоговую формулу&lt;/p&gt;
&lt;p&gt;$$k \times (t, y) = \left(\frac{t^k}{(t+1)^k – t^k}, \frac{y^k}{(t+1)^k – t^k}\right)$$&lt;/p&gt;
&lt;p&gt;теперь можно доказать по индукции по $k$. Делать я этого, конечно же, не буду.&lt;/p&gt;
&lt;h2&gt;Решение&lt;/h2&gt;
&lt;p&gt;Задача будет решена, если мы узнаем закрытый ключ Алисы или Боба. В случае с Бобом нам достаточно найти такое $k \in \mathbb{Z}_p$, что $kg = B$. В наших новых координатах и формулах это выглядит так:&lt;/p&gt;
&lt;p&gt;$$\left(\frac{g_t^k}{(g_t+1)^k – g_t^k}, \frac{g_y^k}{(g_t+1)^k – g_t^k}\right) = (B_t, B_y)$$&lt;/p&gt;
&lt;p&gt;где $(g_t, g_y)$ — координаты генератора $g$ в системе $(t, y)$, а $(B_t, B_y)$ — аналогичные координаты точки $B$. Взяв из этого равенства первую координату, получаем:&lt;/p&gt;
&lt;p&gt;$$ \frac{g_t^k}{(g_t+1)^k – g_t^k} = B_t$$&lt;/p&gt;
&lt;p&gt;$$g_t^k = B_t ((g_t+1)^k – g_t^k)$$&lt;/p&gt;
&lt;p&gt;$$g_t^k (1 + B_t) = B_t (g_t+1)^k$$&lt;/p&gt;
&lt;p&gt;$$\left(\frac{g_t}{g_t+1}\right)^k = \frac{B_t}{1 + B_t}$$&lt;/p&gt;
&lt;p&gt;Чтобы найти искомое $k$, достаточно вычислить логарифм в кольце $\mathbb{Z}_p$, то есть решить задачу &lt;a href="https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5"&gt;дискретного локарифмирования&lt;/a&gt;. Здесь мы с Костей приуныли, потому что поняли, что зашли в тупик. Дискретное логарифмирование — задача, которую нельзя решить за нормальное время.&lt;/p&gt;
&lt;p&gt;Только через час Костя догадался проверить данный нам модуль $p$ на простоту. Совершенно неожиданно он оказался составным:&lt;/p&gt;
&lt;p&gt;$$p = 961236149 \times 951236179 \times 941236273 \times 911236121 \times 931235651 \times 921236161 \times 901236131$$&lt;/p&gt;
&lt;p&gt;Наверняка все знают, как посчитать дискретный логарифм по непростому основанию. А мы не знаем. Так что мы взяли бумажку и стали придумывать алгоритм.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;i&gt;Когда-то Асанов рассказывал байку, что студенты УПИ обычно знают способ решить задачу, а студенты матмеха не знают. Зато студенты матмеха могут придумать решение. Кажется, теперь я понял эту байку.&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Дискретный логарифм по составному основанию&lt;/h2&gt;
&lt;p&gt;Пусть мы нашли дискретный логарифм от $b$ по основанию $a$ в кольцах $\mathbb{Z}_p$ и $\mathbb{Z}_q$. То есть мы нашли такие $n$ и $m$, что&lt;/p&gt;
&lt;p&gt;$$a^n \equiv b \pmod{p}$$&lt;br /&gt;
$$a^m \equiv b \pmod{q}$$&lt;/p&gt;
&lt;p&gt;Теперь мы хотим решить задачу в кольце $\mathbb{Z}_{pq}$, то есть найти такое $k$, что $a^k \equiv b \pmod{pq}$. Порядком числа $a$ в кольце называется такое минимальное натуральное $z$, что $a^z = 1$. Пусть число $a$ имеет порядки $u$ и $v$ в кольцах $\mathbb{Z}_p$ и $\mathbb{Z}_q$ соответственно. Тогда $b \equiv a^n \equiv a^{n + u} \equiv a^{n+2u} \equiv a^{n+3u} \equiv \dots \pmod{p}$ и $b \equiv a^m \equiv a^{m + v} \equiv a^{m+2v} \equiv a^{m+3v} \equiv \dots \pmod{q}$. Если мы найдём число, которое можно выразить и как $n + ?u$, и как $m + ?v$, то оно станет ответом задачи дискретного логарифмирования по модулю $pq$.&lt;/p&gt;
&lt;p&gt;Ну а найти такое число — дело техники и расширенного алгоритма Евклида. Если $n + ?u = m + ?v$, то $?u – ?v = m – n$. Если $m-n$ не делится на $НОД(u, v)$, то решения нет, иначе расширенный алгоритм Евклида решит это &lt;a href="https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BE%D1%84%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%D0%BE_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5#.D0.9B.D0.B8.D0.BD.D0.B5.D0.B9.D0.BD.D1.8B.D0.B5_.D0.B4.D0.B8.D0.BE.D1.84.D0.B0.D0.BD.D1.82.D0.BE.D0.B2.D1.8B_.D1.83.D1.80.D0.B0.D0.B2.D0.BD.D0.B5.D0.BD.D0.B8.D1.8F"&gt;линейное диофантово уравнение&lt;/a&gt;, и всё станет хорошо.&lt;/p&gt;
&lt;p&gt;Так как данный в задании модуль $p$ мы разложили на простые множители $p_1 \times p_2 \times \dots \times p_7$, которые к тому же получились не очень большими, то мы можем решить задачу дискретного логарифмирования в каждом из $\mathbb{Z}_{p_i}$. Соединить семь чисел в один большой ответ поможет описанный только что алгоритм.&lt;/p&gt;
&lt;h2&gt;Как решить дискретный логарифм для  $p_i$&lt;/h2&gt;
&lt;p&gt;Простые делители модуля $p$ были не очень большими, но и не настолько маленькими, чтобы дискретный логарифм можно было найти простым перебором. К счастью, есть алгоритм &lt;i&gt;Baby Steps Giant Steps&lt;/i&gt;, который позволяет сделать это намного быстрее — за $O(\sqrt{n})$. Его мы и реализовали. Да, мы думали о том, что Baby Steps Giant Steps уже реализован в куче библиотек, но лично меня это только раззадоривает — так интересно ведь всё сделать самому.&lt;/p&gt;
&lt;h2&gt;Как найти порядок элемента в кольце&lt;/h2&gt;
&lt;p&gt;На часах было уже около часа ночи, а мне предстояло запрограммировать на питоне поиск порядка элемента в кольце $\mathbb{Z}_p$. Теорема Эйлера шептала, что $x^{\phi(p)} \equiv 1 \pmod{p}$, но $\phi(p)$ могло оказаться не минимальным числом с таким свойством. Значит, искомый порядок — это делитель $\phi(p)$. Но делителей слишком много, чтобы перебирать их все. Так что пришла пора узнать более быстрый способ находить порядок элемента. На помощь пришла &lt;a href="http://cacr.uwaterloo.ca/hac/about/chap4.pdf"&gt;пдфка из интернета&lt;/a&gt;, в которой есть чудесный алгоритм 4.79, я реализовал его и научился быстро находить порядок элемента в кольце, зная разложение на простые множители у $\phi(p)$.&lt;/p&gt;
&lt;h2&gt;Заключение&lt;/h2&gt;
&lt;p&gt;Применив всю магию сверху, мы нашли закрытый ключ Боба $bobSecret$. Умножив его на открытый ключ Алисы, мы нашли мастер-секрет, который и выдал нам флаг:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Master secret found: (254828745614253797280016043417264027645246572307317271091197847, 540509273347153402828726537667691800163306365090607497812000946)
Flag: CTF{Anyone-can-make-bad-crypto}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если вкратце повторить всё наше решение, то оно перестаёт казаться страшным и длинным:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;переходим в систему координат $(t, y)$,&lt;/li&gt;
&lt;li&gt;вычисляем $a = \frac{g_t}{g_t+1}$ и $b = \frac{B_t}{1 + B_t}$,&lt;/li&gt;
&lt;li&gt;решаем задачу дискретного логарифма $a^{?} = b$ в кольце $\mathbb{Z}_p$, зная разложение $p$ на простые множители,&lt;/li&gt;
&lt;li&gt;умножаем полученный ответ на открытый ключ Алисы $A$, это и есть мастер-секрет,&lt;/li&gt;
&lt;li&gt;переходим обратно в координаты $(x, y)$, перемножаем координаты мастер-секрета, расшифровываем флаг.&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>Заметка вторая. О сервисе на PHDays CTF</title>
<guid isPermaLink="false">2</guid>
<link>https://andgein.ru/blog/all/2-phdays-ctf-2017-service/</link>
<pubDate>Wed, 17 May 2017 15:23:38 +0100</pubDate>
<author></author>
<comments>https://andgein.ru/blog/all/2-phdays-ctf-2017-service/</comments>
<description>
&lt;p&gt;&lt;i&gt;Продолжение истории о &lt;a href="/blog/all/1-unicode-and-csv-in-python/"&gt;необычном поведении&lt;/a&gt; юникода и CSV в питоне&lt;/i&gt;&lt;/p&gt;
&lt;h2&gt;Для тех, кому лень читать предыдущую заметку или вспоминать, о чём там было&lt;/h2&gt;
&lt;p&gt;Если использовать стандартные питоновские &lt;a href="https://docs.python.org/3/library/codecs.html#codecs.open"&gt;codecs.open&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/csv.html#csv.DictWriter"&gt;csv.DictWriter&lt;/a&gt; и &lt;a href="https://docs.python.org/3/library/csv.html#csv.DictReader"&gt;csv.DictReader&lt;/a&gt;, то можно столкнуться с интересным поведением. Создаём в программе файл в кодировке UTF-8, пишем туда с помощью DictWriter, а затем читаем через DictReader. Если в данных встречались юникодные символы Pararaph separator или Line separator, то мы считаем больше записей, чем было записано, а их CSV-структура будет поломана.&lt;/p&gt;
&lt;p&gt;Читайте &lt;a href="/blog/all/1-unicode-and-csv-in-python/"&gt;первую заметку&lt;/a&gt; для подробностей.&lt;/p&gt;
&lt;h2&gt;Как из этого получился сервис для классического CTF&lt;/h2&gt;
&lt;p&gt;В апреле мы с ребятами из Хакердома как раз готовили онлайновый CTF для форума &lt;a href="https://phdays.com"&gt;PHDays&lt;/a&gt;. Темой был выбран интернет вещей, а сервисами были умный чайник, термометр, дверной замок, телевизор и даже холодильник. Мне достался последний, потому что я слишком люблю еду.&lt;/p&gt;
&lt;p&gt;Чтобы органично встроить уязвимость в сервис, мне нужно было сделать так, чтобы кто-то писал в CSV-файл, а кто-то другой — читал. В итоге сервис состоял из двух частей — веб-приложения для удобного управления человеком и API для других умных кухонных гаджетов, которым нужна информация от холодильника.&lt;/p&gt;
&lt;p&gt;Веб-интерфейс позволял пользователям регистрироваться и создавать холодильные камеры или просто холодильники. У каждого холодильника был владелец, и увидеть чужие просто так нельзя. В холодильники можно добавлять еду. Для этого сначала было необходимо зарегистрировать новый продукт (например, картошку или молоко) и указать, в чём он измеряется — в граммах, пачках или литрах. Выглядело это так:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="1122" data-ratio="2.5384615384615"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2017-05-16_17-12-56-(2).png" width="1122" height="442" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2017-05-16_17-14-21.png" width="1125" height="676" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2017-05-16_17-23-56.png" width="1124" height="597" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2017-05-16_20-56-37.png" width="1127" height="398" alt="" /&gt;
&lt;img src="https://andgein.ru/blog/pictures/2017-05-16_20-58-25-(2).png" width="1159" height="584" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Кроме холодильников можно было создавать рецепты. Про каждый ингредиент рецепта известно, сколько и какого продукты нужно положить, а также что с ним надо сделать и сколько после этого подождать.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://andgein.ru/blog/pictures/2017-05-16_21-07-14.png" width="1122" height="669" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Рецепты были очень важны для функционирования сервиса, ведь в том самом API было всего две команды — получить список твоих холодильников и получить список рецептов, которые можно приготовить из еды, лежащей в одном из твоих холодильников. Звучит сложно, но идея очень простая — если у вас есть умная мультиварка, то она хочет узнать, какие блюда можно сегодня приготовить из продуктов, лежащих у вас в холодильнике.&lt;/p&gt;
&lt;p&gt;Принадлежность выдаваемых рецептов владельцу холодильника в API не проверялась, но должно было работать само: в рецепте должен присутствовать хотя бы один продукт из холодильника, а в холодильник мы можем добавлять только принадлежащие нам продукты. Моё молоко и молоко Васи — два разных продукта, я могу добавить в холодильники и рецепты только первое, а про второе даже не смогу узнать.&lt;/p&gt;
&lt;h2&gt;Уязвимость&lt;/h2&gt;
&lt;p&gt;Итак, где же тут наши подозреваемые — юникод и CSV?&lt;/p&gt;
&lt;p&gt;Сервис API не имеет доступа к базе данных, поэтому информация для него складывалась веб-приложением в специальные CSV-файлы, откуда считывались API-приложением раз в секунду. Вот пример такого файла:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;id,recipe_id,food_type_id,what_to_do,count,pause_after
1,10,15,Yhws aqx vpjhtlyhw ruv tbcyis,11,7
2,11,16,Ok jbso gtpzs ndgoz udeksmvk,10,16
3,12,17,Y ap mculltvedfwwabbnnnco um,6,19
4,13,18,cniyvdjsyuctaamupp zm  qj nwvm,10,9
5,14,19,Kfdvm ref wtb pdtitb,14,15&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В нём хранятся ингредиенты рецептов. Столбцы, соответственно — id ингредиента, id рецепта (сами рецепты лежат в другом файле), id продукта, что нужно сделать, сколько взять продукта и какую выдержать после этого паузу.&lt;/p&gt;
&lt;p&gt;Давайте посмотрим, что будет, если в поле what_to_do добавить тот самый Paragraph separator:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;6,15,20,blablabla 13503,14,20&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;При чтении этот файл будет выглядеть для DictReader’а как&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;6,15,20,blablabla 
13503,14,20&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;То есть так, будто мы добавили продукт №20 в рецепт №14. Продукт №20 принадлежит нам, значит, мы смогли добавить нашу еду в чужой рецепт №14! Теперь API сможет вывести этот рецепт, если в каком-нибудь из наших холодильников будет лежать продукт №20.&lt;/p&gt;
&lt;p&gt;Для полноты картины покажу, как выглядели сохранение и загрузка данных в CSV.&lt;/p&gt;
&lt;h3&gt;Сохранение:&lt;/h3&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;def dump_model_to_file(model, filename):
    # Use Django API to get all model fields
    columns = [field.attname for field in model._meta.fields]

    with codecs.open(filename, &amp;#039;w&amp;#039;, encoding=&amp;#039;utf-8&amp;#039;) as opened_file:
        # Open csv writer and dump header: special row with columns names
        writer = csv.DictWriter(opened_file, fieldnames=columns)
        writer.writeheader()

        # Iterate over all objects of the model
        for obj in model.objects.all():
            object_dict = {}
            for column in columns:
                # Don&amp;#039;t worry about newlines (\n and \r): csv.DictWriter will enclose such strings in quotes (&amp;quot;)
                # So I think there is no vulnerability here
                object_dict[column] = str(getattr(obj, column, &amp;#039;&amp;#039;))

            # Dump dictionary for current object
            writer.writerow(object_dict)&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Загрузка:&lt;/h3&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;class Model:
    def __init__(self, dictionary):
        for key, value in dictionary.items():
            if value is None:
                value = &amp;#039;0&amp;#039;
            if value.isnumeric():
                value = int(value)
            setattr(self, key, value)


def load_models_from_file(filename):
    objects = {}
    with codecs.open(filename, &amp;#039;r&amp;#039;, encoding=&amp;#039;utf-8&amp;#039;) as opened_file:
        reader = csv.DictReader(opened_file)
        for row in reader:
            objects[model.id] = Model(row)
    return objects&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Ещё две уязвимости&lt;/h2&gt;
&lt;p&gt;В Холодильнике мной была заложена ещё одна уязвимость. Но так получилось, что в итоге уязвимостей оказалось не две, а три. Так бывает на CTF, и в этом нет ничего страшного. Иногда бывает обидно, что ты заложил сложную уязвимость, а случайно оставил простую. Но в данном случае всё случилось удачно: незапланированная уязвимость была проще первой, а запланированная — совсем элементарной (правда, позволяла украсть только 20% флагов). Так как в целом соревнование получилось очень сложным, то появление одной незапланированной уязвимости средней сложности сыграло нам на руку.&lt;/p&gt;
&lt;p&gt;Итак, сначала о запланированной уязвимости. Каждый сервис находился в своём докер-контейнере. Докер (docker) — это удобный способ изолировать своё приложение от других, в линуксе работает за счёт его фирменных LXC-контейнеров и ограничений в cgroup. Подробнее о докере можно почитать на &lt;a href="https://docker.com/"&gt;официальном сайте&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Мой сервис состоял из четырёх докер-контейнеров: для веб-приложения, для API-приложения, для базы данных и для веб-сервера nginx. Во время старта первого накатывались миграции, собиралась статика и выполнялись другие служебные команды. В том числе такая:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;# DON&amp;#039;T RUN IT IN PRODUCTION. SOME EVIL GUYS CAN BRUTEFORCE PASSWORD AND WHO KNOW WHAT HAPPENS...
echo &amp;quot;[+] [DEBUG] Django setup, executing: add superuser&amp;quot;
PGPASSWORD=${POSTGRES_PASSWORD} psql -U ${POSTGRES_USER} -h ${POSTGRES_HOST} -c &amp;quot;INSERT INTO auth_user (password, last_login, is_superuser, username, first_name, last_name, email, is_staff, is_active, date_joined) VALUES (&amp;#039;pbkdf2_sha256\$36000\$k36V24q60mNo\$v5og9qcgc2sqkVwGjZDKNK+wcJy60ix8DIt9E8Yg48c=&amp;#039;, &amp;#039;1970-01-01 00:00:00.000000&amp;#039;, true, &amp;#039;admin&amp;#039;, &amp;#039;admin&amp;#039;, &amp;#039;admin&amp;#039;, &amp;#039;admin@admin&amp;#039;, true, true, &amp;#039;1970-01-01 00:00:00.000000&amp;#039;) ON CONFLICT (username) DO NOTHING&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Эта команда добавляет напрямую в базу данных супер-пользователя с логином admin. Его пароль мы не знаем, так что сразу зайти под ним не можем, но доступен хеш от пароля: ‘pbkdf2_sha256$36000$k36V24q60mNo$v5og9qcgc2sqkVwGjZDKNK+wcJy60ix8DIt9E8Yg48c=’. Подбор пароля не занимает много времени, так как он словарный и встречается во всех списках самых частых паролей.&lt;/p&gt;
&lt;p&gt;Попрактикуйтесь — сможете ли вы подобрать пароль? pbkdf2_sha256 — это тип хеша и подписи, а 36000 — количество итераций. Справиться с задачей поможет hashcat, john the ripper или любой другой подборщик прообразов хешей.&lt;/p&gt;
&lt;h2&gt;Незапланированная уязвимость&lt;/h2&gt;
&lt;p&gt;Последняя уязвимость нашлась в функции добавлении продукта в холодильник. Здесь не проверялось, что вы являетесь владельцем добавляемого продукта. Можно было создать супер-холодильник, содержащий все продукты с номерами от 1 до 1000, а затем попросить API выдать рецепты, содержащие хотя бы какой-нибудь продукт из этого холодильника. Конечно, он находил и чужие рецепты, а вместе с ними выдавал и флаги, хранящиеся в описаниях этих рецептов.&lt;/p&gt;
&lt;h2&gt;Выводы&lt;/h2&gt;
&lt;p&gt;Никогда не используйте csv.DictReader/csv.DictWriter вместе с файлами, открытыми модулем codecs. В новых питонах открывайте CSV-файлы с помощью стандартной функции open, передавая ей аргументы encoding и newline.&lt;/p&gt;
&lt;h2&gt;Бонус для дочитавших до конца&lt;/h2&gt;
&lt;p&gt;Все исходники сервиса, докер-файлы, чекер для проверяющей системы и мой авторский эксплойт для первой уязвимости можно найти в &lt;a href="https://github.com/HackerDom/phdctf-2017"&gt;репозитории разработки&lt;/a&gt;, который мы открыли сразу после окончания соревнования.&lt;/p&gt;
</description>
</item>


</channel>
</rss>