富文本输入框控件没做安全处理被XSS攻击了
|
admin
2025年1月1日 13:30
本文热度 1110
|
前言相信很多前端小伙伴项目中都用到了富文本,但你们有没有做防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 编辑过
|
|
|