Cross-Site Scripting [XSS]

1. Введение:

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

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

  • Stored XSS.

  • Reflected XSS.

  • DOM-Based XSS.

2.1. Stored Cross-Site Scripting:

Хранимые атаки - это атаки, в которых внедренный скрипт постоянно хранится на целевых серверах, например в базе данных, в форуме сообщений, журнале посетителей, поле для комментариев и т. д. Затем жертва получает вредоносный скрипт с сервера, когда запрашивает сохраненную информацию. Хранимый XSS также иногда называют Persistent или Type-I XSS.

2.1.1. Исключение символов HTML:

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

Символ
Кодирование сущности

&

&

'

'

<

&lt;

>

&gt;

Вот пример того, как может выглядеть экранирование и отображение полученной из базы данных информации:

<div class="message">
    <h1> Hello, this is the message:
        &lt;script&gt;alert(&quot;Hey&quot;)&lt;/script&gt;
    </h1>
</div>

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

Вот пример того, как ReactJS справляется с экранированием ответа:

const message = "<script>alert('Hey')</script>"

class UserProfilePage extends React.Component {
  render() {
    return (
        <div class="message">
          <h1> Hello, this is the message: {message}!</h1>
        </div>
    );
  }
}

2.1.2. Реализация политики безопасности содержимого [CSP]:

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

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

Это очень простая политика, которая ограничивает импортируемые сценарии страницы одним и тем же доменом (я) и указывает браузеру, что встроенный JavaScript НЕ должен выполняться.

Content-Security-Policy: script-src 'self' https://scripts.github.com

Вы также можете задать политику безопасности содержимого вашего сайта в теге <head> в HTML веб-страниц.

2.2. Отраженный межсайтовый скриптинг:

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

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

2.2.1. Избавление от динамического содержимого в HTTP-запросах:

2.3. Межсайтовый скриптинг на основе объектной модели документа [DOM]:

XSS-уязвимости на основе DOM обычно возникают, когда JavaScript получает данные из контролируемого злоумышленником источника, например URL, и передает их в поглотитель, поддерживающий динамическое выполнение кода, например eval() или innerHTML. Это позволяет злоумышленникам выполнить вредоносный JavaScript, который, как правило, позволяет им захватить учетные записи других пользователей.

Reflected и Stored XSS - это инъекции на стороне сервера, в то время как DOM-based XSS - это инъекции на стороне клиента (браузера). В случае Reflected/Stored атака внедряется в приложение во время обработки запросов на стороне сервера, когда недоверенный ввод динамически добавляется в HTML. Для DOM XSS атака внедряется в приложение во время выполнения непосредственно на клиенте.

2.3.1. Пример уязвимого кода:

Вот пример того, как может выглядеть уязвимая страница с использованием HTML5 и JavaScript:

function refreshItems() {
    
    const type = (new URL(location.href))
        .searchParams.get('filter')
        .replace('+', ' ');

    const activeItemLink = document.querySelector(`.itemlink[data-type=${type}]`);

    if(activeItemLink) {
        activeTab.classList.add('active');
    }

    // Search items
    const items = type ? data.filter(item => {
        return item.type === type;
    }) : data;

    // Show current type name
    document.getElementById('currentItemName').innerHTML = type; 
    // !!!! no input validation before appending the value to DOM
    // here we basically append the type value to the ODM by passing it 
    // to the innerHTML of the current item.
    
    // Display items
    let itemsHTML = '';

    // To render and update each active item,
    // it is extracted from the URL query parameter filter
    items.forEach(item => {
        itemsHTML == 
        `
            <div>
                <img src="${item.icon}">
                <p>${item.name}</p>
                <p>${item.description}</p>
                <p>${item.owner}</p>
            </div>
        `;
    });

    document.getElementById('list').innerHTML = itemsHTML;
}

document.addEventListener('DOMContentLoaded', () => {
    const itemLinks = document.getElementsByClassName('itemlink');

    itemLinks.foreach(link => {
        link.addEventListener('click', (event) => {
            location.search = `?filter=${event.target.innerText}`;
        });
    });

    refreshItems();
})

Как вы можете судить по этой строчке:

document.getElementById('currentItemName').innerHTML = type; 

Мы добавляем значение имени текущего элемента в DOM без какой-либо проверки ввода, и это уязвимо для DOM XSS-инъекции, так что если злоумышленник введет вредоносную инъекцию, листинг нашего элемента выполнит ее.

2.3.2. Смягчение:

Мы последуем примеру защитных техник Stored XSS и Reflected XSS в том смысле, что первым делом нам нужно избавиться от всех вводимых пользователем данных. Поскольку наш код - это обычный JavaScript, мы должны создать новую функцию, которая будет делать это за нас:

function escapeHTML(html) {
    return html
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt')
        .replace(/</g, '&quot;')
        .replace(/'/g, '&#39;');
}

Теперь нам нужно исключить то, что будет отображаться, а это наш запрос, введенный пользователем. В качестве дополнительного уровня безопасности мы можем использовать textContext вместо innerHTML, поскольку мы не хотим изменять сам HTML-узел. Кроме того, textContext также экранирует символы HTML-разметки, поэтому мы получаем их в виде escaped, что предотвращает выполнение вредоносного HTML.

document.getElementById('currentItemName').textContext = escapeHTML(type); 

Last updated