Как искать и исправлять ошибки в коде

Представляем перевод статьи «How to fix bugs, step by step», опубликованный сайтом tproger. ru.

ошибки в коде

Искать ошибки в программах — непростая задача. Здесь нет никаких готовых методик или рецептов успеха. Можно даже сказать, что это — искусство. Тем не менее есть общие советы, которые помогут вам при поиске. В статье описаны основные шаги, которые стоит предпринять, если ваша программа работает некорректно.

Шаг 1: Занесите ошибку в трекер

После выполнения всех описанных ниже шагов может так случиться, что вы будете рвать на себе волосы от безысходности, все еще сидя на работе, когда поймете, что:

Трекер поможет вам не потерять нить размышлений и о текущей проблеме, и о той, которую вы временно отложили. А если вы работаете в команде, это поможет делегировать исправление коллеге и держать все обсуждение в одном месте.

Вы должны записать в трекер следующую информацию:

Это должно подсказать, как воспроизвести ошибку. Если вы не сможете воспроизвести ее в любое время, ваши шансы исправить ошибку стремятся к нулю.

Шаг 2: Поищите сообщение об ошибке в сети

Если у вас есть сообщение об ошибке, то вам повезло. Или оно будет достаточно информативным, чтобы вы поняли, где и в чем заключается ошибка, или у вас будет готовый запрос для поиска в сети. Не повезло? Тогда переходите к следующему шагу.

Шаг 3: Найдите строку, в которой проявляется ошибка

Если ошибка вызывает падение программы, попробуйте запустить её в IDE под отладчиком и посмотрите, на какой строчке кода она остановится. Совершенно необязательно, что ошибка будет именно в этой строке (см. следующий шаг), но, по крайней мере, это может дать вам информацию о природе бага.

Шаг 4: Найдите точную строку, в которой появилась ошибка

Как только вы найдете строку, в которой проявляется ошибка, вы можете пройти назад по коду, чтобы найти, где она содержится. Иногда это может быть одна и та же строка. Но чаще всего вы обнаружите, что строка, на которой упала программа, ни при чем, а причина ошибки — в неправильных данных, которые появились ранее.

Если вы отслеживаете выполнение программы в отладчике, то вы можете пройтись назад по стектрейсу, чтобы найти ошибку. Если вы находитесь внутри функции, вызванной внутри другой функции, вызванной внутри другой функции, то стектрейс покажет список функций до самой точки входа в программу (функции main() ). Если ошибка случилась где-то в подключаемой библиотеке, предположите, что ошибка все-таки в вашей программе — это случается гораздо чаще. Найдите по стектрейсу, откуда в вашем коде вызывается библиотечная функция, и продолжайте искать.

Шаг 5: Выясните природу ошибки

Ошибки могут проявлять себя по-разному, но большинство из них можно отнести к той или иной категории. Вот наиболее частые.

Если ваша ошибка не похожа на описанные выше, или вы не можете найти строку, в которой она появилась, переходите к следующему шагу.

Шаг 6: Метод исключения

Если вы не можете найти строку с ошибкой, попробуйте или отключать (комментировать) блоки кода до тех пор, пока ошибка не пропадет, или, используя фреймворк для юнит-тестов, изолируйте отдельные методы и вызывайте их с теми же параметрами, что и в реальном коде.

Попробуйте отключать компоненты системы один за другим, пока не найдете минимальную конфигурацию, которая будет работать. Затем подключайте их обратно по одному, пока ошибка не вернется. Таким образом вы вернетесь на шаг 3.

Шаг 7: Логгируйте все подряд и анализируйте журнал

Пройдитесь по каждому модулю или компоненту и добавьте больше сообщений. Начинайте постепенно, по одному модулю. Анализируйте лог до тех пор, пока не проявится неисправность. Если этого не случилось, добавьте еще сообщений.

Ваша задача состоит в том, чтобы вернуться к шагу 3, обнаружив, где проявляется ошибка. Также это именно тот случай, когда стоит использовать сторонние библиотеки для более тщательного логгирования.

Шаг 8: Исключите влияние железа или платформы

Замените оперативную память, жесткие диски, поменяйте сервер или рабочую станцию. Установите обновления, удалите обновления. Если ошибка пропадет, то причиной было железо, ОС или среда. Вы можете по желанию попробовать этот шаг раньше, так как неполадки в железе часто маскируют ошибки в ПО.

Если ваша программа работает по сети, проверьте свитч, замените кабель или запустите программу в другой сети.

Ради интереса, переключите кабель питания в другую розетку или к другому ИБП. Безумно? Почему бы не попробовать?

