Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • Что такое псевдопротокол javascript:?
  • Где можно использовать псевдопротокол javascript:?
  • Почему это опасно?
  • Перенаправление страниц также несет в себе риски
  • Методы защиты
  • Практический пример
  • Заключение
  1. Глава 1 - Начало работы с XSS

Опасный псевдопротокол javascript

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

Поэтому стоит посвятить этому отдельную часть для подробного рассмотрения.

Прежде чем начать, давайте ответим на вопрос из прошлой части.

В точке внедрения innerHTML теги <script> не выполняются. Однако их можно использовать совместно с iframe.

Атрибут srcdoc iframe может содержать полный HTML-код, создавая новую веб-страницу. Таким образом, ранее бесполезный тег <script> можно использовать здесь. Кроме того, поскольку это атрибут, его содержимое можно предварительно закодировать, что означает, что результат будет таким же:

document.body.innerHTML =  '<iframe srcdoc="&lt;script>alert(1)&lt;/script>"></iframe>';

Таким образом, даже если точка внедрения - innerHTML, комбинация <iframe srcdoc> и <script> может использоваться для выполнения кода.

Что такое псевдопротокол javascript:?

Слово "псевдо" относится к псевдокоду, который похож на виртуальный код.

По сравнению с "реальными протоколами" типа HTTP, HTTPS или FTP, псевдопротокол больше похож на специальный протокол, не связанный с сетью. Примеры псевдопротоколов включают mailto: и tel:.

Псевдопротокол javascript: особенен тем, что его можно использовать для выполнения JavaScript-кода.

Где можно использовать псевдопротокол javascript:?

Первое место - атрибут href, упомянутый в предыдущей части

<a href="javascript:alert(1)">Link</a>

Простое нажатие на эту ссылку вызовет JavaScript и XSS срабатывает.

Второе место - атрибут src в <iframe>:

<iframe src="javascript:alert(1)"></iframe>

В отличие от примера с <a>, для его срабатывания не требуется никакого взаимодействия с пользователем.

Наконец, атрибут action в <form> также может содержать то же самое. Атрибут formaction в <button> также аналогичен, но оба, как и <a>, требуют нажатия для запуска:

<form action="javascript:alert(1)">
    <button type="submit">Submit</button>
</form>
<form id="f2" action="javascript:alert(2)">
    <button type="submit">Submit</button>
</form>
<button form="f2">Submit</button>

Почему это опасно?

Псевдопротокол javascript: часто упускают из виду, хотя на практике его используют довольно часто.

Например, imagine website с функцией вставки видео с YouTube по ссылке, которую вводит пользователь. Если разработчик этой функции не думает о безопасности, он может написать ее так:

<iframe src="<?= $youtube_url ?>" width="500" height="300"></iframe>

Правильный подход - проверить, соответствует ли URL формату видеоролика YouTube и убедиться, что он начинается с https://.

Думаете, такая функция встречается редко? Попробуйте ввести на странице Facebook ссылку на собственный ресурс. Такие функции встречаются чаще, чем кажется, верно?

Если реализация на бэкенде написана на коде, это может выглядеть так:

<a href="<?php echo htmlspecialchars($data) ?>">link</a>

Хотя символы <>" и закодированы, что предотвращает добавление тегов и выход из кавычек для добавления атрибутов, злоумышленник все равно может внедрить javascript:alert(1), поскольку в нем нет запрещенных символов.

Кроме того, современные фреймворки фронтенда обычно автоматически обрабатывают экранирование. Если вы не используете dangerouslySetInnerHTML в React или v-html в Vue, проблем быть не должно. Однако с href все по-другому по причинам, упомянутым выше - его содержимое не очищается.

Поэтому, если вы напишете в React так, это приведет к проблемам:

import React from "react";

