Серия веб-безопасности (4): XSS Defense

внешний интерфейс Безопасность XSS Открытый исходный код


Введение

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

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

HttpOnly

HttpOnlyЭто было впервые было предложено Microsoft иIE 6Постепенно он стал стандартом, и все основные браузеры поддерживают этот стандарт. В частности, еслиCookieс участиемHttpOnlyсвойства, то этоCookieбудет отключен для чтения, т.е.JavaScriptНе могу прочитать эту статьюCookie, но при взаимодействии с серверомHttp Requestэто все еще будет в сумкеCookieИнформация, то есть наши обычные взаимодействия, не затрагивается.

Cookieчерезhttp response headerЗакинул в браузер, заглянем в настройкиCookieсинтаксис:

Set-Cookie: <name>=<value>[; <Max-Age>=<age>][; expires=<date>][; domain=<domain_name>][; path=<some_path>][; secure][; HttpOnly]

первыйname=valueПара ключ-значение, а затем некоторые свойства, такие как срок действия, рольdomainа такжеpathНаконец, есть два флага, которые можно установить.secureа такжеHttpOnly.

Каштан:

// 利用 express 这个轮子设置cookie
res.cookie('myCookie', 'test', {
  httpOnly: true
})
res.cookie('myCookie2', 'test', {
  httpOnly: false
})

Затем вернитесь в браузер, чтобы просмотреть:

image-20180926162748454

В это время пытаемся вывести в консоль:

image-20180926163041071

Мы обнаружили, что только без установкиHttpOnlyизmyCookie2выход, так что,javascriptне могу это прочитатьCookieИнформация.

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

но,HttpOnlyНе все, добавилHttpOnlyне значит решитьXSSвопрос.

Строго говоря,HttpOnlyне для конфронтацииXSS,HttpOnlyРешениеXSSПослеCookieПроблемы с угоном, ноXSSАтака принесла болееCookieПроблемы со взломом, а также кража информации о пользователях, выдача себя за вход в систему, управление учетными записями пользователей и ряд действий.

использоватьHttpOnlyпомочь облегчитьXSSатака, но нужны и другие решенияXSSУязвимая схема.

входная проверка

Следует помнить одну вещь: не доверяйте никаким данным.

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

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

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

Есть много открытых источниковXSS Filter,ЭтиXSS FilterВ настоящее время все еще есть некоторые эффекты, вы можете протестировать только входной контент, а расширенные будут совпадать.XSSхарактеристики, например, содержит ли контент<script>,javascriptи другие чувствительные персонажи, но этиXSS FilterОн просто получает ввод пользователя, но не понимает его контекстного значения и часто ошибочно фильтруется.

Например:

Пользователь входит в псевдоним:<|无敌是多么鸡毛|>,дляXSS FilterСказать,<>Это специальный символ, который необходимо отфильтровать, а затем отфильтровать, чтобы он стал|无敌是多么鸡毛|, который напрямую изменяет псевдоним пользователя.

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

выходная проверка

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

HTMLEncode

противHTMLКод закодирован какHTMLEncode, его роль заключается в преобразовании строки вHTMLEntities.

В настоящее время для борьбыXSS, необходимы следующие побеги:

Специальные символы код объекта
& &amp ;
< &lt ;
> &gt ;
" &quot ;
' &#x27 ;
/ &#x2F ;

PS. ;Он необходим, и он должен быть связан с предыдущими знаками, я его отделяю, потому что,markdownто естьHTMLязык, я сразу экранировал специальные символы перед ним, когда подключал его, /(ㄒoㄒ)/~~

Посмотрим на эффект:

image-20180926213835914

image-20180926213904413

Как видите, эти кодыHTMLВышеуказанное было успешно преобразовано в соответствующий символ.

Разумеется, вышеперечисленное лишь самое основное и необходимое,HTMLEncodeЕсть еще много, много других, я перечислил некоторые здесь (пожалуйста, позвольте мне написать их в коде, чтобы они не были экранированы):

