هل طريقة مكافحة XSS الخاصة بي موافق للسماح للمستخدم HTML في PHP؟

StackOverflow https://stackoverflow.com/questions/1383756

سؤال

أنا أعمل على إيجاد طريقة جيدة لجعل البيانات المقدمة من المستخدم ، في هذه الحالة ، السماح لـ HTML وجعلها آمنة وسريعة قدر الإمكان.

أعلم أن كل شخص على هذا الموقع يبدو أنه يفكر http://htmlpurifier.org هو الجواب هنا. أنا أوافق جزئيا. htmlpurifier لديه أفضل رمز المصدر مفتوحًا لتصفية المستخدم HTML ، ولكن هناك حل ضخم للغاية وليس جيدًا للأداء على موقع حركة مرور عالية. قد أستخدم حلًا يومًا ما في يوم من الأيام ، لكن هدفي الآن هو إيجاد طريقة أكثر خفيفة الوزن.

لقد كنت أستخدم وظيفتين أدناه لمدة عامين ونصف حتى الآن دون أي مشاكل حتى الآن ، لكنني أعتقد أن الوقت قد حان لاتخاذ بعض المدخلات من المحترفين هنا إذا كانوا سيساعدونني.

تسمى الوظيفة الأولى filterhtml ($ string) يتم تشغيله قبل حفظ بيانات المستخدم في قاعدة بيانات MySQL. تسمى الوظيفة الثانية format_db_value (نص $ ، $ nl2br = false) وأنا أستخدمه على صفحة حيث أخطط لإظهار البيانات المقدمة من المستخدم.

أسفل الوظائف 2 توجد مجموعة من رموز XSS التي وجدتها http://ha.ckers.org/xss.html ثم قمت بتشغيلهم على هاتين الوظيفين لترى مدى انتشار الكود الخاص بي ، أنا مسرور إلى حد ما بالنتائج ، لقد قاموا بحظر كل رمز جربته ولكني أعلم أنه لا يزال غير آمن بنسبة 100 ٪ بشكل واضح.

هل يمكنك يا رفاق إلقاء نظرة على ذلك وإعطائي أي نصيحة لرمزتي نفسها أو حتى على مفهوم تصفية HTML بأكمله.

أرغب في القيام بنهج القائمة البيضاء يومًا ما ولكن htmlpurifier هو الحل الوحيد الذي وجدته يستحق استخدامه لذلك وكما ذكرت أنه ليس خفيف الوزن كما أريد.

function FilterHTML($string) {
    if (get_magic_quotes_gpc()) {
        $string = stripslashes($string);
    }
    $string = html_entity_decode($string, ENT_QUOTES, "ISO-8859-1");
    // convert decimal
    $string = preg_replace('/&#(\d+)/me', "chr(\\1)", $string); // decimal notation
    // convert hex
    $string = preg_replace('/&#x([a-f0-9]+)/mei', "chr(0x\\1)", $string); // hex notation
    //$string = html_entity_decode($string, ENT_COMPAT, "UTF-8");
    $string = preg_replace('#(&\#*\w+)[\x00-\x20]+;#U', "$1;", $string);
    $string = preg_replace('#(<[^>]+[\s\r\n\"\'])(on|xmlns)[^>]*>#iU', "$1>", $string);
    //$string = preg_replace('#(&\#x*)([0-9A-F]+);*#iu', "$1$2;", $string); //bad line
    $string = preg_replace('#/*\*()[^>]*\*/#i', "", $string); // REMOVE /**/
    $string = preg_replace('#([a-z]*)[\x00-\x20]*([\`\'\"]*)[\\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iU', '...', $string); //JAVASCRIPT
    $string = preg_replace('#([a-z]*)([\'\"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iU', '...', $string); //VBSCRIPT
    $string = preg_replace('#([a-z]*)[\x00-\x20]*([\\\]*)[\\x00-\x20]*@([\\\]*)[\x00-\x20]*i([\\\]*)[\x00-\x20]*m([\\\]*)[\x00-\x20]*p([\\\]*)[\x00-\x20]*o([\\\]*)[\x00-\x20]*r([\\\]*)[\x00-\x20]*t#iU', '...', $string); //@IMPORT
    $string = preg_replace('#([a-z]*)[\x00-\x20]*e[\x00-\x20]*x[\x00-\x20]*p[\x00-\x20]*r[\x00-\x20]*e[\x00-\x20]*s[\x00-\x20]*s[\x00-\x20]*i[\x00-\x20]*o[\x00-\x20]*n#iU', '...', $string); //EXPRESSION
    $string = preg_replace('#</*\w+:\w[^>]*>#i', "", $string);
    $string = preg_replace('#</?t(able|r|d)(\s[^>]*)?>#i', '', $string); // strip out tables
    $string = preg_replace('/(potspace|pot space|rateuser|marquee)/i', '...', $string); // filter some words
    //$string = str_replace('left:0px; top: 0px;','',$string);
    do {
        $oldstring = $string;
        //bgsound|
        $string = preg_replace('#</*(applet|meta|xml|blink|link|script|iframe|frame|frameset|ilayer|layer|title|base|body|xml|AllowScriptAccess|big)[^>]*>#i', "...", $string);
    } while ($oldstring != $string);
    return addslashes($string);
}

يتم استخدام الوظيفة أدناه عند عرض رمز مقدم المستخدم على صفحة ويب

function format_db_value($text, $nl2br = false) {
    if (is_array($text)) {
        $tmp_array = array();
        foreach ($text as $key => $value) {
            $tmp_array[$key] = format_db_value($value);
        }
        return $tmp_array;
    } else {
        $text = htmlspecialchars(stripslashes($text));
        if ($nl2br) {
            return nl2br($text);
        } else {
            return $text;
        }
    }
}

