
bluemonday: منظف HTML سريع بلغة golang (مستوحى من OWASP Java HTML Sanitizer) لتنظيف المحتوى الذي أنشأه المستخدم من XSS
bluemonday هو أداة تنقية HTML مطبّقة في Go. إنّها سريعة وقابلة للتكوين بشكل كبير.
تأخذ bluemonday المحتوى الذي ينشئه المستخدم غير الموثوق كمدخل، وتُعيد HTML تم تنقيته وفقًا لقائمة سماح من عناصر وسمات HTML المعتمدة، بحيث يمكنك تضمين المحتوى بأمان في صفحة الويب الخاصة بك.
إذا كنت تقبل محتوى من إنشاء المستخدم وكان خادمك يستخدم Go، فأنت تحتاج إلى bluemonday.
السياسة الافتراضية للمحتوى الذي ينشئه المستخدم (bluemonday.UGCPolicy().Sanitize()) تحوّل هذا:
Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
إلى هذا غير الضار:
Hello World
وتحوّل هذا:
<a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
إلى هذا:
XSS
بينما تسمح مع ذلك بهذا:
<a href="http://www.google.com/">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
للمرور دون تغيير كبير (أضافت rel="nofollow" وهو أمر جيد للمحتوى الذي ينشئه المستخدم):
<a href="http://www.google.com/" rel="nofollow">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
إنها تحمي المواقع من هجمات XSS. هناك العديد من ناقلات هجوم XSS وأفضل طريقة للتخفيف من المخاطرة هي تنقية إدخال المستخدم مقابل قائمة آمنة معروفة من عناصر وسمات HTML.
يجب عليك دائمًا تشغيل bluemonday بعد أي معالجة أخرى.
إذا كنت تستخدم blackfriday أو Pandoc، فيجب تشغيل bluemonday بعد هذه الخطوات. هذا يضمن عدم إدخال HTML غير آمن لاحقًا في عمليتك.
bluemonday مستوحى بشدة من كل من OWASP Java HTML Sanitizer و HTML Purifier.
يعتمد على قائمة السماح، تحتاج إما إلى بناء سياسة تصف عناصر وسمات HTML المسموح بها (وأنماط regexp للسمات)، أو استخدام إحدى السياسات المقدمة التي تمثل إعدادات افتراضية جيدة.
يتم تطبيق السياسة التي تحتوي على قائمة السماح باستخدام محلل سريع غير تحقق، وأمامي فقط، قائم على الرموز المميزة مطبق في مكتبة Go net/html من فريق Go الأساسي.
نتوقع تزويدنا بـ HTML منسّق جيدًا (عناصر إغلاق لكل عنصر مفتوح قابل للتطبيق، متداخلة بشكل صحيح) ولذا لا نركز على إصلاح HTML سيئ التداخل أو غير مكتمل. نركز ببساطة على ضمان أن أي عناصر موجودة موصوفة في قائمة السماح للسياسة وأن السمات والروابط آمنة للاستخدام على صفحة الويب الخاصة بك. ينطبق GIGO وإذا قمت بتغذيته بـ HTML سيئ، فليس من مهام bluemonday معرفة كيفية جعله جيدًا مرة أخرى.
نعم
نحن نستخدم bluemonday في الإنتاج بعد أن هاجرنا من أداة OWASP Java HTML Sanitizer المستخدمة على نطاق واسع والمختبرة ميدانيًا بكثافة.
نحن نجتاز مجموعة اختباراتنا الواسعة (بما في ذلك اختبارات AntiSamy بالإضافة إلى اختبارات لأي مشكلات تم رفعها). تحقق من أي مشكلات غير محلولة لترى ما إذا كان هناك أي شيء قد يشكل عائقًا لك.
نرحب بطلبات السحب والمشكلات لمساعدتنا في ضمان تقديم حماية شاملة ضد الهجمات المختلفة عبر المحتوى الذي ينشئه المستخدم.
قم بالتثبيت باستخدام go get github.com/microcosm-cc/bluemonday
ثم قم باستدعائها:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
// افعل هذا مرة واحدة لكل سياسة فريدة، واستخدم السياسة طوال عمر البرنامج
// إنشاء/تعديل السياسة ليس آمنًا للاستخدام في عدة goroutines
p := bluemonday.UGCPolicy()
// يمكن بعد ذلك استخدام السياسة لتنقية الكثير من المدخلات وهي آمنة للاستخدام في عدة goroutines
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// المخرجات:
// <a href="http://www.google.com" rel="nofollow">Google</a>
fmt.Println(html)
}
نقدم ثلاث طرق لاستدعاء Sanitize:
p.Sanitize(string) string
p.SanitizeBytes([]byte) []byte
p.SanitizeReader(io.Reader) bytes.Buffer
إذا كنت مهووسًا بالأداء، p.SanitizeReader(r).Bytes() سيعيد []byte دون إجراء أي تحويل غير ضروري للمدخلات أو المخرجات. على الرغم من أن الفرق ضئيل جدًا بحيث لا تحتاج أبدًا إلى الاهتمام.
يمكنك بناء سياساتك الخاصة:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
p := bluemonday.NewPolicy()
// يتطلب أن تكون عناوين URL قابلة للتحليل بواسطة net/url.Parse وإما:
// mailto: http:// or https://
p.AllowStandardURLs()
// نسمح فقط بـ <p> و <a href="">
p.AllowAttrs("href").OnElements("a")
p.AllowElements("p")
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// المخرجات:
// <a href="http://www.google.com">Google</a>
fmt.Println(html)
}
نحن نشحن سياستين افتراضيتين:
bluemonday.StrictPolicy() والتي يمكن اعتبارها مكافئة لتجريد جميع عناصر HTML وسماتها حيث لا يوجد شيء في قائمة السماح الخاصة بها. مثال على سيناريو الاستخدام سيكون عناوين منشورات المدونة حيث لا يُتوقع وجود علامات HTML على الإطلاق، وإذا كانت موجودة، فيجب تجريد العناصر ومحتوى العناصر. هذه سياسة صارمة جدًا.bluemonday.UGCPolicy() والتي تسمح بمجموعة واسعة من عناصر وسمات HTML الآمنة للمحتوى الذي ينشئه المستخدم. لاحظ أن هذه السياسة لا تسمح بـ iframes، object، embed، styles، script، إلخ. مثال على سيناريو الاستخدام سيكون نصوص منشورات المدونة حيث يُتوقع مجموعة متنوعة من التنسيقات إلى جانب احتمالية وجود جداول وصور.جوهر بناء السياسة هو تحديد عناصر وسمات HTML التي تعتبر آمنة لسيناريوهك. توفر OWASP ورقة غش لمنع XSS للمساعدة في شرح المخاطر، ولكن بشكل أساسي:
script، style، iframe، object، embed، base التي تسمح بتنفيذ الكود من قبل العميل أو تضمين محتوى من طرف ثالث يمكنه تنفيذ كودبشكل أساسي، يجب أن تكون قادرًا على وصف HTML المناسب لسيناريوهك. إذا لم تكن لديك ثقة في قدرتك على وصف سياستك، فيرجى التفكير في استخدام إحدى السياسات المشحونة مثل bluemonday.UGCPolicy().
لإنشاء سياسة جديدة:
p := bluemonday.NewPolicy()
لإضافة عناصر إلى سياسة إما بإضافة العناصر فقط:
p.AllowElements("b", "strong")
أو باستخدام regex:
ملاحظة: إذا تم إضافة عنصر بالاسم كما هو موضح أعلاه، فسيتم تجاهل أي regex مطابق
يوصى أيضًا بضمان عدم تداخل أنماط متعددة حيث أن ترتيب التنفيذ غير مضمون ويمكن أن يؤدي إلى تفويت بعض القواعد.
p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
أو إضافة عناصر كفضيلة لإضافة سمة:
// لاحظ النمط الموصى به، انظر التوصية باستخدام .Matching() أدناه
p.AllowAttrs("nowrap").OnElements("td", "th")
مرة أخرى، هذا يدعم أيضًا بديل مطابقة نمط regex:
p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
يمكن إضافة السمات إما إلى جميع العناصر:
p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
أو يمكن إضافة السمات إلى عناصر محددة:
// ليس النمط الموصى به، انظر التوصية باستخدام .Matching() أدناه
p.AllowAttrs("value").OnElements("li")
يوصى دائمًا بأن يتم مطابقة السمة مع نمط. XSS في سمات HTML سهل جدًا بخلاف ذلك:
// \p{L} يطابق الحروف اليونيكود، \p{N} يطابق الأرقام اليونيكود
p.AllowAttrs("title").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).Globally()
يمكنك التوقف في أي وقت واستدعاء .Sanitize():
// string htmlIn تم إدخاله من HTTP POST
htmlOut := p.Sanitize(htmlIn)
ويمكنك أخذ أي سياسة موجودة وتوسيعها:
p := bluemonday.UGCPolicy()
p.AllowElements("fieldset", "select", "option")
على الرغم من أنه من الممكن التعامل مع CSS المضمنة باستخدام AllowAttrs مع قاعدة Matching، فإن كتابة تعبير منتظم واحد ضخم لمعالجة جميع CSS المضمنة التي ترغب في السماح بها بأمان ليست مهمة تافهة. بدلاً من محاولة القيام بذلك، يمكنك السماح بسمة style على أي عنصر (عناصر) تريده واستخدام سياسات النمط للتحكم في الأنماط المضمنة وتنقيتها.
يوصى بشدة باستخدام Matching (مع تعبير منتظم مناسب)
MatchingEnum، أو MatchingHandler لضمان أن كل نمط يتطابق مع احتياجاتك،
ولكن يتم توفير معالجات افتراضية لمعظم الأنماط المستخدمة على نطاق واسع.
على غرار السمات، يمكنك السماح بتعيين خصائص CSS محددة بشكل مضمن:
p.AllowAttrs("style").OnElements("span", "p")
// السماح بخاصية 'color' بقيم RGB(A) hex صالحة فقط (على أي عنصر مسموح له بسمة 'style')
p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
بالإضافة إلى ذلك، يمكنك السماح بتعيين خاصية CSS فقط إلى قيمة مسموح بها:
p.AllowAttrs("style").OnElements("span", "p")
// السماح بتعيين خاصية 'text-decoration' إلى 'underline' أو 'line-through' أو 'none'
// فقط على عناصر 'span'
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
أو يمكنك تحديد العناصر بناءً على تطابق نمط regex:
p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
// السماح بتعيين خاصية 'text-decoration' إلى 'underline' أو 'line-through' أو 'none'
// فقط على عناصر 'span'
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
إذا كنت بحاجة إلى فحص أكثر تحديدًا، يمكنك إنشاء معالج يأخذ سلسلة ويعيد bool للتحقق من صحة القيم لخاصية معينة. تم تحويل معامل السلسلة إلى أحرف صغيرة وتم تحويل نقاط رمز اليونيكود.
myHandler := func(value string) bool{
// تحقق من إدخالك هنا
return true
}
p.AllowAttrs("style").OnElements("span", "p")
// السماح بخاصية 'color' بقيم تم التحقق منها بواسطة المعالج (على أي عنصر مسموح له بسمة 'style')
p.AllowStyles("color").MatchingHandler(myHandler).Globally()
الروابط هي وحوش صعبة لتنقيتها بأمان وهي أيضًا واحدة من أكبر نواقل الهجوم للمحتوى الخبيث.
من الممكن القيام بذلك:
p.AllowAttrs("href").Matching(regexp.MustCompile(`(?i)mailto|https?`)).OnElements("a")
لكن هذا لن يحميك لأن التعبير المنتظم غير كافٍ في هذه الحالة لمنع قيمة مشوهة من فعل شيء غير متوقع.
نحن نقدم بعض الخيارات العامة الإضافية للعمل بأمان مع الروابط.
RequireParseableURLs سيضمن أن عناوين URL قابلة للتحليل بواسطة حزمة net/url الخاصة بـ Go:
p.RequireParseableURLs(true)
إذا قمت بتمكين عناوين URL القابلة للتحليل، فإن الخيار التالي سيسمح بـ AllowRelativeURLs. بشكل افتراضي، هذا معطل (bluemonday هي أداة قائمة سماح... تحتاج إلى إخبارنا صراحة بالسماح بالأشياء) وعند تعطيله، سيمنع جميع عناوين URL المحلية والنسبية للمخطط (أي href="localpage.html"، href="../home.html" وحتى href="//www.google.com" تعتبر نسبية):
p.AllowRelativeURLs(true)
إذا قمت بتمكين عناوين URL القابلة للتحليل، فيمكنك السماح بالمخططات (التي تسمى عادةً بروتوكول عند التفكير في http و https) المسموح بها. ضع في اعتبارك أن السماح بعناوين URL النسبية في الخيار أعلاه سيسمح بمخطط فارغ:
p.AllowURLSchemes("mailto", "http", "https")
بغض النظر عما إذا كنت قد قمت بتمكين عناوين URL القابلة للتحليل، يمكنك فرض أن جميع عناوين URL تحتوي على سمة rel="nofollow". ستتم إضافة هذا إذا لم يكن موجودًا، ولكن فقط عندما يكون href صالحًا:
// هذا ينطبق على عناصر "a" "area" "link" التي لها سمة "href"
p.RequireNoFollowOnLinks(true)
وبالمثل، يمكنك فرض أن جميع عناوين URL تحتوي على "noreferrer" في سمة rel الخاصة بها.
// هذا ينطبق على عناصر "a" "area" "link" التي لها سمة "href"
p.RequireNoReferrerOnLinks(true)
نحن نقدم طريقة ملائمة تطبق كل ما سبق، لكنك ستظل بحاجة إلى السماح بالعناصر القابلة للربط لتطبيق قواعد URL عليها:
p.AllowStandardURLs()
p.AllowAttrs("cite").OnElements("blockquote", "q")
p.AllowAttrs("href").OnElements("a", "area")
p.AllowAttrs("src").OnElements("img")
تعقيد إضافي بخصوص الروابط هو بيانات URI كما هو محدد في RFC2397. تسمح بيانات URI بعرض الصور بشكل مضمن باستخدام هذا التنسيق:
<img src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=">
لقد قدمنا مساعدًا للتحقق من نوع MIME متبوعًا بمحتوى base64 لروابط بيانات URI:
p.AllowDataURIImages()
سيقوم هذا المساعد بتمكين صور GIF و JPEG و PNG و WEBP.
تجدر الإشارة إلى أن هناك مخاطرة أمنية محتملة مع استخدام روابط بيانات URI. يجب عليك فقط تمكين روابط بيانات URI إذا كنت تثق بالفعل في المحتوى.
لدينا أيضًا بعض الميزات للمساعدة في التعامل مع المحتوى الذي ينشئه المستخدم:
p.AddTargetBlankToFullyQualifiedLinks(true)
سيضمن هذا أن روابط الإرساء <a href="" /> المؤهلة بالكامل (وجهة href تتضمن اسم مضيف) ستحصل على target="_blank" المضافة إليها.
بالإضافة إلى ذلك، أي رابط يحتوي على target="_blank" بعد تطبيق السياسة سيتم أيضًا تعديل سمة rel لإضافة noopener. هذا يعني أن الرابط قد يبدأ مثل <a href="//host/path"/> وسينتهي مثل <a href="//host/path" rel="noopener" target="_blank">. من المهم ملاحظة أن إضافة noopener هي ميزة أمان وليست مشكلة. هناك ميزة مؤسفة في المتصفحات حيث أن نافذة المتصفح المفتوحة نتيجة target="_blank" يمكنها التحكم في الفاتح (صفحة الويب الخاصة بك) وهذا يحمي من ذلك. يمكن العثور على خلفية هذا هنا: https://dev.to/ben/the-targetblank-vulnerability-by-example
نقوم أيضًا بتجميع بعض المساعدات لتبسيط بناء السياسة:
// يسمح بسمات "dir"، "id"، "lang"، "title" عالميًا
p.AllowStandardAttributes()
// يسمح بعنصر "img" وسماته القياسية
p.AllowImages()
// يسمح بالقوائم المرتبة وغير المرتبة، وأيضًا قوائم التعريف
p.AllowLists()
// يسمح بجداول HTML وجميع العناصر القابلة للتطبيق والسمات غير المتعلقة بالتصميم
p.AllowTables()
ما يلي غير صالح:
// هذا لا يحدد مكان السماح بالسمات، تحتاج إلى إضافة
// .Globally() أو .OnElements(...)
// سيتم تجاهل هذا دون خطأ.
p.AllowAttrs("value")
// هذا لا يحدد مكان السماح بالسمات، تحتاج إلى إضافة
// .Globally() أو .OnElements(...)
// سيتم تجاهل هذا دون خطأ.
p.AllowAttrs(
"type",
).Matching(
regexp.MustCompile("(?i)^(circle|disc|square|a|A|i|I|1)$"),
)
كلا المثالين يظهران نفس المشكلة، يعلنان عن سمات ولكن بعد ذلك لا يحددان ما إذا كانت مسموحة عالميًا أو فقط على عناصر محددة (وأي عناصر). السمات تنتمي إلى عنصر واحد أو أكثر، وتحتاج السياسة إلى إعلان ذلك.
نحن لا ندرج بعد أي أدوات للمساعدة في السماح بـ CSS وتنقيته. مما يعني أنه ما لم ترغب في القيام بالعمل الشاق في تعبير منتظم واحد (غير مستحسن)، يجب ألا تسمح بسمة "style" في أي مكان.
في نفس السياق، يُعتبر كل من <script> و <style> ضارين. لن يتم عرض هذه العناصر (ومحتواها) افتراضيًا، وتتطلب منك تعيين p.AllowUnsafe(true) بشكل صريح. يجب أن تكون على دراية بأن السماح بهذه العناصر يهزم الغرض من استخدام أداة تنقية HTML لأنك ستسمح صراحةً إما بـ JavaScript (وأي XSS مكتوب بوضوح) و CSS (الذي يمكنه تعديل DOM لإدراج JS)، بالإضافة إلى ذلك، القيود في هذه المكتبة تعني أنها ليست على دراية بما إذا كان HTML منظمًا بشكل صحيح ويمكن أن يسمح لهذه العناصر بتجاوز بعض آليات الأمان المضمنة في معيار محلل HTML WhatWG.
ليس من وظيفة bluemonday إصلاح HTML السيئ الخاص بك، إنها مجرد وظيفة bluemonday منع HTML الخبيث من المرور. إذا كان لديك عناصر HTML غير متطابقة، أو تداخل غير متوافق للعناصر، فستبقى تلك العناصر. ولكن إذا كان لديك HTML منظم جيدًا، فلن تفسده bluemonday.
bluemonday.UGCPolicy()) التي تجسد 90٪ مما يبحثون عنه ولكنها تفعل أكثر مما يحتاجون، وإزالة الأشياء الإضافية التي لا يريدونها لجعلها 100٪ ما يريدونtable ليس سليلًا لـ caption، وأن colgroup، thead، tbody، tfoot و tr مسموحة، وأن بيانات الأحرف غير مسموحة)