const HtmlEncode = (str) => {
    // 设置 16 进制编码,方便拼接
    const hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
    // 赋值需要转换的HTML
    const preescape = str;
    let escaped = "";
    for (let i = 0; i < preescape.length; i++) {
        // 获取每个位置上的字符
        let p = preescape.charAt(i);
        // 重新编码组装
        escaped = escaped + escapeCharx(p);
    }

    return escaped;
    // HTMLEncode 主要函数
    // original 为每次循环出来的字符
    function escapeCharx(original) {
        // 默认查到这个字符编码
        let found = true;
        // charCodeAt 获取 16 进制字符编码
        const thechar = original.charCodeAt(0);
        switch (thechar) {
            case 10: return "<br/>"; break; // 新的一行
            case 32: return "&nbsp;"; break; // space
            case 34: return "&quot;"; break; // "
            case 38: return "&amp;"; break; // &
            case 39: return "&#x27;"; break; // '
            case 47: return "&#x2F;"; break; // /
            case 60: return "&lt;"; break; // <
            case 62: return "&gt;"; break; // >
            case 198: return "&AElig;"; break; // Æ
            case 193: return "&Aacute;"; break; // Á
            case 194: return "&Acirc;"; break; // Â
            case 192: return "&Agrave;"; break; // À
            case 197: return "&Aring;"; break; // Å
            case 195: return "&Atilde;"; break; // Ã
            case 196: return "&Auml;"; break; // Ä
            case 199: return "&Ccedil;"; break; // Ç
            case 208: return "&ETH;"; break; // Ð
            case 201: return "&Eacute;"; break; // É
            case 202: return "&Ecirc;"; break;
            case 200: return "&Egrave;"; break;
            case 203: return "&Euml;"; break;
            case 205: return "&Iacute;"; break;
            case 206: return "&Icirc;"; break;
            case 204: return "&Igrave;"; break;
            case 207: return "&Iuml;"; break;
            case 209: return "&Ntilde;"; break;
            case 211: return "&Oacute;"; break;
            case 212: return "&Ocirc;"; break;
            case 210: return "&Ograve;"; break;
            case 216: return "&Oslash;"; break;
            case 213: return "&Otilde;"; break;
            case 214: return "&Ouml;"; break;
            case 222: return "&THORN;"; break;
            case 218: return "&Uacute;"; break;
            case 219: return "&Ucirc;"; break;
            case 217: return "&Ugrave;"; break;
            case 220: return "&Uuml;"; break;
            case 221: return "&Yacute;"; break;
            case 225: return "&aacute;"; break;
            case 226: return "&acirc;"; break;
            case 230: return "&aelig;"; break;
            case 224: return "&agrave;"; break;
            case 229: return "&aring;"; break;
            case 227: return "&atilde;"; break;
            case 228: return "&auml;"; break;
            case 231: return "&ccedil;"; break;
            case 233: return "&eacute;"; break;
            case 234: return "&ecirc;"; break;
            case 232: return "&egrave;"; break;
            case 240: return "&eth;"; break;
            case 235: return "&euml;"; break;
            case 237: return "&iacute;"; break;
            case 238: return "&icirc;"; break;
            case 236: return "&igrave;"; break;
            case 239: return "&iuml;"; break;
            case 241: return "&ntilde;"; break;
            case 243: return "&oacute;"; break;
            case 244: return "&ocirc;"; break;
            case 242: return "&ograve;"; break;
            case 248: return "&oslash;"; break;
            case 245: return "&otilde;"; break;
            case 246: return "&ouml;"; break;
            case 223: return "&szlig;"; break;
            case 254: return "&thorn;"; break;
            case 250: return "&uacute;"; break;
            case 251: return "&ucirc;"; break;
            case 249: return "&ugrave;"; break;
            case 252: return "&uuml;"; break;
            case 253: return "&yacute;"; break;
            case 255: return "&yuml;"; break;
            case 162: return "&cent;"; break;
            case '\r': break;
            default: found = false; break;
        }
        if (!found) {
            // 如果和上面内容不匹配且字符编码大于127的话,用unicode(非常严格模式)
            if (thechar > 127) {
                let c = thechar;
                let a4 = c % 16;
                c = Math.floor(c / 16);
                let a3 = c % 16;
                c = Math.floor(c / 16);
                let a2 = c % 16;
                c = Math.floor(c / 16);
                let a1 = c % 16;
                return "&#x" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + ";";
            } else {
                return original;
            }
        }
    }
}