Если у вас возникает одна и та же ошибка вне зависимости от среды, то она в вашем коде.

Шаг 9: Обратите внимание на совпадения

Шаг 10: Обратитесь в техподдержку

Наконец, пора попросить помощи у того, кто знает больше, чем вы. Для этого у вас должно быть хотя бы примерное понимание того, где находится ошибка — в железе, базе данных, компиляторе. Прежде чем писать письмо разработчикам, попробуйте задать вопрос на профильном форуме.

Ошибки есть в операционных системах, компиляторах, фреймворках и библиотеках, и ваша программа может быть действительно корректна. Но шансы привлечь внимание разработчика к этим ошибкам невелики, если вы не сможете предоставить подробный алгоритм их воспроизведения. Дружелюбный разработчик может помочь вам в этом, но чаще всего, если проблему сложно воспроизвести вас просто проигнорируют. К сожалению, это значит, что нужно приложить больше усилий при составлении багрепорта.

Полезные советы (когда ничего не помогает)

Что вам точно не поможет

Ошибка, которую я недавно исправил

Роковые ошибки. Как искать логические уязвимости в веб-приложениях

Эта статья — кон­спект вебина­ра по веб‑уяз­вимос­тям, который я про­водил для читате­лей «Хакера». В кон­це будет при­веде­на его запись, где ты смо­жешь уви­деть то, что не попало в тек­сто­вую вер­сию. Если тебе инте­рес­ны такие темы и ты бы хотел разоб­рать­ся в них глуб­же, за­писы­вай­ся на мой курс по безопас­ности веб‑при­ложе­ний!

Сра­зу пре­дуп­режу, что боль­шинс­тво задач, которые мы сегод­ня раз­берем, будут на язы­ке PHP. Оно нес­прос­та — подав­ляющее боль­шинс­тво сай­тов и сер­висов в интерне­те написа­ны имен­но на нем. Так что, нес­мотря на под­порчен­ную репута­цию это­го язы­ка, тебе при­дет­ся раз­бирать­ся в нем, что­бы хакерс­тво­вать серь­езно. Сей­час же, в рам­ках это­го занятия, зна­ние PHP не явля­ется необ­ходимостью, но, конеч­но, будет очень серь­езным под­спорь­ем.

Впро­чем, боль­шинс­тво уяз­вимос­тей не при­вяза­ны к кон­крет­ному язы­ку или сте­ку тех­нологий, так что, узнав их на при­мере PHP, ты лег­ко смо­жешь экс­плу­ати­ровать подоб­ные баги и в ASP. NET, и в каком‑нибудь Node. JS.

А еще пре­дуп­режу, что задач­ки, которые мы сегод­ня раз­берем, не сов­сем началь­ного уров­ня и сов­сем уж «вален­кам» тут делать нечего — сна­чала сто­ит почитать мат­часть и хоть нем­ного пред­став­лять, с чем хочешь иметь дело. Если же ты можешь отли­чить HTTP от XML и у тебя не воз­ника­ет воп­росов вида «а что за дол­лары в коде?», то доб­ро пожало­вать!

warning

Ни автор кур­са, ни редак­ция «Хакера» не несут ответс­твен­ности за твои дей­ствия. При­мене­ние матери­алов этой статьи про­тив любой сис­темы без раз­решения ее вла­дель­ца прес­леду­ется по закону.

Се­год­ня мы раз­берем нес­коль­ко задач, которые я решал сам в рам­ках тре­ниров­ки. Воз­можно, они покажут­ся тебе слож­ными, но не пугай­ся — всег­да есть воз­можность отто­чить свои навыки на сай­тах пра­витель­ств спе­циали­зиро­ван­ных сай­тах для хакеров. Я сей­час говорю о HackTheBox и Root-me, которы­ми поль­зуюсь сам и вся­чес­ки советую дру­гим. Две из сегод­няшних задач взя­ты имен­но отту­да.

Задача 1

Сна­чала я при­веду код, с которым мы сей­час будем работать.

По сути, тут все­го три стро­ки кода. Казалось бы, где тут может зак­расть­ся уяз­вимость?

Что­бы это понять, давай раз­берем алго­ритм, который здесь реали­зован. Вооб­ще, при ауди­те кода сто­ит уметь читать его пос­троч­но. Тог­да про­ще понять, что имен­но может пой­ти не так.

В кон­це очи­щен­ное имя фай­ла под­став­ляет­ся в путь и заг­ружа­ется файл с этим име­нем. Ничего пло­хого.

Итак, что может пой­ти не по пла­ну?