export function App(props) {
    // Assume the following data comes from the user input
    const handleClick = (event) => {
        event.preventDefault(); // Предотвращаем переход по ссылке
        alert(1); // Выполняем нужное действие
    };
    return <a href="#" onClick={handleClick}>click me</a>;
}

Это уязвимость XSS, где выполнение кода всего в один клик.

Однако React ввел предупреждение об этом поведении в версии 16.9, и оно также задокументировано:

В сообщении о предупреждении говорится:

Будущая версия React заблокирует URL javascript: в качестве меры безопасности. Если возможно, используйте обработчики событий. Если вам нужно генерировать небезопасный HTML, попробуйте использовать dangerouslySetInnerHTML.

Будьте осторожны с псевдопротоколом javascript: и всегда думайте о потенциальных уязвимостях XSS при работе с пользовательским вводом. Используйте современные фреймворки и библиотеки, которые помогают безопасно обрабатывать ввод, и следуйте рекомендациям по написанию безопасного кода.

В этом разделе рассмотрим еще несколько аспектов опасности псевдопротокола javascript:

В Vue можно написать код, подобный этому:

<script setup>
import { ref } from 'vue';

const handleClick = (event) => {
    event.preventDefault(); // Предотвращаем переход по ссылке
    alert(1); // Выполняем нужное действие
};
</script>
<template>
  <a href="#" @click="handleClick">click me</a>
</template>

Перенаправление страниц также несет в себе риски

Многие сайты реализуют функцию "перенаправление после входа", которая перенаправляет пользователей на изначально запланированную страницу. Например:

const searchParams = new URLSearchParams(location.search);window.location = searchParams.get("redirect");

В чем проблема с этим кодом?

Значение window.location также может быть псевдопротоколом javascript:

window.location = "javascript:alert(document.domain)";

После выполнения приведенного выше кода вы увидите знакомое окно предупреждения. Это то, о чем должны знать фронтенд-разработчики.

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

Например, найденная уязвимость на сайте Matters News. Вот их страница входа:

После нажатия кнопки подтверждения вызывается функция redirectToTarget, код которой выглядит следующим образом:

/**
 * Redirect to "?target=" or fallback URL with page reload.
 * (works on CSR)
 */
export const redirectToTarget = ({
    fallback = "current",
}: {
    fallback?: "homepage" | "current",
} = {}) => {
    const fallbackTarget = fallback === "homepage" 
        ? `/` // FIXME: to purge cache
        : window.location.href;

    const target = getTarget() || fallbackTarget;
    window.location.href = decodeURIComponent(target);
};

После получения целевого URL, функция использует его напрямую для перенаправления: window.location.href = decodeURIComponent(target). А функция getTarget просто получает значение параметра target из строки запроса.

Таким образом, если URL входа - https://matters.news/login?target=javascript:alert(1), то при успешном входе у пользователя появится всплывающее окно, что является XSS-атакой!

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

Кража логина и пароля через XSS после их ввода и входа, а также перенаправление пользователя на главную страницу без следов может привести к взлому учетной записи и ее захвату.

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

Методы защиты

Вы можете обрабатывать URL самостоятельно, но давайте посмотрим, что обычно происходит, когда вы делаете это.

Поскольку вредоносная строка - javascript:alert(1), некоторые могут подумать, что достаточно проверить, начинается ли она с javascript: или убрать все вхождения javascript из строки.

Однако этот подход неэффективен, поскольку речь идет о содержимом атрибута href, а содержимое атрибута в HTML может быть закодировано. Другими словами, я могу сделать так:

<a href="&#106avascript&colon;alert(1)">click me</a>

Внутри нет содержимого, которое нам нужно фильтровать, и оно не начинается с javascript:, поэтому оно может обойти ограничение.

Лучший подход - разрешать только строки, начинающиеся с http:// или https://. Это обычно предотвращает любые проблемы. Некоторые более строгие методы включают использование JavaScript для разбора URL, например:

