安全技术
用户输入净化
对于像 ion-alert 这样的组件,开发者可以允许自定义或用户提供的内容。这些内容可以是纯文本或 HTML,应被视为不可信内容。与处理任何不可信输入一样,在使用之前对其进行净化至关重要。特别是,未经净化就直接使用 innerHTML 等操作,会为恶意攻击者提供输入恶意内容并可能发起跨站脚本攻击(XSS)的攻击途径。
Ionic 内置了为其提供的组件的基本净化实现。然而,这并非全面的解决方案。确保所有传递的数据都经过净化是开发者的责任。不同的框架有不同的用户输入净化解决方案,因此开发者应熟悉其特定框架所提供的功能。
对于不使用框架的开发者,或框架未提供所需净化方法的开发者,我们推荐使用 sanitize-html。这个包提供了一个简单的 HTML 净化器,允许开发者指定应用中允许的确切标签和属性。
Angular
Angular 内置了 DomSanitizer 类。这通过确保值在 DOM 中使用是安全的,有助于防止 XSS 问题。默认情况下,Angular 会标记任何它认为不安全的数值。例如,以下链接会被 Angular 标记为不安全,因为它试图执行一些 JavaScript。
public myUrl: string = 'javascript:alert("oh no!")';
...
<a [href]="myUrl">Click Me!</a>
要了解更多关于 Angular 提供的内置保护措施,请参阅 Angular 安全指南。
React
React DOM 在渲染之前通过将值转换为字符串来转义嵌入在 JSX 中的值。例如,以下代码是安全的,因为 name 在渲染前被转换为字符串:
const name = values.name;
const element = <h1>Hello, {name}!</h1>;
然而,这并不能阻止有人将 JavaScript 注入到锚元素的 href 属性等位置。以下是不安全的,可能会允许 XSS 攻击发生:
const userInput = 'javascript:alert("Oh no!")';
const element = <a href={userInput}>Click Me!</a>;
如果开发者需要实现更全面的净化,可以使用 sanitize-html 包。
Vue
Vue 本身不提供任何类型的净化方法。建议开发者使用诸如 sanitize-html 这样的包。
要了解更多关于绑定到 v-html 等指令的安全建议,请参阅 Vue 语法指南。
通过 innerHTML 启用自定义 HTML 解析
ion-alert、ion-infinite-scroll-content、ion-loading、ion-refresher-content 和 ion-toast 可以接受自定义 HTML 字符串作为某些属性的值。这些字符串使用 innerHTML 添加到 DOM 中,必须由开发者进行适当净化。默认情况下,此行为是禁用的,这意味着传递给受影响组件的值将始终被解释为纯文本。开发者可以通过在 IonicConfig 中设置 innerHTMLTemplatesEnabled: true 来启用此自定义 HTML 行为。
绕过内置净化器
对于希望向 ion-toast 等组件添加复杂 HTML 的开发者,他们将需要绕过 Ionic Framework 内置的净化器。开发者可以全局禁用净化器,也可以根据具体情况绕过它。
绕过净化功能可能使您的应用容易受到 XSS 攻击。禁用净化器时请务必格外小心。
通过配置禁用净化器
Ionic Framework 提供了一个名为 sanitizerEnabled 的应用配置选项,默认设置为 true。将此值设置为 false 可全局禁用 Ionic Framework 的内置净化器。请注意,这不会禁用其他框架(如 Angular)提供的任何净化功能。
按具体情况绕过净化器
开发者也可以选择在某些情况下绕过净化器。Ionic Framework 提供了 IonicSafeString 类,允许开发者做到这一点。
为了绕过净化器并在相关的 Ionic 组件中使用未净化的自定义 HTML,必须在 Ionic 配置中将 innerHTMLTemplatesEnabled 设置为 true。
如果 innerHTMLTemplatesEnabled 设置为 false,则不应使用 IonicSafeString。
有关更多信息,请参阅 启用自定义 HTML 解析。
用法
- Angular
- Angular (独立版)
- JavaScript
- React
import { IonicSafeString, ToastController } from '@ionic/angular';
...
constructor(private toastController: ToastController) {}
async presentToast() {
const toast = await this.toastController.create({
message: new IonicSafeString('<ion-button>Hello!</ion-button>'),
duration: 2000
});
toast.present();
}
import { IonicSafeString, ToastController } from '@ionic/angular/standalone';
...
constructor(private toastController: ToastController) {}
async presentToast() {
const toast = await this.toastController.create({
message: new IonicSafeString('<ion-button>Hello!</ion-button>'),
duration: 2000
});
toast.present();
}
import { IonicSafeString } from '@ionic/core';
...
const async presentToast = () => {
const toast = document.createElement('ion-toast');
toast.message = new IonicSafeString('<ion-button>Hello!</ion-button>');
toast.duration = 2000;
document.body.appendChild(toast);
return toast.present();
}
import React, { useState } from 'react';
import { Animation, IonButton, IonContent, IonicSafeString, IonToast } from '@ionic/react';
export const ToastExample: React.FC = () => {
const [showToast, setShowToast] = useState(false);
return (
<IonContent>
<IonButton onClick={() => setShowToast(true)} expand="block">显示 Toast</IonButton>
<IonToast
isOpen={showToast}
onDidDismiss={() => setShowToast(false)}
message={new IonicSafeString('<ion-button>Hello!</ion-button>')}
duration={2000}
/>
</IonContent>
)
};