这段代码是 React 在渲染到浏览器前进行的转义 , 可以看到对浏览器有特殊含义的字符都被转义了 , 恶意代码在渲染到 HTML 全都被转成了字符串 , 如下:
// 一段恶意代码<img src=https://www.isolves.com/it/aq/sj/2020-09-24/"empty.png" onerror ="alert('xss')"> // 转义后输出到 html 中
这样就有效的防止了 XSS 攻击 。
JSX 语法JSX 实际上是一种语法糖 , Babel 会把 JSX 编译成 React.createElement() 的函数调用 , 最终返回一个 ReactElement , 以下为这几个步骤对应的代码:
// JSXconst element = ( <h1 className="greeting"> Hello, world! </h1>);// 通过 babel 编译后的代码const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!');// React.createElement() 方法返回的 ReactElementconst element = { $$typeof: Symbol('react.element'), type: 'h1', key: null, props: { children: 'Hello, world!', className: 'greeting' } ...}
我们可以看到 , 最终渲染的内容是在 Children 属性中 , 那了解了 JSX 的原理后 , 我们来试试能否通过构造特殊的 Children 进行 XSS 注入 , 来看下面一段代码:
const storedData = `{ "ref":null, "type":"body", "props":{ "dangerouslySetInnerHTML":{ "__html":"<img src=https://www.isolves.com/it/aq/sj/2020-09-24/"empty.png" onerror ="alert('xss')"/>" } }}`;// 转成 JSONconst parsedData = JSON.parse(storedData);// 将数据渲染到页面render () { return {parsedData} ; }
这段代码中 , 运行后会报以下错误 , 提示不是有效的 ReactChild
Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {ref, type, props}). If you meant to render a collection of children, use an array instead.
那究竟是哪里出问题了?我们看一下 ReactElement 的源码:
const symbolFor = Symbol.for;REACT_ELEMENT_TYPE = symbolFor('react.element');const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // 这个 tag 唯一标识了此为 ReactElement $$typeof: REACT_ELEMENT_TYPE, // 元素的内置属性 type: type, key: key, ref: ref, props: props, // 记录创建此元素的组件 _owner: owner, }; ... return element;}
注意到其中有个属性是 $$typeof , 它是用来标记此对象是一个 ReactElement , React 在进行渲染前会通过此属性进行校验 , 校验不通过将会抛出上面的错误 。React 利用这个属性来防止通过构造特殊的 Children 来进行的 XSS 攻击 , 原因是 $$typeof 是个 Symbol 类型 , 进行 JSON 转换后会 Symbol 值会丢失 , 无法在前后端进行传输 。如果用户提交了特殊的 Children , 也无法进行渲染 , 利用此特性 , 可以防止存储型的 XSS 攻击 。
在 React 中可引起漏洞的一些写法使用 dangerouslySetInnerHTMLdangerouslySetInnerHTML 是 React 为浏览器 DOM 提供 innerHTML 的替换方案 。通常来讲 , 使用代码直接设置 HTML 存在风险 , 因为很容易使用户暴露在 XSS 攻击下 , 因为当使用 dangerouslySetInnerHTML 时 , React 将不会对输入进行任何处理并直接渲染到 HTML 中 , 如果攻击者在 dangerouslySetInnerHTML 传入了恶意代码 , 那么浏览器将会运行恶意代码 。看下源码:
function getNonChildrenInnerMarkup(props) { const innerHTML = props.dangerouslySetInnerHTML; // 有dangerouslySetInnerHTML属性 , 会不经转义就渲染__html的内容 if (innerHTML != null) { if (innerHTML.__html != null) { return innerHTML.__html; } } else { const content = props.children; if (typeof content === 'string' || typeof content === 'number') { return escapeTextForBrowser(content); } } return null;}
推荐阅读
- 正史中的关羽真有这么厉害吗 关羽是哪部作品的人物
- 三国干涉还辽中的三国指的是
- 三国中的贾诩是什么人物 贾诩是一个怎样的人
- 历史名著中的茶文化,茶文化之茶与神农历史记载
- 探究!一个数据包在网络中的心路历程
- 三十六计中的围魏救赵是什么意思 36计第二计围魏救赵的故事
- 大肠杆菌特效药在生活当中的作用是什么
- 儒家孝悌之道 儒学中的孝
- 曹操在官渡之战和赤壁之战中的不同结局给我们什么启示 官渡之战曹操胜,赤壁之战曹操败,启示
- 靈脂酒的功效与作用