console.log(new URL("javascript:alert(1)"));
/*  
{    
    // ...
    href:"javascript:alert(1)",
    origin: "null",
    pathname: "alert(1)",    
    protocol: "javascript:",  
}
*/

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

Еще одна распространенная ошибка - использовать разбор URL на основе имени хоста или источника, например:

console.log(new URL("javascript:alert(1)"));
/*  
{    
    // ...    
    hostname: "",    
    host: "",    
    origin: null  
}
*/

Когда hostname или host пусты, это означает, что URL недействителен. Хотя на первый взгляд этот метод может показаться приемлемым, мы можем использовать функцию JavaScript, где // рассматривается как комментарий, в сочетании с символами новой строки, чтобы создать строку, которая выглядит как URL, но на самом деле является псевдопротоколом javascript::

console.log(new URL("javascript://example.com/%0aalert(1)"));

/*  
{    
    // ...    
    hostname: "",    
    host: "",    
    origin: null  
}
*/

Хотя это похоже на URL, он отлично работает в Chrome без каких-либо проблем или ложных срабатываний. Однако Safari ведет себя по-другому. При выполнении того же кода в Safari 16.3 вывод:

console.log(new URL("javascript://example.com/%0aalert(1)"));

/*  
{    
    // ...    
    hostname: "example.com",
    host: "example.com",
    origin: "null"  
}
*/

Если вы действительно хотите использовать регулярное выражение для проверки, является ли это псевдопротоколом javascript:, вы можете обратиться к реализации в исходном коде React (многие библиотеки используют аналогичное регулярное выражение):

// A javascript: URL can contain leading C0 control or \u0020 SPACE,// and any newline or tab are filtered out as if they're not part of the URL.// 
https://url.spec.whatwg.org/#url-parsing//
 Tab or newline are defined as \r\n\t:// 
https://infra.spec.whatwg.org/#ascii-tab-or-newline//
 A C0 control is a code point in the range \u0000 NULL to \u001F// INFORMATION SEPARATOR ONE, inclusive:// 
https://infra.spec.whatwg.org/#c0-control-or-space/*
 eslint-disable max-len */const isJavaScriptProtocol =  /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i;

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

Помимо упомянутых методов, простое добавление target="_blank" может иметь значительный эффект, поскольку многие браузеры уже устранили эту проблему.

В Chrome при нажатии на ссылку открывается новая вкладка с URL about:blank#blocked.

В Firefox открывается новая вкладка без URL.

В Safari ничего не происходит. Ни один из этих браузеров не выполняет JavaScript.

Тестируемые версии: Chrome 115, Firefox 116 и Safari 16.3.

В реальном мире большинство ссылок имеют атрибут target="_blank".

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

Практический пример

В Telegram Web (у Telegram есть несколько веб-версий) есть функция ensureProtocol, которая проверяет наличие :// в URL. Если его нет, она автоматически добавляет http://:

export function ensureProtocol(url?: string) {
    if (!url) {
        return undefined;
    }
    return url.includes("://") ? url : `http://${url}`;
}

Эту защиту легко обойти, используя что-то вроде javascript:alert('://'). Это позволяет успешно использовать псевдопротокол javascript:. Однако проблема в том, что сервер также проверяет, является ли URL действительным адресом, а предыдущая строка явно не является таковой.

URL может включать имя пользователя и пароль в начале (используется для HTTP-аутентификации), разделенные двоеточием (`:`), например:

https://username:password@www.example.com

Поэтому Slonser обнаружил, что эту строку можно использовать для обхода проверки:

javascript:alert@github.com/#://

Наконец, с помощью кодирования URL генерируется URL с паролем, который содержит только допустимые символы:

javascript:alert%28%27Slonser%20was%20here%21%27%29%3B%2F%2F@
github.com#;alert(10);://eow5kas78d0wlv0.m.pipedream.net%27//
 after decodedjavascript:alert('Slonser was here!');//@
