富文本輸入框控件沒做安全處理被XSS攻擊了
|
admin
2025年1月1日 13:30
本文熱度 766
|
前言相信很多前端小伙伴項目中都用到了富文本,但你們有沒有做防XSS 攻擊處理?最近的項目由于比較緊急我也沒有處理而是直接正常使用,但公司內部有專門的安全部門針對測試,然后測出來富文本被XSS 攻擊了,而且危險級別為高。
啊這....,那我就去解決一下吧,順便從XSS 和解決方案兩個角度記錄到下來畢竟好久沒更新文章了。 先說說什么是XSS攻擊?「簡述」:XSS 全稱Cross-Site Scripting 也叫跨站腳本攻擊,是最最最常見的網絡安全漏洞,其實就是攻擊者在受害者的瀏覽器中注入惡意腳本執行。這種攻擊通常發生在 Web 應用程序未能正確過濾用戶輸入的情況下,導致惡意腳本被嵌入到合法的網頁中。執行后會產生竊取信息、篡改網頁、和傳播病毒與木馬等危害,后果相當嚴重。 XSS 又有三大類
存儲型 XSS即Stored XSS惡意的腳本被放置在目標服務器上面,通過正常的網頁請求返回給用戶端執行。
「例如」 在觀看某個私人博客評論中插入惡意腳本,當其他用戶訪問該頁面時,腳本會執行危險操作。 反射型 XSS即Reflected XSS惡意的腳本通過 URL 參數或一些輸入的字段傳遞給目標的服務器,用戶在正常請求時會返回并且執行。
「例如」 通過鏈接中的參數后面注入腳本,當用戶點擊此鏈接時,腳本就會在用戶的瀏覽器中執行危險操作。 DOM 基于的 XSS即DOM-based XSS惡意的腳本利用 DOM(Document Object Model) 操作來修改頁面內容。這種類型的 XSS 攻擊不涉及服務器端的代碼操作,僅僅是通過客戶端插入 JavaScript 代碼實現操作。
富文本就是屬于第一種,把腳本藏在代碼中存到數據庫,然后用戶獲取時會執行。 富文本防XSS的方式?網上一大堆不明不白的方法還有各種插件可以用,但其實自己轉義一下就行,根本不需要復雜化。
當我們不做處理時傳給后臺的富文本數據是這樣的。

