摘要
本文介紹了java開發流行架構struts2以及webwork的一些安全缺陷,并舉例說明架構本身以及開發人員使用架構時,所産生的種種安全問題,以及作者挖掘架構安全漏洞的一些心得體會。
推薦以下人群閱讀
了解java開發
了解架構開發
了解web application安全
“網絡安全愛好者”
正文
目前java開發網站,通常不會是純JSP的,大都使用了java framework。
有了這些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{
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檔案
<action name="aaaaaaa">
<result name="success">user/aaa.jsp</result>
</action>
配置這個檔案後,當使用者輸入
http://www.inbreak.net/app/aaaaaaa.action
的時候,struts會負責讓AaaaAction中的execute方法處理使用者請求。
處理之後,該方法傳回“return SUCCESS;”,struts又負責找到result的name是seccuess所指向的jsp頁面。
把該頁面解析後,傳回給使用者。
而使用者看到的就是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 {
private Long id=0l;
private String name;
private String pass;
private Integer type=1;
。。。下面的get和set方法代碼略
}
定義後,在action中,添加一個屬性
User reguser;
使用者注冊的頁面代碼如下:
<form XXXXXXX>
<input name="reguser.name">
當使用者送出這個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,叫做
<input name="reguser.type">
然後輸入值是2,把這個值一起交給action。在這個流程中,這個值,當然也會被自動帶到資料庫中,向下處理的邏輯中,這個使用者,就已經變成管理者了。
當你看到了一個struts2或者webwork的應用,可以嘗試使用屬性攻擊,修改目前表單,裡面有所有你猜測到的屬性,一并送出上來,就可能會影響整個邏輯,達到攻擊目的。文中僅僅是一個例子,事實上,在資料傳遞的過程中,可以任意覆寫資料的預設值,本來就是一個危險的缺陷,而struts2和webwork這兩個架構僅僅看到了它帶來的好處,忽略了這方面基于安全性的考慮,僅僅關注了使用者送出資料的正确性。對比在沒有struts2這個功能的時候,我們卻需要在action中一個一個的把需要的變量,從使用者送出的request中解出來,一個一個處理,不可能出現這種安全問題。現在它包裝了這個過程,自以為很方面,卻出了嚴重問題。
Action中的方法被暴力猜解缺陷
前文提到,有一種方法可以讓使用者通路action時,不通路預設的execute方法,而是直接通路其他action中的方法,條件是在action中,寫一個public的方法。開發人員如果需要做一個登陸後,展示所有使用者清單的功能,而他的一個“解耦合”的開發習慣,将在這裡導緻安全缺陷。
定義一個如下的action
public class Userlogin extends ActionSupport{
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;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
這段代碼的時候,實際上,是寫了兩個public的方法。
那這兩個表面上沒有任何實質含義的方法,會有什麼安全隐患呢?
這需要和前文聯系起來,前文提到,我們在struts.xml檔案中,定義如下:
<action name="user">
<result name="success">user/userlist.jsp</result>
<result name="addUser">user/addUser.jsp</result>
<result name="added">user/added.jsp</result>
<result name="false">user/false.jsp</result>
</action>
這段代碼含義是,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”類型。
<action name="test">
<result name="false">user/false.jsp</result>
<result name="redir" type="redirect">${redirecturl}</result>
</action>
這段代碼,大家唯一可能看不懂的,就是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”,是以來到了
<result name="redir" type="redirect">${redirecturl}</result>
而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
<action name="ajaxtest">
<result type="stream">
<param name="contentType">text/html</param>
<param name="inputName">input</param>
</result>
</action>
之後寫TestajaxAction:
public InputStream input;
public String execute() throws Exception{
input = new StringBufferInputStream("aaaa<td><script>alert("kxlzx")</script>aa");
return SUCCESS;
}
其實大家都看出來我的意思了,傳回了contentType為“text/html”的頁面,内容為
aaaa<td><script>alert('kxlzx')</script>aa
結果浏覽器解析的時候,出現了XSS漏洞。
本來預設的contentType是text/plain,不需要配置,如果使用者直接打開,隻會看到一個Stream,不會解析其中的html和js。現在書上介紹說要寫成這樣,不知道作者是否知道這個教程對大家的影響,結果已經誤導了大批的開發人員。
事實上,這不是struts的問題,是struts“權威”教程的問題。權威的教程,一旦出現安全漏洞,往往會誤導大批的開發人員,不知道大家在挖漏洞的時候,是否注意到了這點,特别是當官方的DEMO出現漏洞,那絕對是驚天地泣鬼神的悲劇。
Struts2的那些result類型缺陷(自定的頁面)
有時候,開發人員為了友善,喜歡配置struts.xml如下:
<action name="test">
<result name="success">user/test.jsp</result>
<result name="testpro">user/testproperty.jsp</result>
<result name="redir" type="redirect">${redir}</result>
<result name="testloadfilepath">${testloadfilepath}</result>
<result name="false">user/redirfalse.jsp</result>
<result name="input">user/input.jsp</result>
</action>
請注意,其中一條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" %>
<s:property value="username"/>
第一行是告訴struts這裡要使用struts的标簽庫,第二行就是一個标簽的使用,含義是輸出username的值,username會從session、request、page等地方取,這裡不關注取資料的次序。
struts2的taglib設計缺陷(struts2.0不支援escapeJavaScript)
說到輸出,大家都能想到XSS漏洞,那麼作為一個流行架構,struts2在這裡做了什麼控制呢?
struts2.0對部分标簽做了預設的htmlescape:
剛才那個标簽實際上效果等于
<s:property value="username" escape="true"/>
别以為做了htmlescape就夠了,輸出在javascript中的時候,還會出現xss漏洞。是以struts在2.1.6這個版本也支援了javascriptescape:
struts2.1.6:
<s:property value="pass" escape="true" escapeJavaScript="false"/>
預設開啟如上所示,當你要輸出到js中的時候,可以使用escapeJavaScript進行轉義。
也就是說,一旦你确定這個struts是2.0的,隻要開發人員把變量輸出到js中,十有八九會出xss問題。
struts2的taglib設計缺陷(沒有富文本安全輸出标簽)
而包括最高版本2.1.8在内,仍然沒有支援富文本安全輸出,這是一件悲劇的事情,如果用struts開發一個大衆blog的應用,又支援富文本的文章,開發人員隻能把htmlescape和jsescape都去掉,才能保證業務正常運作,是以導緻了XSS漏洞。
struts2的taglib設計缺陷(并不是所有輸出标簽都做了預設的htmlescape)
有幾個标簽是不做htmlescape的,比如
<s:a>
<s:text>
<s:url>
這其實是一個嚴重陷阱,因為隻要提到struts2,前輩們都會告訴你,放心使用,它預設做了htmlescape。那是什麼原因導緻一些标簽沒有做預設的escape呢?作者翻了下源碼,也沒有找出具體原因,不知道那些人是怎麼想的。
并且,經過簡單的fuzz,發現在特定環境下,那些做了輸出轉義的标簽也會出現問題。
我們知道預設的htmlescape是不轉義單引号的,是以,當struts标簽庫的源碼中,出現一些标簽屬性的輸出時,如果标簽屬性的周圍使用的是單引号,而攻擊者又能控制标簽屬性内容的時候,就會出現xss漏洞。如下:
<input name="username" onclick='xss'>
當這個xss的内容可以由攻擊者控制,即使對xss的内容作了htmlescape,依然可以被攻擊者bypass。
基于這個原理,作者搜尋了struts标簽庫源碼,那些“XXX.ftl”檔案中搜尋“}'”符号,找到N多,測試其中一個如下:
-------------
<s:textfield >标簽,在正常使用的時候,他會放到一個<s:form>标簽内,最終輸出html後,會變成一個輸入框。
它有個屬性叫“tooltip”,如果這個标簽為使用者可控制,比如從資料庫中讀取使用者輸入,而這個标簽所在的
<s:form>開啟了:
<s:form tooltipConfig="#{'jsTooltipEnabled':'true'}">
的時候,使用者輸入的tooltip的值,會出現以下情況:
struts2.0 -->
<span dojoType="tooltip" ... caption="kxlzx<script></script>">
caption内容就是tooltip的值,從資料庫查出
struts2.1.6&struts2.1.8 -->
<img onmouseover="domTT_activate(this, …'StrutsTTClassic');alert('xss');a('','styleClass’…" />
onmouseover生成一個domTT_activate函數調用,參數中其中一個值,是tooltip的内容。這裡被bypass了。
------------
這些搜出的幾個個地方實際上根本沒有做任何escape,就直接輸出了資料。下面那個即使做了預設的htmlescape,還是會出問題,除非它預設做了javascriptEscape。struts2預設有地方做javascriptEscape麼?
答案是“沒有”。是以,它們全都能被XSS!
struts2的這些escape,其實是一個很太監的安全方案,安全工程師最恨的就是這種方案,做了安全方案,還不做完全,留下一堆問題。
struts2的HTTP Parameter Pollution處理缺陷
webwork和struts2都有這個問題,當使用者給web應用送出:
http://www.inbreak.net/app/test!redirect.action?redir=kxlzx&redir=aaad61
時,如果我們在action中定義了
private String redir;
public String getRedir() {
return redir;
}
public void setRedir(String redir) {
this.redir = redir;
}
Action就會取到redir的值為“kxlzx, aaad61”注意中間是有空格的。
這種資料是由webwork(struts2)把兩個參數合并而成的,但是如果我們request.getParameter("redir");拿到的值,卻隻是第一個(值為kxlzx)。
我們知道struts2提倡使用攔截器做一些事情,他可以在action的execute方法執行之前和之後做一些操作。那就有一些開發,想當然的在這裡防禦一下url跳轉、SQL注入、XSS等攻擊。我們看看他們會怎麼做:
@Override
public String intercept(ActionInvocation arg0) throws Exception {
……
String name = request.getParameter("name");
if(name!=null&&name.indexOf("'")>-1){
System.out.println("find sql injection");
request.getSession().setAttribute("msg", "find sql injection");
return "falseuser";
}
String redir = request.getParameter("redir");
if(redir!=null&&!redir.equals("http://www.b.com")){
System.out.println("find url redirect");
request.getSession().setAttribute("msg", "find url redirect");
return "falseuser";
}
return arg0.invoke();
}
在這段代碼中,作者僅僅示例了在攔截器中防禦sql注入和url跳轉漏洞,sql注入的防禦規則是檢查“’”單引号,而url跳轉漏洞規則是檢查必須跳轉到”http://www.b.com”去。作者知道沒有完全防禦,是以大家先不要在這裡追究防禦方案,僅僅是一個示例。
而開發人員在業務代碼如下:
String sql = "select book_name,book_content from books";
if (name != null) {
sql += " where book_name like '%" + name + "%'";
}
很明顯能注入。
public String redirect() {
return "redir";
}
也明顯存在url跳轉漏洞。
但是由于攔截器在action之前執行,是以如果我們輸入了
http://www.inbreak.net/app/test!findUserByName.action?name=a'
攔截器當然就會傳回錯誤“find sql injection”;
因為執行到了
String name = request.getParameter("name");
if(name!=null&&name.indexOf("'")>-1){
發現name的值确實有單引号。
但是如果我們輸入了
http://www.inbreak.net/app/test!findUserByName.action
?name=aaaaa&name=a' union select name,pass from user where ''<>'
就直接繞過了攔截器的判斷。因為攔截器擷取的request.getParameter("name"),是第一個參數的值aaaaa,抛棄了第二個參數的值,但是action中的name的值,卻是“aaaaa, a' union select name,pass from user where ''<>' ”是以被注入了
大多數攔截器都是這樣做的防禦,包括一些filter等。
這件事情發生在url跳轉漏洞時,卻不明顯,因為攻擊者頂多構造一個:
http://www.inbreak.net/app/test!redirect.action?redir=http://www.b.com&redir=www.inbreak.net
抓包看看
它跳到了http://www.b.com, www.inbreak.net去了。是以IE直接報錯,說打不開這個位址。但是我們還有别的浏覽器,總是喜歡給大家友好資訊的浏覽器,看看chrome給使用者什麼提示:
Chrome也認為這是一個錯誤的連結,是以給出了“正确”的連結位址。這不是剛好被釣魚網站利用麼?
struts2的官方漏洞公告和修補後引發的安全缺陷
從有struts2,到現在為止,官方一共釋出了4個漏洞,在
http://struts.apache.org/2.x/docs/security-bulletins.html
* S2-001 — Remote code exploit on form validation error
* S2-002 — Cross site scripting (XSS) vulnerability on <s:url> and <s:a> tags
* S2-003 — XWork ParameterInterceptors bypass allows OGNL statement execution
* S2-004 — Directory traversal vulnerability while serving static content
從名字上,可以看出漏洞的内容,作者僅僅對其中兩個做了源碼級别的漏洞修補評估,發現了很多悲劇的事情。
同學們有興趣可以去研究剩下兩個漏洞。
struts2的官方漏洞公告和修補後引發的安全缺陷(S2-002)
先看看“S2-002 — Cross site scripting (XSS) vulnerability on <s:url> and tags”這個漏洞。
顧名思義是對<s:url>和<s:url>的xss漏洞修補,但是前文提到,這裡有XSS漏洞,難道是在忽悠大家?我們看看這幫工程師是怎麼修補的,來到這個svn位址:
http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/util/UrlHelper.java?r1=614814&r2=615103&diff_format=h
注意這兩行:
看到這兩行代碼的時候,作者笑了,因為作者仿佛看到了至少兩件悲劇的事情,現在把它們寫成故事:
第1件悲劇的事情,某年某月某日,一個腳本小子給官方報告漏洞,說在使用<s:url>标簽的時候,代碼為:
<s:url action="%{#parameters.url}"></s:url>
之後他輸入了
http://www.inbreak.net/app/test!testpro.action?url=<script>alert('hacked by kxlzx')</script>
并告訴官方這裡是一個XSS漏洞,希望官方修補掉。
官方很重視,一個開發就去修補,添加如下判斷:
if (result.indexOf("<script>") >= 0){
result = result.replaceAll("<script>", "script");
并進行了冒煙測試、功能測試、黑盒測試、白盒測試。認為沒有問題了,因為送出攻擊者給的惡意url後,輸出了
scriptalert('hacked by kxlzx')</script>
結果并沒有在頁面執行xss腳本。後來那腳本小子也測試了一下,發現沒問題,這事情就過去了,瞞着人民大衆,悄悄的修補了。
第2件悲劇的事情,又過了某人某月某日,某另一個腳本小子又發了漏洞,還是那段代碼,但是url改成了:
http://www.inbreak.net/app/test!testpro.action?url=<<script>>alert('hacked by kxlzx')</script>
注意,這裡是<<script>>,經過了replaceAll函數後,剛好變成了<script>,重新組成了XSS漏洞。
官方這次不得不重新重視起來,決定把if判斷,變成了while,不管你有多少<<<<<<<script>>>>>>>,都給你變成
scriptalert('hacked by kxlzx')</script>
并進行了冒煙測試、功能測試、黑盒測試、白盒測試。這次還發了公告出來,說這裡沒問題了,我們很重視安全漏洞,已經修補了。
作者看到這裡,測試新的bypass官方修補代碼的url為:
http://www.inbreak.net/app/test!testpro.action?url=<script kxlzx=kxlzx>alert('hacked by kxlzx')</script>
于是XSS腳本又被執行了,因為這裡是<script kxlzx=kxlzx>,不是<script>,是以不符合判斷條件,沒有被replaceAll,再次bypas了漏洞修補。。。
struts2的官方漏洞公告和修補後引發的安全缺陷(S2-004)
這個漏洞的修補,比上一個更加令人無奈,這是一個/../擷取資源檔案的漏洞
S2-004 — Directory traversal vulnerability while serving static content
要了解這個漏洞的成因,大家需要先了解一個知識點。
當struts的FilterDispatcher收到url請求如下兩個路徑下的檔案時:
http://www.inbreak.net/app/struts/
或
http://www.inbreak.net/app/static/
會去取struts核心包的core.src.main.resources.org.apache.struts2下面的靜态資源檔案,這些資源檔案其實是一些js腳本和一些css檔案。前文提到
<img onmouseover="domTT_activate(this, …'StrutsTTClassic');alert('xss');a('','styleClass’…" />
代碼中的domTT_activate,其實就是
http://www.inbreak.net/app/struts/domTT.js
檔案中的一個函數。
在struts2.0的時候,隻要你敢上某幾個版本的struts2,攻擊者就可以通過
http://www.inbreak.net/app/struts/..%252f
http://www.inbreak.net/app/struts/..%252f..%252f..%252fWEB-INF/classess/example/Login.class/
浏覽你的web目錄,下載下傳web目錄下的檔案。
先不說漏洞修補,請讀者趕緊想想,你公司的開發人員,是否使用了struts2,并且把“Struts 2.0.0 - Struts 2.0.11.2 ”之間的幾個版本包裝了或者根本沒有包裝,直接上了web應用。如果有這種情況,就可以直接用以上方式攻擊,這幾天作者找了幾個大型門戶網站的漏洞,發現他們都存在這個漏洞,順便下載下傳了他們的資料庫配置檔案,同時報告了漏洞。
Struts官方可能也受到了攻擊,于是修補了代碼。
作者同樣檢視了svn修補記錄:
http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/dispatcher/DefaultStaticContentLoader.java?r1=674498&r2=687425&pathrev=687425&diff_format=h
可以看到“ if (!name.endsWith(".class")) {”這行代碼在修補漏洞時,被删除了。
修補前的代碼中,為什麼以前要過濾.class檔案呢?是因為struts提供了一個功能:
如果開發人員想自己使用這個靜态檔案映射的功能,可以配置web.xml
<filter>
<filter-name>struts</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
<init-param>
<param-name>packages</param-name>
<param-value>net.inbreak.action</param-value>
</init-param>
</filter>
如上配置後,當使用者輸入
http://www.inbreak.net/app/struts/domTT.js
的時候,實際上struts會去找net.inbreak.action這個檔案夾下的domTT.js檔案發給使用者,而不再尋找核心包的那個檔案夾了。這個功能開放後,官方為了防止對應包下的class檔案被下載下傳後反編譯成源碼,是以寫了行代碼,過濾.class檔案。
就因為這行代碼的存在,一時間,剛巧又正是struts2流行的時代。網際網路大批的文章介紹struts2核心源碼,在介紹到FilterDispatcher的時候,必然會提到,這裡會過濾.class檔案,如果開發人員使用這個功能,可以放心,自己的class檔案不會被人下載下傳。
後來出了漏洞,攻擊者可以用
http://www.inbreak.net/app/struts/..%252f..%252f..%252fWEB-INF/classess/example/Login.class/
繞過官方限制,下載下傳class檔案。最終的确修補了這個/../的漏洞。然而悲劇的是,因為class檔案實際上還是可以被下載下傳,是以官方修補的同時,去掉了“if (!name.endsWith(".class")) { ”這一行代碼,可能是覺得這一行代碼太丢人。
曾經的教程,還在網際網路上,告訴大家class檔案不會被下載下傳,官方也發表聲明修補了/../漏洞。但是看到教程的開發們,卻早已把目錄映射了靜态檔案:
<param-name>packages</param-name>
<param-value>net.inbreak.action</param-value>
如果這個開發的net.inbreak.action包下有個UserLogin.class檔案,在struts2有漏洞的版本,會面臨伺服器上所有檔案被下載下傳的命運。即使開發更新了struts,因為核心代碼中的class檔案的判斷去掉了,導緻這個檔案還是可以被
http://www.inbreak.net/app/struts/UserLogin.class
官方在沒有任何通知的前提下,在教程滿天飛告訴大家class檔案不會被下載下傳的前提下,為了面子悄悄的取掉了這個判斷。這回好了,無論更新否,都不讓人消停,萬一開發人員頭腦發熱,配置如下:
<param-name>packages</param-name>
<param-value>/</param-value>
就出大事了,可以直接下載下傳資源目錄下所有檔案。
http://www.inbreak.net/app/struts/hibernate.cfg.xml
總結
作者挖了struts2的一些漏洞,也挖了一些web其他架構的漏洞(不在本文範圍),總結下挖取這些架構漏洞的方式。
首先,是上不上架構的問題。一旦開發用了這些架構,web應用會直接面臨一些漏洞:
1,開放了某功能,導緻采用架構的應用預設漏洞
因為這個架構在未經允許的情況下,悄悄的打開了某些功能,可能是為了友善開發等作用,結果導緻了漏洞的産生。
舉個例:
比如DWR這個AJAX架構一旦使用,在某些版本裡,輸入
http://www.inbreak.net/app/dwr/
就能看到一個頁面,裡面是所有ajax方法的映射,甚至一些service方法沒有配置,自動映射了出來。你可以在這個頁面給那些映射出來的方法送出參數,調用方法。比如有個getUserpasswdByid的方法,看名稱就知道,傳遞使用者id,傳回密碼。
再舉例就是本文中的struts2的../../漏洞。
如果要挖這種漏洞,絕對是0day級别的漏洞,是以大家有必要懷疑每一個實作步驟,這種漏洞其實大部分就出現在開發環境和正式環境的差異,以及一些奇奇怪怪的功能點上。
2,擴充了某功能,導緻安全問題
我們的web應用,本來是可以自己寫代碼實作一些功能的,但是這個架構為了友善開發和管理,把開發人員要寫的代碼包裝了下,隻要簡單的幾行就能實作原本大批代碼才能做到的功能。而這些便利,卻帶來了很多安全問題。挖漏洞的同時,應該特别注意哪些地方比原來友善了很多,擴充了很多,這些擴充和友善,是否考慮到了安全因素。
比如本文介紹的struts的result(urlredirect)相關漏洞。
3,DEMO或教程或使用者指南誤導
自從架構出現後,為了讓人們最快的了解和使用架構,官方出了很對使用者手冊,demo等功能。而很多大牛們,也相應的出了教程或者書籍,但是這些教程,DEMO,書籍上的例子,恰恰産生了很多漏洞。甚至開發本來不按照教程走,不會有漏洞,卻被教程誤導了。如果黑客看到這些書籍,請立刻把他列到你的掃描器中,絕對有不少開發會按照教程上去做。
例如本文提到的那個書籍介紹我們使用ajax的事情。
4,原本有安全方案,但是被某功能代碼覆寫
這其實是一件悲劇的事情,告訴我們要注意在日常開發中和漏洞修補中,是否有不明真相的開發人員,為了實作某個功能,悄悄的把原本的安全方案斷章取義的覆寫掉了。挖漏洞的時候,要特别注意安全方案附件的代碼變動,很可能發現非常弱智的邏輯問題。
例如本文送出的class檔案的判斷。
5,不完善的安全
一個安全方案的實施,應該是徹頭徹尾的,要注意方案的完整性,不能有些地方方案OK,有些地方不能實施。在挖漏洞的時候,也不要被安全方案吓住,并不是有了方案,聽起來牛X,就絕對不會有漏洞的,最起碼應該經過全面fuzz。
例如本文提到的XSS遺漏點,以及富文本的遺漏。
6,版本更新後,沒有醒目安全公告
我們知道所有的架構師都不願意有事沒事更新架構,特别是不穩定的架構版本,因為這個更新可能帶來很多不可預料的問題,是以可能即使看到了安全公告,也沒有去更新。如果不懂安全,更不願意更新架構了。是以官方必須做到一個漏洞的修補,一個公告的釋出,必須帶有相關的代碼log。告訴大家具體哪裡做了改動。而挖漏洞的同學更應該盯緊這些地方,百般推敲和測試修改的部分,不要被一次公用的測試結果吓到,模糊的認為漏洞已經修補了。
7,悲劇的方案
很多時候,我們會看到官方修補漏洞,或者一些安全方案的實施結果。那是不是真的都能達到修補漏洞的效果呢?
例如本文的<s:url>标簽的xss漏洞,官方都這個漏洞的修補,真是絞盡腦汁,最終還是悲劇了。
8,優秀的方案,悲劇的執行
RT,不再說明。
9,挑戰web伺服器配置
這個問題有必要說一說,struts有事沒事做個靜态映射做什麼?其實是目的就是為了架構和應用分離,很明顯那些js檔案應該放在項目中的web目錄下,但是為什麼要做呢?還不是因為struts包釋出的時候,還沒有項目,隻有架構。
它為了達到即使上了任何項目,也能有辦法通路到它提供的那些js的目的,隻好被逼無奈做了這個功能,靜态目錄映射。無論任何項目,部署後,隻要url後面根目錄加上/struts,或者/static就可以通路js。後來做了這個功能感覺良好還不算,居然把功能提供出來,給推薦給開發人員使用。歸根結底是因為struts挑戰了web伺服器的配置,非要自己做靜态映射。要知道人家web伺服器做的映射,是經過多少年黑客入侵打磨出來的,struts有嗎?
這種情況突出在單獨映射某目錄,單獨對某目錄做權限,做DIR清單等功能,如果你看到一個架構也做了這種功能,恭喜你!趕快去挖,十有八九存在漏洞!
10,沒有安全方案,也沒有提醒
本文其實沒有提到一些web漏洞,比如csrf,比如session fix,比如傳輸加密等,很明顯struts是存在漏洞的,隻是作者覺得這些東西沒必要這裡說,大家都是明眼人,看到form裡沒有token,百分百csrf嘛!
想想官方,官方也明明知道上了自己的架構後,會存在這些漏洞,為什麼連個提醒都沒有呢?本來開發就不知道,你又藏着掖着。如果架構負責任,發個公告,說如果你用了我的架構,實際上要小心什麼什麼攻擊。。。
呃。。。我明白官方為什麼不敢說了。-_-!
這些架構除了那些“隻要你用,必然有漏洞”的安全缺陷外,還有不少問題,會出現在開發人員使用架構的過程中:
1,兩個架構都友善,結合起來有漏洞
有個架構叫做Spring security,是基于url的通路權限控制,做的很好。如果你不是管理者,絕對不能通路admin目錄。但是有很多web架構,通路一個action或者通路一個controller,不止一個url可以通路,在這裡做了相容性處理,多個url指向同一個應用,導緻Spring security這種基于url的通路控制,名存實亡。
2,開發人員“正常”使用架構後,可能産生漏洞
這是最最慘的事情,架構絕對不會認領這種類型的漏洞的,他會認為這是開發的問題。然而本文的“action方法暴力破解、url redirect擴大化”這兩個安全缺陷,實際上也是架構存在的意義(友善開發)帶來的後果。官方會修補麼?我想它頂多會說,大家不要這樣那樣而已,絕對不會做什麼安全方案的。要知道這些漏洞是具有struts或者webwork特色的,隻有使用了這些架構才會有的。
3,危險功能點
架構帶來了一些功能點,比如Tag lib的一些XSS,隻要使用,必定有漏洞。
4,架構給開發挖坑
這根本就是陷阱,還是要說/static、/struts,如果開發不配置,頂多下載下傳個js,影響不大,萬一開發使用了這個功能,映射了某個目錄,那就掉進坑裡去了。
5,一個架構帶來的漏洞被另一個架構最大化
本文提到了變量預設值被覆寫後,又因為hibernate不需要寫sql語句,而最終被存儲進了資料庫中。
回想一個問題,如果讓我們自己寫sql語句實作,難道在添加普通使用者的時候,會真的專門寫個變量,接收使用者傳進來注冊管理者的字段麼?
但是這是hibernate得問題麼?當然不是,隻是因為有了它,導緻漏洞更加嚴重而已。
補充
本文對struts2的種種安全缺陷,就提到這裡。個人覺得這是一個挖漏洞的方向,對framework漏洞的挖掘。
可能大家都專注于代碼安全,沒有太多的去看架構本身是否安全,以及使用了架構後,是否真的安全。
是以很多人忽略這個問題,我相信這不是一個新的開始,也不是一個結束,隻是讓大家開始更加重視架構安全。
作者也僅僅在本文提到了struts,webwork這兩個架構,事實上架構很多,他們真的安全麼?有待考證!
最後寫給那些真正打算把技術應用于實踐的同學們,架構漏洞掃描器,是可以做出來的,對于難以解決的猜解問題,可以對網站蜘蛛一下,然後儲存那些開發人員喜歡使用的字段名稱,關注各個input的名字,action的名字,目錄名字等,生成猜解一個清單。而判斷是否用了struts更加簡單:
特征1:XXX.action 可能是struts或webwork
特征2:XXX.do 可能是struts或webwork
特征3:XXX!bbb.action 可能是struts或webwork
特征4:XXX/struts/..%252f 必然是struts2
其他細節處,不再一一給出,有待大家發掘。
相關的漏洞修補方式,作者不在此處獻醜,請大家根據漏洞原理自行處理。