Как ты уже, конеч­но, догадал­ся — проб­лема в фун­кции очис­тки вво­да (которая preg_replace ). Давай обра­тим­ся к пер­вой попав­шей­ся шпар­галке по регуляр­ным выраже­ниям.

ШпаргалкаШпар­галка

Тут пря­мо написан ответ, как обой­ти защиту (под­сказ­ка: ищи спра­ва).

Ви­дишь точ­ку? А шапоч­ку ( ^ )? Та стро­ка чита­ется как «если в начале стро­ки находит­ся любое количес­тво любых сим­волов, кро­ме перено­са стро­ки, и это закан­чива­ется сле­шем, уда­лить соот­ветс­тву­ющую часть стро­ки».

Клю­чевое тут «кро­ме перено­са стро­ки». Если в начале стро­ки будет перенос стро­ки — регуляр­ка не отра­бота­ет и вве­ден­ная стро­ка попадет в include( ) без филь­тра­ции.

РезультатРе­зуль­тат

Задача 2

Это за­дач­ка с root-me, где ты, воз­можно, уже видел ее. Но мы все рав­но рас­смот­рим ее под­робнее — она отно­сит­ся к реалис­тичным, и шан­сы встре­тить что‑то подоб­ное в жиз­ни немалень­кие.

В задании нам дает­ся прос­той фай­лооб­менник и про­сят получить дос­туп к панели адми­на.

Интерфейс файлообменникаИн­терфейс фай­лооб­менни­ка

Ин­терфейс край­не прост: есть кноп­ка заг­рузки фай­ла на сер­вер и прос­мотр заг­ружен­ных фай­лов по пря­мым ссыл­кам. Забегая впе­ред, ска­жу, что гру­зить скрип­ты на PHP, bash и про­чие — бес­полез­но, про­вер­ки реали­зова­ны вер­но и ошиб­ка в дру­гом мес­те.

Об­рати вни­мание на ниж­нюю часть стра­ницы, а точ­нее — на фра­зу «frequent backups: this opensource script is launched every 5 minutes for saving your files». И при­веде­на ссыл­ка на скрипт, вызыва­емый каж­дые пять минут в сис­теме.

Да­вай гля­нем на него прис­таль­нее:

Ка­залось бы — что тут такого? На парамет­ры ты вли­ять не можешь, а ман­тру при­зыва tar вооб­ще зна­ешь как свои пять паль­цев. А проб­лема в самой ман­тре: тут она написа­на не пол­ностью. Точ­нее, не в том виде, как ее уви­дит сам tar.

Что дела­ет звез­дочка? Вмес­то нее bash под­ста­вит име­на всех фай­лов в текущей пап­ке. Вро­де ничего кри­миналь­ного.

А давай обра­тим­ся к ма­нуалу на Tar, который нам любез­но пре­дос­тавлен вмес­те с усло­вием задачи.

Интересности в TarИн­терес­ности в Tar

Те­перь вспом­ним про звез­дочку: вмес­то нее шелл (bash) под­ста­вит спи­сок всех фай­лов в текущей пап­ке, при этом они могут иметь любые име­на. В том чис­ле такие, которые будут вос­при­няты архи­вато­ром как спе­циаль­ные парамет­ры.

Прос­то заголо­вок и коман­да копиро­вания админ­ской панели в текущую пап­ку. Естес­твен­но, тут мог быть реверс‑шелл или еще что‑то, но для решения кон­крет­но этой задачи такая «тяжелая артилле­рия» не нуж­на.

Те­перь дожида­емся выпол­нения нашего шел­ла — и уви­дим в окне фай­лооб­менни­ка файл админ‑панели в виде прос­того тек­ста. Оста­лось толь­ко открыть его и най­ти там пароль!

Пароль в чистом видеПа­роль в чис­том виде

Задача 3

Тут у нас пла­гин для WordPress, который поз­воля­ет запись аудио и видео.

Я не буду про­сить тебя най­ти уяз­вимость, а сра­зу покажу ее.

Уязвимое местоУяз­вимое мес­то

Как вид­но из строк 247–251 на скрин­шоте, не пре­дус­мотре­но никаких про­верок на тип или содер­жимое фай­ла — это прос­то клас­сичес­кая заг­рузка!