github.com#;alert(10);://eow5kas78d0wlv0.m.pipedream.net'

Сервер распознает указанную выше строку как ссылку, и клиент может обойти проверку ://. Когда пользователь нажимает на эту ссылку, запускается XSS-атака.

export function ensureProtocol(url?: string) {
    if (!url) {
        return undefined;
    }
    // HTTP was chosen by default as a fix for
    // https://bugs.telegram.org/c/10712
    // It is also the default protocol in the official TDesktop client.
    try {
        const parsedUrl = new URL(url);
        
        // eslint-disable-next-line no-script-url
        if (parsedUrl.protocol === "javascript:") {
            return `http://${url}`;
        }
        
        return url;
    } catch (err) {
        return `http://${url}`;
    }
}

Заключение

В этой статье мы рассмотрели опасные аспекты псевдопротокола javascript:. Его можно поместить внутри атрибута href тега <a>, что является распространенным вариантом использования. Кроме того, разработчики часто забывают о потенциальных рисках или могут не знать о них, что приводит к уязвимостям. Хотя в большинстве случаев гиперссылки открываются на новых вкладках, предотвращая выполнение кода JavaScript, нет никакой гарантии, что поведение будет таким везде (например, когда целевой атрибут не указан) или при использовании старых браузеров или альтернативных методов для открытия новых вкладок. Это создает опасность для пользователей. Кроме того, при выполнении перенаправлений важно учитывать проблемы, связанные с псевдопротоколом javascript:. Без надлежащей обработки данных - это может привести к XSS-уязвимости. Разработчикам крайне важно постоянно знать об этих проблемах и соответствующим образом решать их в коде, как сказано в знаменитой цитате:

Никогда не доверяйте пользовательскому вводу.

PreviousБолее глубокое понимание XSSNextПервая линия обороны от XSS - Sanitization

Last updated 8 months ago

Если я введу как ссылку на YouTube javascript:alert(1), это станет уязвимостью XSS. Даже если добавить проверку на наличие в URL, ее можно обойти с помощью javascript:alert(1);console.log('').

Это легко упускаемая из виду область. Например уязвимость на платформе онлайн-курсов в :

Дополнительные обсуждения в GitHub: 1. 2.

Это также успешно выполнит JavaScript-код. Данный метод атаки упоминается в документации Vue под названием "" . Разработчикам рекомендуется выполнять валидацию и обработку URL на сервере, а не ждать этого от фронтенда.

Для обработки на фронтенде предлагается использовать библиотеку .

Во-первых, если у вас есть библиотеки, такие как упомянутая ранее , это будет идеальным решением. Хотя это может быть не полностью безопасно, библиотека широко используется и прошла обширные тестирования, поэтому многие проблемы и методы обхода, возможно, уже решены.

В Safari он успешно анализирует имя хоста и хост. Кстати, я узнал этот трюк из твита .

Для получения дополнительной информации вы можете обратиться к статьям: - -

Рассмотрим уязвимость, обнаруженную недавно (в июне 2023 года) в веб-версии Telegram. Она связана с псевдопротоколом javascript: и упоминается в статье

В этом случае javascript - это имя пользователя, alert - пароль, а хост - . Хотя он не начинается с http:// или https://, сервер по-прежнему считает его допустимым URL.

Позже Telegram исправил эту проблему, реализовав метод, о котором я упоминал ранее, который проверяет URL-адрес и гарантирует, что протокол не является javascript:. :

youtube.com
youtube.com
Тайване Hahow
Устаревание URL javascript:
React@16.9 block javascript:void(0); #16592
False-positive security precaution warning (javascript: URLs) #16382
Внедрение URL
sanitize-url
sanitize-url
Masato
The curious case of XSS and the mouse middle button
Anchor Tag XSS Exploitation in Firefox with Target="_blank"
Slonser История одной XSS в Telegram
github.com
Fix