前文
对于的el表达式的注入来说,通常是通过利用其能够动态执行的特性,之后通过反射的方式进行执行
或者更常见的是通过调用JS引擎,之后通过执行js代码进行命令执行或者其他操作
这里主要是对yzddMr6师傅在KCON的议题的学习,看看师傅的优质思路
EL表达式相关
根据我们对el表达式的了解,主要是将el表达式的字符串解析成了一个一个的Node
结点
而在jsp中具体的是在proprietaryEvaluate
方法中对其进行表达式的解析成ValueExpression
的实现类
可以看出children
变量就是抽象出的一个一个的Node结点
之后是通过调用AstValue#getValue
进行表达式的执行
大概的逻辑如下
取出第一个Node作为base结点,并获取结点数、ElResolver等
循环的遍历每一个子结点,因为解析表达式是根据一个一个的
.
进行分割的,所以下一个结点一定是AstDotSuffix
类对象这时,如果下下个结点是
AstMethodParameters
类对象,则表示正在调用一个方法,之后的判断语句只是对Optional#orElseGet
方法的支持罢了而在识别出在调用方法时,通过
getParameters
获取参数,AstMethodParameters#getParameters
也就是将参数数组化并输出而核心的执行是在
ElResolver#invoke
方法中,在el中支持的所有解析器如下
核心是通过第7个BeanELResolver
解析器
流程如下首先将method转化成String类型的方法名
之后在基类base中查看是否有该方法,如果有则获取该方法
最后通过反射调用该方法
最后的最后将其设置为成功解析,并返回执行结果
上面得到的执行结果,最后也重新赋值给了新的base基类,并进行下一次的相同循环
上面就是关于在jsp中el表达式动态执行的细节部分,总结一下
将表达式解析成Node结点
访问内部的每个结点,顺序且连续的反射调用每一个方法
Node结点
AstMapData
能够返回一个HashMap结构的数据
AstConcatenation
能够拼接两个字符串,并将拼接后的字符串返回
AstLambdaExpression
可以进行lambda表达式的执行
AstListData
能够进行list数据的处理
AstBracketSuffix
处理[]
的方式中的数据
非常规思路
其他方式执行
常规的调用方法是通过.
的方式连接进行调用,而这个.xx
最终也是解析成了AstDotSuffix
,在Node结点中可以作为后缀的除了.
格式,还存在着[]
中括号的方式
类似前面 EL表达式相关提及到的关于方法的调用的过程,在确定了base结点的情况下,计算下一个结点值时这里就是AstBracketSuffix#getValue
方法返回的值,直接是返回[]
内的第一项的内容
若其第一项是字符串,将会解析成AstString
类,该类的getValue
方法就会直接返回对应的值,就能够达到和使用.
一样的效果进行方法的反射调用
<%--${param.getClass().forName(param.c).newInstance().eval(param.cmd)}--%>
<%--${''.getClass().forName("javax.script.ScriptEngineManager").newInstance()--%>
<%--.getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec(\"calc\")")}--%>
${''['getClass']()['forName']("javax.script.ScriptEngineManager")['newInstance']()
['getEngineByName']("js")['eval']("java.lang.Runtime.getRuntime().exec(\"calc\")")}
同样能够命令执行
类似的,对于webshell来讲,可以通过param.x
的方式通过x
传入参数进行执行
EL中的getter/setter
之前一直只认为通过.
的方式如果不是调用一个方法,就是访问一个类对象的属性值,通过学习师傅的议题,原来在el表达式中是通过调用对应属性的getter方法而获取的值
看看为什么吧!
对于通过.
连接的结点,将会解析成AstDotSuffix
,而使用=
号连接的两边解析成的是AstAssign
,等号的两边分别为该结点的两个子结点
类似于前面分析过的那样,会通过调用结点的getValue
方法进行表达式的evaluate,这里的即是调用AstAssign#get