UNPKG

@blueking/xss-filter

Version:

蓝鲸 XSS 过滤工具

342 lines (291 loc) 11.1 kB
# 蓝鲸 XSS 过滤工具 一个基于 [xss](https://www.npmjs.com/package/xss) 库封装轻量级的工具,用于过滤字符串或 JSON 中 HTML 内容片段的 XSS(跨站脚本)攻击。支持 Vue(Vue 2 和 Vue 3)指令,或者独立使用过滤方法。 ## 安装 ```bash npm install @blueking/xss-filter ``` ## 使用 ### Vue 指令 #### 注册指令 ```javascript // Vue 3 import { createApp } from 'vue'; import { BkXssFilterDirective } from '@blueking/xss-filter'; import App from './app.vue'; createApp(App).use(BkXssFilterDirective).mount('#app'); // Vue 2 import Vue from 'vue'; import { BkXssFilterDirective } from '@blueking/xss-filter'; Vue.use(BkXssFilterDirective); new Vue({ render: h => h(App), }).$mount('#app'); ``` #### 在模板中使用 ```html <!-- 默认 --> <div v-bk-xss-html="userContent"></div> <!-- 纯文本模式,只保留html内容中标签内的纯文本 --> <div v-bk-xss-html.plain="userContent"></div> <!-- 传入对象模式(自定义选项) --> <div v-bk-xss-html="{ content: userContent, options: { whiteList: { p: [] } } }"></div> ``` #### 配置全局选项 以 vue 3 项目为例: ```javascript import { createApp } from 'vue'; import App from './App.vue'; import { BkXssFilterDirective } from '@blueking/xss-filter'; import type { XssDirectiveConfig } from '@blueking/xss-filter'; const app = createApp(App); // 安装指令并配置全局选项 app.use(BkXssFilterDirective, { defaultOptions: { // 全局 XSS 配置选项 whiteList: { a: ['href', 'title', 'target', 'rel'] } }, plainModeByDefault: false // 默认使用富文本模式 } as XssDirectiveConfig); app.mount('#app'); ``` ### 独立使用过滤方法 从模块中导入函数: ```javascript import { filterXss, deepFilterXss, filterPlainText } from '@blueking/xss-filter'; // 过滤掉所有非白名单内的 HTML 标签和属性 const safeString = filterXss('<script>alert("XSS")</script>'); // 过滤掉所有 HTML 标签和属性,只保留标签内的纯文本 const safeText = filterPlainText('<script>alert("XSS")</script>'); // 过滤掉 JSON 中 HTML 片段,默认采用 filterPlainText 方法只保留 HTML 标签内的纯文本,可配置为filterXss并传入自定义过滤选项 const safeObject = deepFilterXss({ html: '<script>alert("XSS")</script>' }); ``` ## API 参考 ### 函数 - `filterXss(html: string, options?: XssOptions): string` 过滤掉所有非白名单内的 HTML 标签和属性。 - `filterPlainText(text: string, options?: XssOptions): string` 过滤掉所有 HTML 标签和属性,只保留标签内的纯文本。 - `deepFilterXss(obj: T, filterFn?: FilterFunction = filterPlainText, options?: XssOptions): T` 过滤掉 JSON 中 HTML 片段,默认采用filterPlainText方法只保留 HTML 标签内的纯文本,可配置为filterXss并传入自定义过滤选项。 ```javascript interface XssOptions { // 白名单,不在白名单上的标签将被过滤,不在白名单上的属性也会被过滤 whiteList?: Record<string, string[]>; // 去掉不在白名单上的标签,但保留标签里的纯文本内容 stripIgnoreTag?: boolean; // 去掉不在白名单上的标签及里面的纯文本内容 stripIgnoreTagBody?: boolean | string[]; // 去掉 HTML 备注 allowCommentTag?: boolean; // 自定义匹配到标签时的处理方法 // tag是当前的标签名称,html是该标签的的html。如果返回字符串,则当前标签将被替换为该字符串;如果不返回任何值,则使用默认处理方法(在白名单上:通过onTagAttr来过滤属性;不在白名单上:通过onIgnoreTag指定) onTag?: (tag: string, html: string, options: Record<string, boolean | number>) => string | undefined; // 自定义匹配到标签的属性时的处理方法 // tag是当前的标签名称,name是当前属性名称,value是属性值,isWhiteAttr表示是否为白名单上属性 // 如果返回字符串,则当前属性值会被替换为该字符串;如果不返回任何值,则使用默认处理方法(在白名单上:调用safeAttrValue来过滤属性值,并输出该属性;不在白名单上:通过onIgnoreTagAttr指定) onTagAttr?: (tag: string, name: string, value: string, isWhiteAttr: boolean) => string | undefined; // 自定义匹配到不在白名单上的标签时的处理方法 // 参数与onTag相同。如果返回字符串,则当前标签将被替换为该字符串;如果不返回任何值,则使用默认处理方法(通过escape指定) onIgnoreTag?: (tag: string, html: string, options: Record<string, boolean | number>) => string | undefined; // 自定义匹配到不在白名单上的标签时的处理方法 // 参数与onTagAttr相同。如果返回字符串,则当前属性值会被替换为该字符串;如果不返回任何值,则使用默认处理方法(删除该属性) onIgnoreTagAttr?: (tag: string, name: string, value: string, isWhiteAttr: boolean) => string | undefined; // 自定义 HTML 转义函数 // 默认返回 html.replace(/</g, "&lt;").replace(/>/g, "&gt;");,不建议修改 escapeHtml?: (html: string) => string; // 自定义标签属性值的转义函数 // 参数与onTagAttr相同。返回一个值表示该属性值 safeAttrValue?: (tag: string, name: string, value: string) => string | undefined; css?: boolean | CssFilterOptions; imgSrcMode?: 'base64' | string[] | 'none'; // 支持三种模式:base64、信任域名、不限制 } // style属性值过滤接口 interface CssFilterOptions { // css属性白名单 whiteList?: { [key: string]: boolean | ((value: string) => boolean) | RegExp; }; // 自定义匹配到在白名单上的属性时的处理方法 // 返回字符串表示覆盖此段CSS,不返回任何值表示使用默认生成方法,即name:value onAttr?: (name: string, value: string, options) => string | undefined; // 自定义匹配到不在白名单上的属性时的处理方法 // 返回字符串表示覆盖此段CSS,不返回任何值表示使用默认生成方法,即去掉此段CSS onIgnoreAttr?: (name: string, value: string, options) => string | undefined; } ``` ## 内置默认的白名单配置 ```javascript // 标签属性白名单 { "a": ["href", "title", "target", "rel", "id", "class", "style"], "aside": ["id", "class", "style"], "audio": ["src", "autoplay", "controls", "loop", "muted"], "b": [], "blockquote": ["id", "class", "style"], "body": ["id", "class", "style"], "br": [], "button": ["type", "id", "class", "style"], "canvas": ["width", "height"], "code": ["id", "class"], "div": ["id", "class", "style"], "em": [], "h1": ["id", "class", "style"], "h2": ["id", "class", "style"], "h3": ["id", "class", "style"], "h4": ["id", "class", "style"], "h5": ["id", "class", "style"], "h6": ["id", "class", "style"], "hr": [], "i": [], "img": ["src", "alt", "title", "width", "height", "id", "class", "style"], "li": ["id", "class", "style"], "nav": ["id", "class", "style"], "ol": ["id", "class", "style"], "p": ["id", "class", "style"], "pre": ["class"], "s": [], "section": ["id", "class", "style"], "span": ["id", "class", "style"], "strong": [], "style": [], "table": ["id", "class", "style", "border", "cellspacing", "cellpadding"], "textarea": ["id", "class", "style"], "tbody": ["id", "class", "style"], "td": ["id", "class", "style", "colspan", "rowspan"], "th": ["id", "class", "style", "colspan", "rowspan"], "thead": ["id", "class", "style"], "tr": ["id", "class", "style"], "u": [], "ul": ["id", "class", "style"], "video": ["src", "autoplay", "controls", "loop", "muted"] } ``` ```javascript // style属性值白名单 { 'background': true, 'background-attachment': true, 'background-clip': true, 'background-color': true, 'background-image': true, 'background-origin': true, 'background-position': true, 'background-repeat': true, 'background-size': true, 'border': true, 'border-bottom': true, 'border-bottom-color': true, 'border-bottom-left-radius': true, 'border-bottom-right-radius': true, 'border-bottom-style': true, 'border-bottom-width': true, 'border-collapse': true, 'border-color': true, 'border-image': true, 'border-image-outset': true, 'border-image-repeat': true, 'border-image-slice': true, 'border-image-source': true, 'border-image-width': true, 'border-left': true, 'border-left-color': true, 'border-left-style': true, 'border-left-width': true, 'border-radius': true, 'border-right': true, 'border-right-color': true, 'border-right-style': true, 'border-right-width': true, 'border-spacing': true, 'border-style': true, 'border-top': true, 'border-top-color': true, 'border-top-left-radius': true, 'border-top-right-radius': true, 'border-top-style': true, 'border-top-width': true, 'border-width': true, 'box-decoration-break': true, 'box-shadow': true, 'box-sizing': true, 'box-snap': true, 'box-suppress': true, 'break-after': true, 'break-before': true, 'break-inside': true, 'clear': true, 'color': true, 'color-interpolation-filters': true, 'display': true, 'display-inside': true, 'display-list': true, 'display-outside': true, 'font': true, 'font-family': true, 'font-feature-settings': true, 'font-kerning': true, 'font-language-override': true, 'font-size': true, 'font-size-adjust': true, 'font-stretch': true, 'font-style': true, 'font-synthesis': true, 'font-variant': true, 'font-variant-alternates': true, 'font-variant-caps': true, 'font-variant-east-asian': true, 'font-variant-ligatures': true, 'font-variant-numeric': true, 'font-variant-position': true, 'font-weight': true, 'height': true, 'letter-spacing': true, 'lighting-color': true, 'list-style': true, 'list-style-image': true, 'list-style-position': true, 'list-style-type': true, 'margin': true, 'margin-bottom': true, 'margin-left': true, 'margin-right': true, 'margin-top': true, 'max-height': true, 'max-width': true, 'min-height': true, 'min-width': true, 'padding': true, 'padding-bottom': true, 'padding-left': true, 'padding-right': true, 'padding-top': true, 'text-align': true, 'text-align-last': true, 'text-combine-upright': true, 'text-decoration': true, 'text-decoration-color': true, 'text-decoration-line': true, 'text-decoration-skip': true, 'text-decoration-style': true, 'text-emphasis': true, 'text-emphasis-color': true, 'text-emphasis-position': true, 'text-emphasis-style': true, 'text-height': true, 'text-indent': true, 'text-justify': true, 'text-orientation': true, 'text-overflow': true, 'text-shadow': true, 'text-space-collapse': true, 'text-transform': true, 'text-underline-position': true, 'text-wrap': true, 'width': true, 'word-break': true, 'word-spacing': true, 'word-wrap': true, } ```