Есть, прав­да, огра­ниче­ние: файл гру­зит­ся в стан­дар­тную дирек­торию WordPress ( / wordpress/ wp-content/ uploads/< YEAR>/ < MONTH>). Это зна­чит, что лис­тинг содер­жимого нам по умол­чанию недос­тупен. А в стро­ке 247 генери­рует­ся слу­чай­ный иден­тифика­тор, который под­став­ляет­ся в начало име­ни фай­ла, то есть обра­тить­ся к / wordpress/ wp-content/ uploads/ 2021/ 01/ shell. php уже не вый­дет. Непоря­док!

По­луча­ет уни­каль­ный иден­тифика­тор с пре­фик­сом, осно­ван­ный на текущем вре­мени в мик­росекун­дах.

<…>

Вни­мание. Эта фун­кция не гаран­тиру­ет получе­ния уни­каль­ного зна­чения. Боль­шинс­тво опе­раци­онных сис­тем син­хро­низи­рует вре­мя с NTP либо его ана­лога­ми, так что сис­темное вре­мя пос­тоян­но меня­ется. Сле­дова­тель­но, воз­можна ситу­ация, ког­да эта фун­кция вер­нет неуни­каль­ный иден­тифика­тор для про­цес­са/потока. <…>

Так как PHP — про­ект откры­тый, мы можем под­смот­реть исходни­ки фун­кций стан­дар­тной биб­лиоте­ки. Откры­ваем исходник uniqid( ) на GitHub, перехо­дим к стро­ке 76 и наб­люда­ем сле­дующее:

Что тут про­исхо­дит? А то, что воз­вра­щаемое зна­чение зависит исклю­читель­но от текуще­го вре­мени, которое в рам­ках одной пла­неты впол­не пред­ска­зуемо.

Хоть выход­ная пос­ледова­тель­ность и выг­лядит слу­чай­ной, она таковой не явля­ется. Что­бы не быть голос­ловным, вот при­мер име­ни фай­ла, сге­нери­рован­ного таким алго­рит­мом:

По­лучен­ное зна­чение лег­ко мож­но кон­верти­ровать обратно в дату и вре­мя его генера­ции:

Ко­неч­но, бру­тить все 13 сим­волов — вши заедят, но у нас есть спо­соб получ­ше: мы можем проб­рутить вари­анты на осно­ве вре­мени заг­рузки плюс‑минус пол­секун­ды, что­бы нивели­ровать раз­бежки часов на кли­енте и сер­вере. А мож­но прос­то поверить, что часы у обо­их хос­тов точ­ные, а зна­чит, мож­но про­верить не мил­лион вари­антов (1 секун­ду), а толь­ко вари­анты, воз­можные меж­ду вре­менем отправ­ки зап­роса и вре­менем получе­ния отве­та. На шус­тром канале это будет поряд­ка 300–700 мс, что не так и мно­го.

Ко­неч­но, не все реаль­ные кей­сы тре­буют глу­боких поз­наний в PHP или дру­гом сер­верном язы­ке. Мно­гие ошиб­ки мож­но най­ти, даже не откры­вая код — с помощью авто­мати­чес­ких ска­неров. Под­робнее о них — в на­шей статье об авто­мати­чес­ком взло­ме. Они здо­рово помога­ют, так что не грех иметь пароч­ку под рукой для экс­пресс‑ана­лиза!

Я наб­росал прос­той скрипт на Python для демонс­тра­ции такой воз­можнос­ти. Его код пред­став­лен ниже:

Нам нуж­но запус­тить его нес­коль­ко раз, что­бы подоб­рать минималь­ное вре­мя меж­ду отправ­кой зап­роса и получе­нием отве­та — это поз­волит умень­шить вре­мя перебо­ра.

Так­же нуж­но пом­нить, что раз­бежки все же могут быть, и чис­то на вся­кий слу­чай сто­ит про­верить, нас­коль­ко локаль­ное вре­мя соот­ветс­тву­ет вре­мени на сер­вере. Час­тень­ко оно воз­вра­щает­ся сер­вером в заголов­ке Last-Modified и поз­воля­ет понять, какую величи­ну кор­рекции внес­ти в свои рас­четы.

Как бы еще опти­мизи­ровать перебор?

Ну, во‑пер­вых, питон сам по себе очень мед­ленный и, конеч­но, не смог бы выпол­нить соеди­нение, переда­чу заголов­ков, отправ­ку фай­ла и про­чие мел­кие нак­ладные рас­ходы в тот же момент. А интер­пре­татор PHP на сто­роне сер­вера едва ли момен­таль­но про­верит пра­ва, запус­тит скрипт, отра­бота­ет слу­жеб­ные фун­кции и дой­дет до собс­твен­но уяз­вимого мес­та. Тут мож­но накинуть эдак тысяч сто мик­росекунд без малей­ших потерь.

