本文介紹了java開發流行框架struts2以及webwork的一些安全缺陷,並舉例說明框架自己以及開發人員使用框架時,所產生的種種安全問題,以及做者挖掘框架安全漏洞的一些心得體會。
推薦如下人羣閱讀:
瞭解java開發
瞭解框架開發
瞭解web application安全
「網絡安全愛好者」
正文:
有了這些framework,讓開發人員更加快速的開發出代碼,也讓代碼很是具備可擴展性,那些分層架構的思想,更是深刻人心。這些也大大影響了安全代碼審覈,曾提出「分層審覈代碼」的思想,好比在DAO層專門檢查sql注入,在view層檢查xss等。這些框架都有本身的層級,本次文章主要講的是struts這個框架的相關安全問題,也會有小部分涉及到struts後面的DAO層。
而struts這個框架更新佔有市場份額極大的一個框架,它在各個層級中,位於如圖所示位置:
能夠看到struts在web應用中,負責處理接收用戶數據,調用業務處理,以及展現數據的工做。因此本文把struts的功能分爲controller層和view層,controller層來完成接收用戶數據,分發用戶請求,而view專門用於展現數據。
一個單獨的struts,是不合邏輯的,由於架構師一般喜歡多種框架集合,讓它們各自負責某一層的處理。研究一個框架的安全問題,不能僅僅站在框架的角度,還應該充分考慮到開發人員是如何使用這些框架的,他們最喜歡寫什麼樣的代碼,這樣才能還原一個正常的、完整的web應用場景。
從搜索結果看,互聯網中,絕大多數教程推薦struts+hibernate+spring這樣的黃金組合,那麼,我假設有一個應用使用了這個組合,以struts爲重點,站在攻擊者的角度,層層分析struts的設計缺陷。
Struts2
開發回顧與簡單學習
爲了讓你們回顧或者學習一下struts2,咱們一塊兒來創建一個action、jsp頁面,作一個接收用戶輸入,以後處理一下,再展現出來給用戶的過程,精通struts2的同窗能夠跳過此步。
-------------------------------------struts
回顧start
首先創建action,叫作AaaaAction:
public class AaaaAction extends ActionSupport{javascript
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String execute(){
System.out.println("exe");
return SUCCESS;
}
public String bbb(){
System.out.println("bbbbb");
return SUCCESS;
}
}
請注意execute這個方法,讓用戶輸入action的地址後,默認會訪問這個方法。
以後配置struts.xml文件
user/aaa.jspphp
配置這個文件後,當用戶輸入
http://www.inbreak.net/app/aaaaaaa.action
的時候,struts會負責讓AaaaAction中的execute方法處理用戶請求。
處理以後,該方法返回「return SUCCESS;」,struts又負責找到result的name是seccuess所指向的jsp頁面。把該頁面解析後,返回給用戶。html
而用戶看到的就是aaa.jsp頁面的html代碼。
struts2
繼承了webwork的全部優勢,其實等因而webwork的升級,若是開發人員想讓用戶直接訪問action中的某方法,而不是訪問默認的execute方法,只要定義一個方法叫作bbb,而且是public的,用戶就能夠直接輸入
http://www.inbreak.net/app/aaaaaaa!bbb.action
直接訪問了bbb方法。
那request中的參數若是接收呢?struts2中,這個過程被包裝了起來,使用很是方便,只要在action中定義一個屬性,叫作public String name;。而後加入getName和setName方法,就能夠像正常使用屬性同樣,接收到用戶傳遞過來的變量。不管是get請求仍是post請求,均可以使用這種方式接收用戶輸入。
整個過程就如此簡單,如今你們對流程有了瞭解,咱們就開始討論正文,若是仍是想了解更多,請自行google。
----------------------------------struts
回顧end
Struts2
安全缺陷
能夠看到struts2在數據流向方面,有兩個重點,一個是進入(in),一個是輸出(out)。而我在作漏洞挖掘的思路,也是跟着這個數據的流程,開始分析的,下面咱們就開始讓數據進入。
Action
屬性默認值能夠被覆蓋缺陷:
在平常的java項目中,咱們常常會遇到保存一個新的對象(好比註冊一個用戶),而後給這個對象賦予一些用戶提交上來的屬性值,在這裏,只須要定義一個對象類:
public class User {java
private Long id=0l;
private String name;
private String pass;
private Integer type=1;
。。。下面的
get和set方法代碼略
}
定義後,在action中,添加一個屬性
User reguser;
用戶註冊的頁面代碼以下:
當用戶提交這個form到action中後,struts2會負責自動映射reguser.name的值到reguser的相關屬性(name)中,因此在execute這個方法中,就可使用reguser.getName()拿到用戶提交的reguser.name的值。因此咱們下面的代碼就很簡單了:
public String execute(){
add(user);
add
方法,更簡單了,由於咱們項目中集成了hibernate,這個框架自動映射user類中的各個屬性,自動組成insert語句。咱們只要在add中調用session.save(user);就能夠保存用戶到數據庫中。
前文提到那麼多「簡單」兩個字,難道這些過程都是安全的而他給咱們僅僅帶來了方便麼?
struts2
只負責映射全部對象,他提供了form驗證,也只能驗證form中屬性值的內容,好比email格式等,並不能約束用戶提交其餘屬性上來,因而這就變成了十分危險的功能。
當User中有個屬性type,表明User是否管理員時(1爲普通用戶,2爲管理員),麻煩來了,攻擊者在原來的註冊表單中,新加入一個input,叫作
而後輸入值是2,把這個值一塊兒交給action。在這個流程中,這個值,固然也會被自動帶到數據庫中,向下處理的邏輯中,這個用戶,就已經變成管理員了。
當你看到了一個struts2或者webwork的應用,能夠嘗試使用屬性攻擊,修改當前表單,裏面有全部你猜想到的屬性,一併提交上來,就可能會影響整個邏輯,達到攻擊目的。文中僅僅是一個例子,事實上,在數據傳遞的過程當中,能夠任意覆蓋數據的默認值,原本就是一個危險的缺陷,而struts2和webwork這兩個框架僅僅看到了它帶來的好處,忽略了這方面基於安全性的考慮,僅僅關注了用戶提交數據的正確性。對比在沒有struts2這個功能的時候,咱們卻須要在action中一個一個的把須要的變量,從用戶提交的request中解出來,一個一個處理,不可能出現這種安全問題。如今它包裝了這個過程,自覺得很方面,卻出了嚴重問題。
Action
中的方法被暴力猜解缺陷:
前文提到,有一種方法可讓用戶訪問action時,不訪問默認的execute方法,而是直接訪問其餘action中的方法,條件是在action中,寫一個public的方法。開發人員若是須要作一個登錄後,展現全部用戶列表的功能,而他的一個「解耦合」的開發習慣,將在這裏致使安全缺陷。
定義一個以下的action
public class Userlogin extends ActionSupport{web
private String uname="";
private String upwd;
private List list;
//getter and setter 方法略
public String login(){
if(uname!=null&&upwd!=null&&uname.equals("kxlzx")&&upwd.equals("pass"))
{//if login success
return list();
}
return false;
}
public String list(){
list.add("kxlzx");list.add("kxlzx1");list.add("kxlzx2");list.add("kxlzx3");
return "list";
}
}
Userlogin
中,由於list這個功能(顯示全部用戶列表),實際上是一個通用的功能,很容易被其餘地方調用,因此開發人員把它單獨寫成了一個方法。
當用戶登錄的時候,打開
http://www.inbreak.net/app/userlogin!login.action
來到了用戶的登錄頁面,能夠看到,只有用戶輸入正確的用戶名和密碼,才能最終調用list()方法,顯示結果。
可是struts2把全部public的方法都暴露了出去,致使如今用戶輸入了
http://www.inbreak.net/app/userlogin!list.action
用戶訪問這個連接後,struts2調用list方法,而後返回結果給用戶,因此沒有登錄,就顯示了全部用戶信息,直接繞過了login中的登錄驗證。
在沒有struts2的時候,咱們要在servlet的doget或者dopost方法中,寫if判斷等代碼,才能讓用戶調用其餘servlet中的方法,如今看來其實這也是一種保護措施。而如今struts2爲了方便開發,把全部的public方法統一映射了出去,致使開發把一個常用的功能,習慣寫成一個public的方法,如今竟然成了嚴重漏洞。
struts2
的action屬性設計缺陷:
再回頭看看咱們在action中的屬性定義,你會發現,如今他們都成了漏洞,由於struts2規定屬性的get和set方法,都必須是public的。
那麼咱們定義了
private String name;ajax
public String getName() {spring
return name;
}
public void setName(String name) {sql
this.name = name;
}
這段代碼的時候,實際上,是寫了兩個public的方法。
那這兩個表面上沒有任何實質含義的方法,會有什麼安全隱患呢?
這須要和前文聯繫起來,前文提到,咱們在struts.xml文件中,定義以下:
user/userlist.jsp數據庫
user/addUser.jspjson
user/added.jsp
user/false.jsp
這段代碼含義是,UserAction中,任何一個方法執行後,若是返回的是success這個字符串, 就會把user/userlist.jsp返回給用戶。
若是返回是addUser,就會把user/addUser.jsp返回給用戶。
如今UserAction是管理用戶的頁面,在咱們的系統中,有普通管理員和超級管理員,他們的區別是普通管理員能夠查看用戶,可是不能添加一個用戶。
因此,咱們在UserAction中,寫了
public String addUser(){
if(true){ //事實上這裏是個超級管理員的判斷,我偷懶了。
return "false";
}
return "addUser";
}
這個方法的代碼判斷了不容許普通管理員訪問,可是user/addUser.jsp這個jsp頁面中並無這個判斷邏輯。由於開發認爲只有返回addUser的時候,纔會來到這個頁面,而要返回addUser,則必須經過超級管理員的驗證。
那咱們能讓一個方法返回addUser麼?固然能夠!
http://www.inbreak.net/app/user!getUsername.action?username=addUser
這個連接,struts2會怎麼處理呢?
他會找struts.xml中,對應段路徑user,因而找到了對應的處理Action(net.inbreak.UserAction),因爲路徑中有了「!getUsername」,因而就去找這個Action中的getUsername這個方法,很明顯,這個方法實際上是username這個屬性的get方法,若是你要讓Action接收用戶提交的username,你就必需要定義這個方法。
那這個方法會返回什麼呢?會返回action的字段username的值!哈哈!username用戶已經提交給action了,連接後面寫着「?username=addUser」,struts2把這個值賦予了action中的username屬性。那這裏返回的固然就是「addUser」!
一系列巧合後,致使如今給用戶返回了user/addUser.jsp頁面,這是一個添加用戶的表單頁面,而且用戶沒有去走驗證是否爲超級管理員這一步。
如今用戶看到了一個添加用戶的頁面,他有兩種攻擊思路:
1
,直接提交,若是處理用戶提交的那個action沒有再次判斷用戶身份,那就提交成功了。
2
,若是他判斷了用戶身份,咱們還能夠csrf他,由於咱們知道了這個action的地址,和它須要的參數!
因爲struts2的action和jsp文件分離,致使開發人員每每會在action的方法中,執行權限判斷,而jsp頁面中並無再次執行這個判斷,他覺得action判斷就夠了。而恰恰action的屬性,給咱們帶來了一個可自定義返回result的方法,致使咱們能夠繞過action訪問jsp頁面。
Struts2
的那些result類型缺陷(redirect):
剛纔咱們領教了struts2給咱們帶來那些屬性的好處,如今咱們再日後走一步,研究Action方法的返回結果。
其實並非只由String類型的返回結果,struts2還有其餘類型的返回,好比「redirect」類型。
user/false.jsp
${redirecturl}
這段代碼,你們惟一可能看不懂的,就是type="redirect"了。
這是一個url redirect的方式,struts2爲了方便你們開發,把「自定義302跳轉到其餘url」這種方式給包裝了起來。只要如上定義,咱們就能夠在action中寫方法:
public String redirect() {
return "redir";
}
而後定義屬性
private String redirecturl;
當用戶打開
http://www.inbreak.net/app/test!redirect.action?redirecturl=/a.jsp
的時候,就會302跳轉到
http://www.inbreak.net/app/a.jsp
這是很常見的url跳轉應用,在struts2中,如上配置一下,就能夠實現。
相信明眼人都看出來了,很明顯這裏存在url跳轉漏洞,若是用戶輸入了
http://www.inbreak.net/app/test!redirect.action?redirecturl=http://www.ph4nt0m.org
就會跳轉到http://www.ph4nt0m.org這個釣魚網站(-_-!)。那麼如何防護呢?
文章有點長,不知道你們被我繞暈了沒有,請先去休息下,思考前文內容是否看懂了。
書接上文,要防護url跳轉到釣魚網站,咱們確定須要一個白名單機制,或者根本就讓他跳轉到本站下。因而有了以下判斷:
public String redirect() {
if(redirecturl.startsWith("/"))
{
return "redir";
}
return "false";
}
可能你看出來了,僅僅判斷"/"開頭,實際上是不能杜絕url跳轉漏洞的,由於
http://www.inbreak.net/app/test!redirect.action?redirecturl=//www.ph4nt0m.org
同樣會跳轉。而在這裏卻足夠了,由於struts2已經接管了這個過程,只要以「/」開頭,通通先給你自動加上本地域名,抓包後,你會看到
location: http://www.inbreak.net/app//www.ph4nt0m.org
其實是不會有問題的。
struts2
也認爲這樣判斷不會有問題了,然而用戶輸入
http://www.inbreak.net/app/test!getStr.action?str=redir&redirecturl=http://www.ph4nt0m.org
其實前篇已經分析過了,這樣就利用action中的str屬性,繞過了必須以「/」開頭的判斷,直接跳轉了。
test
裏有個str屬性,可自定義返回,這裏自定義了「redir」,因此來到了
${redirecturl}
而redirecturl的值,也提交給了action,因此跳轉了。
Struts2
的那些result類型缺陷(Ajax):
在struts2中使用ajax,也是被struts2支持的,它提供了一種返回類型,叫作「stream」。在研究這個result的使用時,做者看到一本書,叫作《 Struts 2權威指南:基於WebWork核心的MVC開發 》。這本書很是出名,幾乎全部的struts2使用者都推薦使用。
http://book.csdn.net/bookfiles/479/index.html
書上介紹ajax能夠這麼使用:
配置struts.xml
以後寫TestajaxAction:
public InputStream input;
public String execute() throws Exception{
input = new StringBufferInputStream("aaaa
return SUCCESS;
}
其實你們都看出來個人意思了,返回了contentType爲「text/html」的頁面,內容爲
結果瀏覽器解析的時候,出現了XSS漏洞。
原本默認的contentType是text/plain,不須要配置,若是用戶直接打開,只會看到一個Stream,不會解析其中的html和js。如今書上介紹說要寫成這樣,不知道做者是否知道這個教程對你們的影響,結果已經誤導了大批的開發人員。
事實上,這不是struts的問題,是struts「權威」教程的問題。權威的教程,一旦出現安全漏洞,每每會誤導大批的開發人員,不知道你們在挖漏洞的時候,是否注意到了這點,特別是當官方的DEMO出現漏洞,那絕對是驚天地泣鬼神的悲劇。
Struts2
的那些result類型缺陷(自定的頁面):
有時候,開發人員爲了方便,喜歡配置struts.xml以下:
user/test.jsp
user/testproperty.jsp
${redir}
${testloadfilepath}
user/redirfalse.jsp
user/input.jsp
請注意,其中一條result,名稱是」testloadfilepath」,${testloadfilepath}的
做用是自定義的jsp頁面地址,接收session或request中傳過來的這個變量的值。那麼用戶提交
http://www.inbreak.net/app/test.action?testloadfilepath=user/test.jsp
固然就會返回user/test.jsp頁面,很是的靈活。雖然並非全部的開發都會這麼作,可是一旦出現這種狀況,會產生什麼問題呢?
http://www.inbreak.net/app/test!getRedir.action?redir=testloadfilepath&testloadfilepath=WEB-INF/classes/hibernate.cfg.xml
不知道你們看懂這段url的含義沒有,先調用getRedir,能夠自定義返回到testloadfilepath,而testloadfilepath已經指定了WEB-INF/classes/hibernate.cfg.xml。WEB-INF目錄下,都是受web容器保護的東西,默認不容許直接request相對地址來訪問。該目錄裏面有程序編譯後的class文件(能夠被直接反編譯爲java源碼),有數據庫配置文件等敏感文件,如今打開如上url,直接被下載了hibernate.cfg.xml,這裏放着數據庫用戶名和密碼。
這樣,攻擊者就能夠下載你的全部源代碼,全部服務器上的文件。struts在提供給咱們這種方式的時候,並無任何官方說明這裏有危險,這就是一個不定時炸|彈。
struts2
的taglib設計缺陷
通過幾個例子下來,不知道你們注意到沒有,從用戶輸入走到這裏,已經走到了輸出這一步了。struts2的那些result的type,其實就是幾種輸出方式,有jsp、ajax、redirect,通過jsonplugin等插件配置,還能夠支持其餘輸出方式。甚至支持一些標籤庫,好比freemarker等標籤庫。不過咱們只談struts2自帶的標籤庫,在一個jsp頁面的最上方,寫上一段代碼,就可使用struts2提供的數據輸出和頁面數據操做的標籤了。比以往咱們在jsp輸出「<%=name%>」要方便的多,下面給個例子:
test.jsp
代碼
<%@ taglib prefix="s" uri="/struts-tags" %>
第一行是告訴struts這裏要使用struts的標籤庫,第二行就是一個標籤的使用,含義是輸出username的 值,username會從session、request、page等地方取,這裏不關注取數據的次序。
struts2
的taglib設計缺陷(struts2.0不支持escapeJavaScript)
說到輸出,你們都能想到XSS漏洞,那麼做爲一個流行框架,struts2在這裏作了什麼控制呢?
struts2.0
對部分標籤作了默認的htmlescape:
剛纔那個標籤實際上效果等於
別覺得作了htmlescape就夠了,輸出在javascript中的時候,還會出現xss漏洞。因此struts在2.1.6這個版本也支持了javascriptescape:
struts2.1.6
:
默認開啓如上所示,當你要輸出到js中的時候,可使用escapeJavaScript進行轉義。
也就是說,一旦你肯定這個struts是2.0的,只要開發人員把變量輸出到js中,十有八九會出xss問題。
struts2
的taglib設計缺陷(沒有富文本安全輸出標籤)
而包括最高版本2.1.8在內,仍然沒有支持富文本安全輸出,這是一件悲劇的事情,若是用struts開發一個大衆blog的應用,又支持富文本的文章,開發人員只能把htmlescape和jsescape都去掉,才能保證業務正常運行,因此致使了XSS漏洞。
已有 0 人發表留言,猛擊->>這裏<<-參與討論
JavaEye推薦