XSS-атака на основе DOM и InnerHTML - PullRequest
4 голосов
/ 17 мая 2011

Как можно обеспечить защиту описанной ниже DSS-атаки на основе DOM?

В частности, есть ли функция protect (), которая сделает приведенный ниже код безопасным? Если нет, то есть ли другое решение? Например: присвоение div идентификатору, а затем присвоение элементу обработчика onclick

<?php
function protect()
{
   // For non-DOM XSS attacks, hex-encoding all non-alphanumeric characters
   // with ASCII values less than 256 works (ie: \xHH)
   // But is it possible to augment this function to protect against
   // the below DOM based XSS attack?
}
?>

<body>
  <div id="mydiv"></div>
  <script type="text/javascript">
    var xss = "<?php echo protect($_GET["xss"]) ?>";
    $("#mydiv").html("<div onclick='myfunc(\""+xss+"\")'></div>")
  </script>
</body>

Я надеюсь получить ответ, который не будет "избегать использования innerHTML" или "переделайте переменную xss в [a-zA-Z0-9]" ... т.е. есть ли более общее решение?

Спасибо

Ответы [ 3 ]

2 голосов
/ 17 мая 2011

Расширяя ответ Vineet, вот набор тестовых примеров, на которые следует обратить внимание:

1 голос
/ 07 июля 2011

Я играл с PHP DOMDocument и связанными с ним классами с целью написания HTML-парсера, который может справиться с подобными вещами.В данный момент он находится на очень ранней стадии разработки и еще далеко не готов к реальному использованию, но мои ранние эксперименты, похоже, показывают некоторую перспективу этой идеи.

По сути, вы загружаете вашу разметку в DOMDocument, а затем пересекаете дерево.Для каждого узла в дереве вы проверяете тип узла по списку разрешенных типов узлов.Если тип узла отсутствует в списке, он удаляется из дерева.

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

Это код, который я использую вместе с тестовым примером, который обрабатывает домашнюю страницу StackOverflow.Как я уже сказал, он далек от производственного кода качества и является не более чем доказательством концепции.Тем не менее, я надеюсь, что вы найдете это полезным.

<code><?php
class HtmlClean
{
    private $whiteList      = array (
        '#cdata-section', '#comment', '#text', 'a', 'abbr', 'acronym', 'address', 'b', 
        'big', 'blockquote', 'body', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 
        'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'fieldset', 'h1', 'h2', 'h3', 'h4', 
        'h5', 'h6', 'head', 'hr', 'html', 'i', 'img', 'ins', 'kbd', 'li', 'link', 'meta', 
        'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strike', 'strong', 'style', 'sub', 
        'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'ul', 
        'var'
    );

    private $attrWhiteList  = array (
        'class', 'id', 'title'
    );

    private $dom            = NULL;

    /**
     * Get current tag whitelist
     * @return array
     */
    public function getWhiteListTags ()
    {
        $this -> whiteList  = array_values ($this -> whiteList);
        return ($this -> whiteList);
    }

    /**
     * Add tag to the whitelist
     * @param string $tagName
     */
    public function addWhiteListTag ($tagName)
    {
        $tagName    = strtolower (trin ($tagName));
        if (!in_array ($tagName, $this -> whiteList))
        {
            $this -> whiteList []   = $tagName;
        }
    }

    /**
     * Remove a tag from the whitelist
     * @param string $tagName
     */
    public function removeWhiteListTag ($tagName)
    {
        if ($index = array_search ($tagName, $this -> whiteList))
        {
            unset ($this -> whiteList [$index]);
        }
    }

    /**
     * Load document markup into the class for cleaning
     * @param string $html The markup to clean
     * @return bool
     */
    public function loadHTML ($html)
    {
        if (!$this -> dom)
        {
            $this -> dom    = new DOMDocument();
        }
        $this -> dom -> preserveWhiteSpace  = false;
        $this -> dom -> formatOutput        = true;
        return $this -> dom -> loadHTML ($html);
    }

    public function outputHtml ()
    {
        $ret    = '';
        if ($this -> dom)
        {
            $ret    = $this -> dom -> saveXML ();
        }
        return ($ret);
    }

    private function cleanAttrs (DOMnode $elem)
    {
        $attrs  = $elem -> attributes;
        $index  = $attrs -> length;
        while (--$index >= 0)
        {
            $attrName   = strtolower ($attrs -> item ($indes) -> name);
            if (!in_array ($attrName, $this -> attrWhiteList))
            {
                $elem -> removeAttribute ($attrName);
            }
        }       
    }

    /**
     * Recursivly remove elements from the DOM that aren't whitelisted
     * @param DOMNode $elem
     * @return array List of elements removed from the DOM
     * @throws Exception If removal of a node failed than an exception is thrown
     */
    private function cleanNodes (DOMNode $elem)
    {
        $removed    = array ();
        if (in_array (strtolower ($elem -> nodeName), $this -> whiteList))
        {
            // Remove non-whitelisted attributes
            if ($elem -> hasAttributes ())
            {
                $this -> cleanAttrs ($elem);
            }
            /*
             * Iterate over the element's children. The reason we go backwards is because
             * going forwards will cause indexes to change when elements get removed
             */
            if ($elem -> hasChildNodes ())
            {
                $children   = $elem -> childNodes;
                $index      = $children -> length;
                while (--$index >= 0)
                {
                    $removed = array_merge ($removed, $this -> cleanNodes ($children -> item ($index)));
                }
            }
        }
        else
        {
            // The element is not on the whitelist, so remove it
            if ($elem -> parentNode -> removeChild ($elem))
            {
                $removed [] = $elem;
            }
            else
            {
                throw new Exception ('Failed to remove node from DOM');
            }
        }
        return ($removed);
    }

    /**
     * Perform the cleaning of the document
     */
    public function clean ()
    {
        $removed    = $this -> cleanNodes ($this -> dom -> getElementsByTagName ('html') -> item (0));
        return ($removed);
    }
}

$test       = file_get_contents( ('http://www.stackoverflow.com/'));
// Windows-stype linebreaks really foul up the works. There's probably a better fix for this
$test       = str_replace (chr (13), '', $test);

$cleaner    = new HtmlClean ();
$cleaner -> loadHTML ($test);

echo ('<h1>Before</h1><pre>' . htmlspecialchars ($cleaner -> outputHtml ()) . '
');$ start = microtime (true);$ remove = $ cleaner -> clean ();$ cleanTime = microtime (true) - $ start;echo ('

Список удаленных тегов

');foreach ($ удалено как $ elem) {var_dump ($ elem -> nodeName);} echo ('

После

' . htmlspecialchars ($cleaner -> outputHtml ()) . '
');// тест производительности var_dump ($ cleanTime);?>
0 голосов
/ 17 мая 2011

Я не эксперт по PHP, но если вы хотите предотвратить атаки XSS на образец кода, представленный в текущем формате с минимальными изменениями, вы можете использовать PHP-версию OWASP ESAPI . В частности, используйте класс кодека JavaScript из ESAPI , чтобы защитить содержимое переменной xss, как это выглядит в контексте JavaScript.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...