Эмммм... Автор ленивый, а остальные заметки добавляются сами собой, что должно быть относительно полнымHTMLEncodeКодировка была преобразована, вы можете использовать ее напрямую (можете поставить палец вверх~), давайте проверим ее:

<div id="id"></div>
// 当我们输入:
document.querySelector('#id').innerHTML = '<img onerror=alert(1) src=1/>'

Страница неизбежно происходитXSSвпрыск:

image-20180926224132524

// 当我们利用 HTMLEncode 之后
document.querySelector('#id').innerHTML = HtmlEncode('<img onerror=alert(1) src=1/>')
console.log(HtmlEncode('<img onerror=alert(1) src=1/>'))

Страница обнаружения полностью отображает введенный контент:

image-20180926224340611

JavaScriptEncode

JavaScriptEncodeа такжеHTMLEncodeкодируется по-другому, он должен использовать\Экранирование специальных символов.

в противостоянииXSS, также требуется, чтобы выходные переменные были заключены в кавычки, чтобы избежать проблем с безопасностью, но у многих разработчиков нет такой привычки, поэтому они могут использовать только более строгиеJavaScriptEncodeДля обеспечения безопасности данных: все символы, кроме цифр, символов и символов меньше 127, кодируются в шестнадцатеричном формате.\xHHКодирован, с больше, чем Unicode (очень строгий режим).

То же самое показано в коде:

//使用“\”对特殊字符进行转义,除数字字母之外,小于127使用16进制“\xHH”的方式进行编码,大于用unicode(非常严格模式)。
// 大部分代码和上面一样,我就不写注释了
const JavaScriptEncode = function (str) {
    const hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
    const preescape = str;
    let escaped = "";
    for (let i = 0; i < preescape.length; i++) {
        escaped = escaped + encodeCharx(preescape.charAt(i));
    }
    return escaped;
    // 小于127转换成十六进制
    function changeTo16Hex(charCode) {
        return "\\x" + charCode.charCodeAt(0).toString(16);
    }
    function encodeCharx(original) {
        let found = true;
        const thecharchar = original.charAt(0);
        const thechar = original.charCodeAt(0);
        switch (thecharchar) {
            case '\n': return "\\n"; break; //newline
            case '\r': return "\\r"; break; //Carriage return
            case '\'': return "\\'"; break;
            case '"': return "\\\""; break;
            case '\&': return "\\&"; break;
            case '\\': return "\\\\"; break;
            case '\t': return "\\t"; break;
            case '\b': return "\\b"; break;
            case '\f': return "\\f"; break;
            case '/': return "\\x2F"; break;
            case '<': return "\\x3C"; break;
            case '>': return "\\x3E"; break;
            default: found = false; break;
        }
        if (!found) {
            if (thechar > 47 && thechar < 58) { //数字
                return original;
            }
            if (thechar > 64 && thechar < 91) { //大写字母
                return original;
            }
            if (thechar > 96 && thechar < 123) { //小写字母
                return original;
            }
            if (thechar > 127) { //大于127用unicode
                let c = thechar;
                let a4 = c % 16;
                c = Math.floor(c / 16);
                let a3 = c % 16;
                c = Math.floor(c / 16);
                let a2 = c % 16;
                c = Math.floor(c / 16);
                let a1 = c % 16;
                return "\\u" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + "";
            } else {
                return changeTo16Hex(original);
            }
        }
    }
}

КромеHTMLEncodeа такжеJavaScriptКроме того, существует множество функций кодирования для различных ситуаций, таких какXMLEncode,JSONEncodeЖдать.

