天天看點

Struts2架構安全缺陷

摘要

本文介紹了java開發流行架構struts2以及webwork的一些安全缺陷,并舉例說明架構本身以及開發人員使用架構時,所産生的種種安全問題,以及作者挖掘架構安全漏洞的一些心得體會。

推薦以下人群閱讀

了解java開發

了解架構開發

了解web application安全

“網絡安全愛好者”

正文

目前java開發網站,通常不會是純JSP的,大都使用了java framework。

有了這些framework,讓開發人員更加快速的開發出代碼,也讓代碼非常具有可擴充性,那些分層架構的思想,更是深入人心。這些也大大影響了安全代碼稽核,曾提出“分層稽核代碼”的思想,比如在DAO層專門檢查sql注入,在view層檢查xss等。這些架構都有自己的層級,本次文章主要講的是struts這個架構的相關安全問題,也會有小部分涉及到struts後面的DAO層。

而struts這個架構更新占有市場佔有率極大的一個架構,它在各個層級中,位于如圖所示位置:

Struts2架構安全缺陷

可以看到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架構安全缺陷

但是struts2把所有public的方法都暴露了出去,導緻現在使用者輸入了

http://www.inbreak.net/app/userlogin!list.action

使用者通路這個連結後,struts2調用list方法,然後傳回結果給使用者,是以沒有登陸,就顯示了所有使用者資訊,

直接繞過了login中的登陸驗證。

Struts2架構安全缺陷

在沒有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”!

Struts2架構安全缺陷

一系列巧合後,導緻現在給使用者傳回了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

Struts2架構安全缺陷

其實前篇已經分析過了,這樣就利用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,這裡放着資料庫使用者名和密碼。

Struts2架構安全缺陷

這樣,攻擊者就可以下載下傳你的所有源代碼,所有伺服器上的檔案。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架構安全缺陷

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”;

Struts2架構安全缺陷

因為執行到了

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 ''<>'

Struts2架構安全缺陷

就直接繞過了攔截器的判斷。因為攔截器擷取的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

抓包看看

Struts2架構安全缺陷

它跳到了http://www.b.com, www.inbreak.net去了。是以IE直接報錯,說打不開這個位址。但是我們還有别的浏覽器,總是喜歡給大家友好資訊的浏覽器,看看chrome給使用者什麼提示:

Struts2架構安全缺陷

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

注意這兩行:

Struts2架構安全缺陷

看到這兩行代碼的時候,作者笑了,因為作者仿佛看到了至少兩件悲劇的事情,現在把它們寫成故事:

第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應用。如果有這種情況,就可以直接用以上方式攻擊,這幾天作者找了幾個大型門戶網站的漏洞,發現他們都存在這個漏洞,順便下載下傳了他們的資料庫配置檔案,同時報告了漏洞。

Struts2架構安全缺陷

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

Struts2架構安全缺陷

可以看到“ 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架構安全缺陷

總結

作者挖了struts2的一些漏洞,也挖了一些web其他架構的漏洞(不在本文範圍),總結下挖取這些架構漏洞的方式。

首先,是上不上架構的問題。一旦開發用了這些架構,web應用會直接面臨一些漏洞:

1,開放了某功能,導緻采用架構的應用預設漏洞

因為這個架構在未經允許的情況下,悄悄的打開了某些功能,可能是為了友善開發等作用,結果導緻了漏洞的産生。

舉個例:

比如DWR這個AJAX架構一旦使用,在某些版本裡,輸入

http://www.inbreak.net/app/dwr/

就能看到一個頁面,裡面是所有ajax方法的映射,甚至一些service方法沒有配置,自動映射了出來。你可以在這個頁面給那些映射出來的方法送出參數,調用方法。比如有個getUserpasswdByid的方法,看名稱就知道,傳遞使用者id,傳回密碼。

Struts2架構安全缺陷

再舉例就是本文中的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

其他細節處,不再一一給出,有待大家發掘。

相關的漏洞修補方式,作者不在此處獻醜,請大家根據漏洞原理自行處理。

繼續閱讀