الرموز أدناه من ha.ckers.org ويبدو أنهم جميعا يفشلون في وظائف بلدي أعلاه

لم أجرب الجميع على هذا الموقع على الرغم من أن هناك الكثير ، فهذا مجرد بعضهم.
الكود الأصلي موجود في السطر العلوي من كل مجموعة والرمز بعد الركض من خلال وظيفاتي على السطر أدناه.

<IMG SRC="javascript:alert(\'XSS\');"><b>hello</b> hiii
<IMG SRC=...alert('XSS');"><b>hello</b> hiii

<IMG SRC=JaVaScRiPt:alert('XSS')>
<IMG SRC=...alert('XSS')>

<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
<IMG SRC=...alert(String.fromCharCode(88,83,83))>

<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>
<IMG SRC=...alert('XSS')>

<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>
<IMG SRC=F  MLEJNALN !>

<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>
<IMG SRC=...alert('XSS')>


<IMG SRC="jav&#x0A;ascript:alert('XSS');">
<IMG SRC=...alert('XSS');">

perl -e 'print "<IMG SRC=javascript:alert("XSS")>";' > out
perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out

<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>
...

<iframe src=http://ha.ckers.org/scriptlet.html <
...

<LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER>
......

<META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet">
...; REL=stylesheet">

<IMG STYLE="xss:...(alert('XSS'))">
<IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))">

<XSS STYLE="xss:...(alert('XSS'))">
<XSS STYLE="xss:expression(alert('XSS'))">

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>


<IMG
SRC
=
"
j
a
v
a
s
c
r
i
p
t
:
a
l
e
r
t
(
'
X
S
S
'
)
"
>

<IMG
SRC
=...
a
l
e
r
t
(
'
X
S
S
'
)
"
>
هل كانت مفيدة؟

المحلول

نصائح أخرى

الطريقة الوحيدة للتأكد من أن العلامات والسمات التي يمكنهم استخدامها وكتابة regexps صارمة للتحقق من صحة قيم السمات المسموح بها. إذا كنت تريد السماح بسمات مثل "النمط" ، فأنت لديك تعقيد إضافي.

قد يجعل القائمة السوداء الهجوم فقط لبعض الأشخاص أكثر صعوبة ، لكن ذلك لن يجعل الأمر أكثر صعوبة بالنسبة للشخص الذي يستخدم تقنية لم تسمع بها بعد.

سأحاول استخدام regexp لإضافة علامات إغلاق مفقودة إلى ما أدخله المستخدمون واستبدالهم <br> مع <br /> وما إلى ذلك ، ثم تحليله باستخدام SimpleXML ، ثم تكراره وإزالة أي علامة غير موجودة في القائمة البيضاء ، أي سمة ليست في القائمة البيضاء للعلامة المعطاة ، وأي سمة لها قيمة تتوافق مع regexp الدقيقة لـ هذه السمة. بعد كل شيء سأستخدم ASXML () لاستعادة النص. سأبدأ مع الحد الأدنى من العلامات والسمات وأضيف علامات جديدة حسب الحاجة إلى توخي الحذر بشكل خاص من أي شيء قد يحتوي على عنوان URL.

Imho htmlawed هي الأفضل - تغطية HTML سريعة وسريعة وكاملة ، وأكثرها مرونة ... قائمة سوداء أو بيضاء للعلامات والسمات. آمن؟ هزائم جميع رموز Ha.ckers XSS

ماذا عن استخدام محلل HTML الأصلي لـ PHP؟

كنت مهتمًا بذلك ، لذلك كتبت بعض التعليمات البرمجية للاختبار (يتطلب PHP 5.3.6+):

$badHtml = file_get_contents('badHtml.txt');
$html = sprintf('<div id="input">%s</div>', $badHtml);

// tidy is no required, but may fix invalid markup
$tidy = new \tidy();
$tidy->parseString($html, array(), 'utf8');
$tidy->cleanRepair();

$dom = new \DomDocument('1.0', 'UTF-8');
libxml_use_internal_errors(true);
$dom->loadHtml($tidy);
$input = $dom->getElementById('input');

// tag as key, attributes as values
$allowed = array(
  'table'  => array('border'),  
  'tbody'  => array(),
  'tr'     => array(),
  'td'     => array(),
  'th'     => array(),
  'img'    => array('src', 'alt'),
  'p'      => array(),
  'ul'     => array(),
  'ol'     => array(),
  'li'     => array(),
  'a'      => array('href', 'title'),
  'strong' => array(),
  'em'     => array(),
  'sub'    => array(),
  'sup'    => array(),
);

$walk = function(\DomNode $node) use($allowed, &$walk){

  // only check tags
  if($node->nodeType !== XML_ELEMENT_NODE)
    return;

  if(!isset($allowed[$node->nodeName]))
    return $node->parentNode->removeChild($node);

  foreach($node->attributes as $key => $attr){
    if(!in_array($key, $allowed[$node->nodeName], true))
     $node->removeAttribute($key);

    // expect URLs here
    if(!in_array($key, array('href', 'src'), true))
      continue;

    if(!filter_var($attr->value, FILTER_VALIDATE_URL))
      return $node->parentNode->removeChild($node); 

  }

  array_map($walk, iterator_to_array($node->childNodes));  
};

// convert DOMNodeList to array because this way the bad stuff
// can be removed within the loop
array_map($walk, iterator_to_array($input->childNodes));

// export HTML
$sanitized = $dom->saveHtml($input);

الإخراج ، دون تشغيل مرتبة:

enter image description here

يبدو طيب. أم أنها أزلت كثيرا؟ :) يجب أن تكون أسرع من htmlpurifier ، أكثر أمانًا من الناحية النظرية لأنها أقل تساهلاً ، وربما أسرع من regexes أيضًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top