Функция кодирования должна использовать соответствующую функцию в соответствующей ситуации.Следует отметить, что длина данных изменяется после кодирования.Если файл имеет ограничения на длину данных, некоторые функции могут быть затронуты. Когда мы используем функцию кодирования, мы должны обратить внимание на эту деталь, чтобы избежать ненужногоbug.

правильная защитаXSS

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

XSSСуть по-прежнемуHTML 注入, данные пользователя обрабатываются какHTMLЧасть кода выполняется, что смешивает исходную семантику и создает новую семантику.

Если сайт используетMVC(MVVM)структуру, тоXSSпроизойдет вViewслой, который генерируется, когда переменная встраивается в страницу, поэтому проверка ввода, когда пользователь отправляет данные, на самом деле не защищает атакуемое место, а предотвращает атаку.Ниже я подытожу некоторыеXSSСценарии, которые произошли, а затем решить их один за другим.

существуетHTMLвывод в этикетке

существуетHTMLПеременная выводится непосредственно в этикетке без какой-либо обработки, что приведет кXSS.

<a href=# ><img src=1 onerror=alert(1)></a>

Решение таким образом заключается в том, что все элементы, которые необходимо вывести на страницу, пропускаются черезHTMLEncode.

существуетHTMLвывод в свойствах

быть сHTMLМетод атаки на выходе в теге аналогичен, за исключением того, что выходное содержимое автоматически закроет тег.

<a href="我是变量" ></a>
<!-- 我是变量: "><img src=1 onerror=alert(1)><" -->
<!-- 插入之后变为 -->
<a href=""><img src=1 onerror=alert(1)><""></a>

Способ защиты таким способом до сих порHTMLEncode.

существует<script>вывод в этикетке

Предполагая, что все наши переменные находятся внутри кавычек:

let a = "我是变量"
// 我是变量 = ";alert(1);//
a = "";alert(1);//"

Злоумышленнику нужно только закрыть тег, чтобы провести атаку Текущий метод защиты:JavaScriptEncode.

существуетCSSсредний выход

существуетCSSсредний илиstyleэтикетка илиstyle attributeШаблон атаки в виде очень большой, как правило, похоже на следующие примеры:

<style>@import url('http:xxxxx')</style>
<style>@import 'http:xxxxx'</style>
<style>li {list-style-image: url('xxxxxx')}</style>
<style>body {binding:url('xxxxxxxxxx')}</style>
<div style='background-image: url(xxxx)'></div>
<div style='width: expression(xxxxx)'></div>

решатьCSSС одной стороны, необходимо строго контролировать ввод переменной пользователемstyleтеги, с другой стороны, не ссылаются на неизвестныеCSSфайл, если должны были быть изменения пользователяCSSЕсли вам нужна переменная, вы можете использоватьOWASP ESAPIсерединаencodeForCSS()функция.

типичная третья сторонаCSSПримеры атак на библиотеки:

input[type="password"][value$="0"]{ background-image: url("http://localhost:3000/0") }
input[type="password"][value$="1"]{ background-image: url("http://localhost:3000/1") }
input[type="password"][value$="2"]{ background-image: url("http://localhost:3000/2") }
input[type="password"][value$="3"]{ background-image: url("http://localhost:3000/3") }
input[type="password"][value$="4"]{ background-image: url("http://localhost:3000/4") }
input[type="password"][value$="5"]{ background-image: url("http://localhost:3000/5") }
input[type="password"][value$="6"]{ background-image: url("http://localhost:3000/6") }
input[type="password"][value$="7"]{ background-image: url("http://localhost:3000/7") }
input[type="password"][value$="8"]{ background-image: url("http://localhost:3000/8") }
input[type="password"][value$="9"]{ background-image: url("http://localhost:3000/9") }
...

Остальное не пишется, то есть прописываются все символы, которые можно ввести с клавиатуры.

input[type="password"]это селектор css, функция состоит в том, чтобы выбрать поле ввода пароля,[value$="0"]Указывает, что значение, соответствующее входу, завершается 0.