Во‑вто­рых, выпол­нение uniqid( ) оче­вид­но про­исхо­дит не в самом кон­це фун­кции. Еще нуж­но вре­мя на обра­бот­ку заг­ружен­ного фай­ла, запись отве­та (заголов­ков), отправ­ку это­го все­го по сети и на обра­бот­ку отве­та интер­пре­тато­ром Python. Тут тоже мож­но поряд­ка 100 000 мик­росекунд вычесть.

Вот так на ров­ном мес­те мы сок­ратили перебор на 200 000 зап­росов. Мно­го это или мало? В моем слу­чае это сок­ратило количес­тво зап­росов еще при­мер­но на треть.

Ос­талось поряд­ка 500 000 вари­антов, которые мож­но переб­рать в пре­делах часа или даже мень­ше — у меня это заняло минут 15.

Те­перь давай напишем еще один скрипт, который и будет искать наш шелл с исполь­зовани­ем это­го алго­рит­ма:

Вот и всё: запус­каешь, через некото­рое вре­мя получа­ешь путь, и хост зах­вачен!

На­вер­няка у тебя воз­ник воп­рос, нель­зя ли как‑то еще усо­вер­шенс­тво­вать этот перебор, потому что 500 тысяч вари­антов — это все рав­но как‑то мно­гова­то? Мож­но, но такого зна­чимо­го уско­рения, как рань­ше, уже не будет. Суть в том, что мож­но идти не от начала про­межут­ка вре­мени к кон­цу, а от середи­ны к кра­ям. По опы­ту, это работа­ет нес­коль­ко быс­трее.

Другой способ

Задача 4

Пос­ледняя на сегод­ня задач­ка — тоже с root-me и тоже из катего­рии реалис­тичных, но замет­но пос­ложнее. Сер­вис Web TV — новей­шая фран­цуз­ская раз­работ­ка в сфе­ре интернет‑телеви­дения. Но нас инте­ресу­ет не новая дешевая тра­гедия, а админка.

Главная страница Web TV. Простите за мой французскийГлав­ная стра­ница Web TV. Прос­тите за мой фран­цуз­ский

Толь­ко — вот незада­ча — Gobuster никаких приз­наков админки не обна­ружил. При­дет­ся изу­чать, что нам дос­тупно. А дос­тупен логин (там фор­ма авто­риза­ции) и ссыл­ка на нерабо­тающий эфир.

Поп­робу­ем залоги­нить­ся и перех­ватить зап­рос на авто­риза­цию с помощью Burp.

Буква З в слове «реальность» означает «защищенность»Бук­ва З в сло­ве «реаль­ность» озна­чает «защищен­ность»

Зап­рос отправ­ляем в Repeater (пов­торитель). Пусть пока там полежит.

Взгля­нем еще разок на фор­му логина. Какие мыс­ли тебя посеща­ют, ког­да ты видишь фор­му для авто­риза­ции? Конеч­но, SQL-инъ­екция! А давай ткнем туда кавыч­ку. Написа­ли. Отправ­ляем. Хм, ничего не поменя­лось. А как вооб­ще узнать, что что‑то поменя­лось? Смот­ри на заголо­вок Content-Length в отве­те: в нашем слу­чае там при­ходит ров­но 2079 байт, если инъ­екции не было, и, оче­вид­но, при­дет силь­но дру­гой резуль­тат в про­тив­ном слу­чае. Я поп­робовал еще нем­ного, и инъ­екция так прос­то не выяви­лась, так что давай поищем в дру­гом мес­те, а потом вер­немся к это­му зап­росу.

Я поп­робовал перей­ти на стра­ницу / page_index и получил ошиб­ку как на скри­не ниже.

Ошибка интерпретатораОшиб­ка интер­пре­тато­ра

/?action=../../index. php/?action=../../index. php

Вид­но не все, но если открыть ответ в Burp или даже прос­то прос­мотреть код стра­ницы бра­узе­ром — откры­вает­ся пол­ный исходник. Вот тебе и directory traversal налицо.

Результат обхода каталогаРе­зуль­тат обхо­да катало­га

Пом­нишь, мы не мог­ли най­ти путь к админке? А на скрин­шоте он есть: имен­но на него будет редирект, ког­да скрипт про­верит логин и пароль.

Да­вай, не отхо­дя от кас­сы, сра­зу и его про­чита­ем — вдруг там что‑нибудь инте­рес­ное есть.

Источники:

https://techrocks. ru/2018/03/29/how-to-find-and-fix-bugs/

https://xakep. ru/2021/01/12/websec-errors/

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: