解決layui的table組件data-content未轉義導致的注入問題。

本文更新不及時,建議到原文地址瀏覽:解決layui的table組件data-content未轉義導致的注入問題

layui是一套非常不錯的後臺管理系統UI框架,其簡約並具有系統感的設計贏得了許多開發者的青睞。不過我在使用layui的動態表格組件時遇到了一個問題,當數據中存在未轉義的數據時,表格在渲染時會備份一個數據在dom屬性上,而此數據未進行html轉義導致存在注入攻擊問題。我通過源碼閱讀,瞭解到了爲什麼此問題的原因,並完美修復這個問題。下面將進行詳細介紹。

一、問題重現

並不是所有情況都會有這個問題,只有當你在爲某一個字段設置了templet模板。不管是一個方法,還是一個模板語法,layui纔會在此單元表格上新增一個data-content屬性。而此時,如果你的數據又恰巧包含了html能解析的內容,比如:當你有一份形如下面的數據要顯示到table組件裏面時,由於未對data-content屬性進行轉義賦值,導致你的頁面可能與預期展示不一樣:

var data = [
    {id: 1, name: "Tom\"> <b οnclick=\"alert(66)\">呵呵</b>"},
    {id: 2, name: "Bob\"> <b οnclick=\"alert('你完了')\">完蛋了</b>"}
];

把這份數據通過設定templet渲染到table組件。得到下面的效果:

{"class":"useDialog"}

不僅僅是被攻擊者達到了意圖,而且點擊它們還會彈出提示信息。再來看一看dom結構被這個數據搞成了什麼樣:

{"class":"useDialog"}

可以看到,由於data-content沒有轉義,導致界面dom結構凌亂,並且丟失了前端正常的功能。標記1處就是因爲沒有轉義,數據裏的html被應用到了dom結構裏,儘管我們在templet裏進行了轉義使得標記2正常顯示數據,但依然沒能讓data-content進行轉義。而如果這是一個十分惡意的攻擊,那麼這個網站應該在這裏就被攻破了。我們當然不能允許這樣的事情發生。畢竟要恰飯的。

二、修復問題

修復問題的辦法有好幾種或者更多,這裏推薦幾個比較簡單的解決方案。

1、後端修復

後端在數據庫中將數據查詢出來後,就對有些敏感字段先進行一下html轉義,讓前端接收到的數據已經是一個無需擔心注入的安全字符串。那麼這樣就可以放心使用了。要在後端進行轉義,下面提供一個示例代碼:

String badStr = "<script>alert(66)</script>"
// 使用 Spring 提供的轉換工具
String safeStr = HtmlUtils.htmlEscape(badStr);

// 現在,就可以放心的使用 safeStr 傳遞給前端了。

2、前端修改table組件代碼

如果後端代碼被多處使用,而有些"端"並不需要轉換,只針對部分“端”才需要,那如果後端修復不是很方便的話,前端就不得不自行修復了。在前面也瞭解到,即便我們在templet裏面使用了轉義輸出,也依然阻止不了data-content的發生。看來data-content的賦值是我們控制不了的了。

所以,不妨直接打開table組件代碼,搜索data-content字段,直接定位這個賦值代碼。下面代碼截取自layui項目table組件[提交版本號5904e5b1344efa661b53f1cbd2a5d0e5b12ea4ef]的部分代碼:

that.eachCols(function(i3, item3){
  var field = item3.field || i3
  ,key = options.index + '-' + item3.key
  ,content = item1[field];

  if(content === undefined || content === null) content = '';
  if(item3.colGroup) return;

  //td內容
  var td = ['<td data-field="'+ field +'" data-key="'+ key +'" '+ function(){ //追加各種屬性
    var attr = [];
    if(item3.edit) attr.push('data-edit="'+ item3.edit +'"'); //是否允許單元格編輯
    if(item3.align) attr.push('align="'+ item3.align +'"'); //對齊方式
    if(item3.templet) attr.push('data-content="'+ content +'"'); //自定義模板
    if(item3.toolbar) attr.push('data-off="true"'); //行工具列關閉單元格事件
    if(item3.event) attr.push('lay-event="'+ item3.event +'"'); //自定義事件
    if(item3.style) attr.push('style="'+ item3.style +'"'); //自定義樣式
    if(item3.minWidth) attr.push('data-minwidth="'+ item3.minWidth +'"'); //單元格最小寬度
    return attr.join(' ');
  }() +' class="'+ function(){ //追加樣式

上述代碼中,第14行代碼處可以看到,這裏直接對data-content進行賦值,完全沒有進行轉義的行爲。所以,我們現在只需要加上轉義的代碼就好了:

// 原來的代碼
if(item3.templet) attr.push('data-content="'+ content +'"'); //自定義模板

// 修改爲:
if(item3.templet) attr.push('data-content="'+ laytpl("{{=d.t}}").render({t:content}) +'"'); //自定義模板

可以看到,上述代碼中,藉助了laytpl模板引擎,非常方便的就實現了數據轉義後賦值到data-content裏面,這樣,就不會再出現注入攻擊了。修改後,我們看看現在的效果以及dom結構:

{"class":"useDialog"}

現在不論是界面顯示,還是dom結構,都已經顯示正常。data-content也進行了轉義顯示,沒有任何問題。

三、注意

上述在前端修復的方案,其修改的代碼是layui的源代碼,並非你下載的layui代碼,但你可以修改好後,將修改好了的table組件代碼覆蓋到你下載的layui對應的組件,也可以有效果。你也可以直接修改下載的源代碼。不過下載的代碼是經過編譯縮小的,所有字段都變成了單個字母不便於識別,修改時要小心,找準位置和代碼,使用同樣的方式進行修復,不過要注意,這時laytpl可能就是另一個字母變量了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章