Поэтому, если вы поместите 0 в поле пароля, перейдите к запросуhttp://localhost:3000/0интерфейса, но браузеры по умолчанию не хранят введенные пользователем значения вvalueсвойства, но некоторые фреймворки синхронизируют эти значения, напримерReact.

Мы моделируем синхронизациюvalueстоимость:

<body>
  <input type="password" value="" id="pwd">
</body>
<script>
  const pwd = document.querySelector('#pwd');
  pwd.oninput = (e) => {
    pwd.attributes.value.value = e.target.value
  }
</script>

Затем смотрим на эффект:

Jietu20180927-150717-HD

Смотреть! Ваши пароли были отправлены на пульт, так что введитеCSSСлишкомXSSОдно из средств нападения, только подумай, не невозможное -

существуетURLсредний выход

Вывод адресного листа также более сложен. Вообще говоряURLизpathилиsearchпрямое использование в атакеURLEncodeВот и все.URLEncodeПреобразовать строку в%HHв виде пространства, похожего на%20.

Возможные методы атаки:

<!-- 原始 URL -->
<a href="http://localhost:3000/?test=我是变量"></a>
<!-- 攻击 URL -->
<a href="http://localhost:3000/?test=" onclick=alert(1)""></a>
<!-- URLEncode -->
<a href="http://localhost:3000/?test=%22%20onclick%3balert%281%29%22"></a>

Но вы использовалиURLEncodeВсе хорошо?

нет нет нет

Если весьURLконтролируется пользователем, то предыдущийhttp://,localhost:3000Если части ускользнут, это будет испорчено Эти части не могут быть устранены.

ОдинURLСостав следующий:

[Protocal][Host][Path][Search][Hash]

Каштан:

http://localhost:3000/a/b/c?search=123#666aaa

[Protocal]вести перепискуhttp://

[Host]вести перепискуlocalhost:3000

[Path]вести переписку/a/b/c

[Search]вести переписку?search=123

[Hash]вести переписку#666aaa

В общем случае, если переменная представляет собой всюURL, вы должны сначала проверить, начинается ли переменная сhttpВ начале после этого будут обрабатываться переменные внутриURLEncode.

Обработка расширенного текста

На некоторых сайтах сайт позволяет пользователям обогащатьHTMLКод метки, такой как изображения, видео и т. д. в тексте, все эти тексты отображаются с опорой наHTMLкод для реализации.

Итак, как нам нужно различать безопасные富文本а такжеXSSЧто с нападением?

Мне довелось выполнять связанные операции фильтрации расширенного текста в Huawei, Основная идея такова:

  1. Сначала выполняется проверка ввода, чтобы убедиться, что пользовательский ввод завершен.HTMLкод, не прошитый код
  2. пройти черезhtmlParserразобратьHTMLТеги, свойства, события кода
  3. 富文本из事件должны быть запрещены, потому что富文本Это не нужно事件Такого рода вещи, другие опасные ярлыки также должны быть запрещены, например:<iframe>,<script>,<base>,<form>Ждать
  4. Используя механизм белого списка, разрешены только безопасные встраивания тегов, такие как:<a>,<img>,divи т. д., белый список предназначен не только для тегов, но и для属性
  5. фильтровать пользователейCSS, проверьте наличие опасного кода

резюме

Теоретически,XSSХотя уязвимости сложные, но от них можно полностью избавиться в конструкцииXSSРешения, объединяющие текущие потребности бизнеса, определяющие каждое из них с точки зрения бизнес-рисковXSSУязвимости, использовать разные методы для разных сценариев, и в то же время многие проекты с открытым исходным кодом могут учиться и улучшать свои собственныеXSSрешение.

Наконец, я сожалею, что продвигаю свою работу на основеTaroБиблиотека компонентов, написанная фреймворком:MP-ColorUI.

Я очень рад, что могу сыграть главную роль, спасибо.

Щелкните здесь для документации

Нажмите здесь, чтобы узнать адрес GitHub