上面帶有標簽,甚至有src 和script 之類的操作,在里面放一些腳本真的太簡單了。
因此,我們創建富文本成功提交給后臺的時候把各種<>/\ 之類危險符號轉義成指定的字符就能防止腳本了。
如下所示,方法參數value 就是要傳遞給后臺的富文本內容。
export const getXssFilter = (value: string): string => { // 定義一個對象來存儲特殊字符及其對應的 HTML 實體 const htmlEntities = { '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''', '\\': '\', '|': '|', ';': ';', '$': '$', '%': '%', '@': '@', '(': '(', ')': ')', '+': '+', '\r': '', '\n': '', ',': ',', }; // 使用正則表達式替換所有特殊字符 let result = value.replace(/[&<>"'\\|;$%@()+,]/g, function (match) { return htmlEntities[match] || match; }); return result; };
此時傳給后臺的富文本參數是這樣的,把敏感符號全部轉義。

但展現給用戶看肯定要看正常的內容啊,這里就要把內容重新還原了,這步操作可以在前端完成,也可以在后端完成。
如果是前端完成可以用以下方法把獲取到的數據進行轉義。
// 還原特殊字符 export const setXssFilter = (input) => { return input .replace(/|/g, '|') .replace(/&/g, '&') .replace(/;/g, ';') .replace(/$/g, '$') .replace(/%/g, '%') .replace(/@/g, '@') .replace(/'/g, '\'') .replace(/"/g, '"') .replace(/\/g, '\\') .replace(/</g, '<') .replace(/>/g, '>') .replace(/(/g, '(') .replace(/)/g, ')') .replace(/+/g, '+') .replace(//g, '\r') .replace(//g, '\n') .replace(/,/g, ','); }
但是。。。。上面只適合使用于純富文本的場景,如果在普通文本的地方回顯會依然觸發危險腳本。如下所示

其實直接轉義后不還原即可解決,但由于是富文本這種情況比較特殊情況,不還原就失去文本樣式了,怎么辦??
最終解決方案是對部分可能造成XSS 攻擊的特殊字符和標簽進行轉義處理,例如:script、iframe 等。
示例代碼
export const getXssFilter = (value: string): string => { // 定義一個對象來存儲特殊字符及其對應的 HTML 實體 const htmlEntities = { '&': '&', '\'': ''', '\r': '', '\n': '', 'script': 'script', 'iframe': 'iframe', // 'img': 'img', 'object': 'ojst', 'embed': 'embed', 'on': 'on', 'javascript': 'javascript', 'expression': 'expresssion', 'video': 'video', 'audio': 'audio', 'svg': 'svg', 'background-image': 'background-image', }; // 使用正則表達式替換所有特殊字符 let result = value.replace(/[&<>"'\\|;$%@()+,]/g, function (match) { return htmlEntities[match] || match; }); // 額外處理 `script`、`iframe`、`img` 等關鍵詞 result = result.replace(/script|iframe|object|embed|on|javascript|expression|background-image/gi, function (match) { return htmlEntities[match] || match; }); return result; };
效果只會對敏感部分轉義

但這種方案不用還原轉義,因為做的針對性限制。 小結其實就是對特殊符號轉換后還原的思路,相當的簡單。如果那里寫的不好或者有更好的建議,歡迎大佬指點啦。
閱讀原文:原文鏈接
該文章在 2025/1/2 13:05:27 編輯過
| |
全部評論1 |
|
admin
2025年1月2日 13:6
點晴公司的解決代碼,用以下代碼做提交前的預處理,輸出時再臨時轉換為正常的HTML代碼,并且屏蔽掉iframe和script:
function HtmToTxt(tmpInfo){
var str_info=tmpInfo;
if (str_info+"CS"!="CS"){
str_info = str_info.replace(/=/g, "$01@");
str_info = str_info.replace(/&/g, "$02@");
str_info = str_info.replace(/%/g, "$03@");
str_info = str_info.replace(/\(/g, "$04@");
str_info = str_info.replace(/\)/g, "$05@");
str_info = str_info.replace(/>/g, "$06@");
str_info = str_info.replace(/</g, "$07@");
str_info = str_info.replace(/{/g, "$08@");
str_info = str_info.replace(/}/g, "$09@");
str_info = str_info.replace(/,/g, "$10@");
str_info = str_info.replace(/\+/g, "$11@");
str_info = str_info.replace(/\"/g, "$12@");
str_info = str_info.replace(/!/g, "$13@");
str_info = str_info.replace(/\'/g, "$14@");
str_info = str_info.replace(/;/g, "$15@");
str_info = str_info.replace(/\//g, "$16@");
str_info = str_info.replace(/-/g, "$17@");
str_info = str_info.replace(/#/g, "$18@");
str_info = str_info.replace(/\r\n/g, "$25@");
str_info = str_info.replace(/\n/g, "$25@");
str_info = str_info.replace(/ /g, "$26@");
str_info = str_info.replace(/script/gi, "$27@");
str_info = str_info.replace(/select/gi, "$28@");
str_info = str_info.replace(/update/gi, "$29@");
str_info = str_info.replace(/delete/gi, "$30@");
str_info = str_info.replace(/from/gi, "$31@");
str_info = str_info.replace(/where/gi, "$32@");
str_info = str_info.replace(/create/gi, "$33@");
str_info = str_info.replace(/alter/gi, "$34@");
str_info = str_info.replace(/drop/gi, "$35@");
str_info = str_info.replace(/truncate/gi, "$36@");
str_info = str_info.replace(/insert/gi, "$37@");
str_info = str_info.replace(/union/gi, "$38@");
str_info = str_info.replace(/exec/gi, "$39@");
str_info = str_info.replace(/\?/g, "$40@");
str_info = str_info.replace(/\[/g, "$41@");
str_info = str_info.replace(/\]/g, "$42@");
}
return str_info;
}
function TxtToHtm(tmpInfo){
var str_info=tmpInfo;
if ((str_info+"CS").indexOf("&")>-1 && (str_info+"CS").indexOf("¥")>-1){
str_info = str_info.replace(/=/g, "=");
str_info = str_info.replace(/&/g, "&");
str_info = str_info.replace(/%/g, "%");
str_info = str_info.replace(/(/g, "(");
str_info = str_info.replace(/)/g, ")");
str_info = str_info.replace(/>/g, ">");
str_info = str_info.replace(/
str_info = str_info.replace(/{/g, "{");
str_info = str_info.replace(/}/g, "}");
str_info = str_info.replace(/,/g, ",");
str_info = str_info.replace(/+/g, "+");
str_info = str_info.replace(/"/g, "\"");
str_info = str_info.replace(/!/g, "!");
str_info = str_info.replace(/'/g, "'");
str_info = str_info.replace(/;/g, ";");
str_info = str_info.replace(
str_info = str_info.replace(/-/g, "-");
str_info = str_info.replace(/#/g, "#");
str_info = str_info.replace(/ /g, " ");
str_info = str_info.replace(/ /g, " ");
str_info = str_info.replace(/ /g, " ");
str_info = str_info.replace(/"/g, "\"");
str_info = str_info.replace(/'/g, "'");
str_info = str_info.replace(/
/g, "\r\n");
str_info = str_info.replace(/
/g, "\r\n");
str_info = str_info.replace(/
/g, "\r\n");
str_info = str_info.replace(/select/g, "select");
str_info = str_info.replace(/update/g, "update");
str_info = str_info.replace(/delete/g, "delete");
str_info = str_info.replace(/from/g, "from");
str_info = str_info.replace(/where/g, "where");
str_info = str_info.replace(/create/g, "create");
str_info = str_info.replace(/alter/g, "alter");
str_info = str_info.replace(/drop/g, "drop");
str_info = str_info.replace(/truncate/g, "truncate");
str_info = str_info.replace(/insert/g, "insert");
str_info = str_info.replace(/union/g, "union");
str_info = str_info.replace(/exec/g, "exec");
str_info = str_info.replace(/\<script/gi, "<script");
str_info = str_info.replace(/\<\/script\>/gi, "<script");
str_info = str_info.replace(/\<iframe/gi, "<iframe");
str_info = str_info.replace(/\<\/iframe\>/gi, "</iframe>");
}
if ((str_info+"CS").indexOf("$")>-1 && (str_info+"CS").indexOf("@")>-1){
str_info = str_info.replace(/\$01\@/g, "=");
str_info = str_info.replace(/\$02\@/g, "&");
str_info = str_info.replace(/\$03\@/g, "%");
str_info = str_info.replace(/\$04\@/g, "(");
str_info = str_info.replace(/\$05\@/g, ")");
str_info = str_info.replace(/\$06\@/g, ">");
str_info = str_info.replace(/\$07\@/g, "<");
str_info = str_info.replace(/\$08\@/g, "{");
str_info = str_info.replace(/\$09\@/g, "}");
str_info = str_info.replace(/\$10\@/g, ",");
str_info = str_info.replace(/\$11\@/g, "+");
str_info = str_info.replace(/\$12\@/g, "\"");
str_info = str_info.replace(/\$13\@/g, "!");
str_info = str_info.replace(/\$14\@/g, "'");
str_info = str_info.replace(/\$15\@/g, ";");
str_info = str_info.replace(/\$16\@/g, "/");
str_info = str_info.replace(/\$17\@/g, "-");
str_info = str_info.replace(/\$18\@/g, "#");
str_info = str_info.replace(/\$19\@/g, " ");
str_info = str_info.replace(/\$20\@/g, " ");
str_info = str_info.replace(/\$21\@/g, "\"");
str_info = str_info.replace(/\$22\@/g, "'");
str_info = str_info.replace(/\$23\@/g, "\r\n");
str_info = str_info.replace(/\$24\@/g, "\r\n");
str_info = str_info.replace(/\$25\@/g, "\r\n");
str_info = str_info.replace(/\$26\@/g, " ");
str_info = str_info.replace(/\$27\@/g, "script");
str_info = str_info.replace(/\$28\@/g, "select");
str_info = str_info.replace(/\$29\@/g, "update");
str_info = str_info.replace(/\$30\@/g, "delete");
str_info = str_info.replace(/\$31\@/g, "from");
str_info = str_info.replace(/\$32\@/g, "where");
str_info = str_info.replace(/\$33\@/g, "create");
str_info = str_info.replace(/\$34\@/g, "alter");
str_info = str_info.replace(/\$35\@/g, "drop");
str_info = str_info.replace(/\$36\@/g, "truncate");
str_info = str_info.replace(/\$37\@/g, "insert");
str_info = str_info.replace(/\$38\@/g, "union");
str_info = str_info.replace(/\$39\@/g, "exec");
str_info = str_info.replace(/\$40\@/g, "?");
str_info = str_info.replace(/\$41\@/g, "[");
str_info = str_info.replace(/\$42\@/g, "]");
str_info = str_info.replace(/\<script/gi, "<script");
str_info = str_info.replace(/\<\/script\>/gi, "<script");
str_info = str_info.replace(/\<iframe/gi, "<iframe");
str_info = str_info.replace(/\<\/iframe\>/gi, "</iframe>");
}
return str_info;
}? 附件:HtmToTxt.rar? 該評論在 2025/1/2 13:08:25 編輯過
|
|
|