@blueking/xss-filter
Version:
蓝鲸 XSS 过滤工具
342 lines (291 loc) • 11.1 kB
Markdown
# 蓝鲸 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, "<").replace(/>/g, ">");,不建议修改
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,
}
```