远程实时执行java代码和shell脚本。可通过http请求直接在服务器运行

代码

需要部署到服务端,在springMVC框架内,依赖groovy脚本解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public interface Debug {

@Component
class Java extends GenericServlet {

@Autowired
AutowireCapableBeanFactory beanFactory;
private final GroovyClassLoader groovyClassLoader = new GroovyClassLoader();

public String runJava(String code) {
try {
Object bean = beanFactory.createBean((Class<?>) groovyClassLoader.parseClass(code));
String s = bean.toString();
try {
beanFactory.destroyBean(bean);
} catch (Throwable t) {
s += "\n" + ThrowableUtil.stackTraceToString(t);
}
return s;
} catch (Throwable t) {
return ThrowableUtil.stackTraceToString(t);
}
}

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/plain;charset=UTF-8");
res.getWriter().write(runJava(req.getParameter("code")));
}
}

@Component
class Shell extends GenericServlet {

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
try {
res.setContentType("gzip");// 避免nginx压缩
runShell(req.getParameter("code"), new OutputStream() {
public void write(int b) throws IOException {
res.getOutputStream().write(b);
res.getOutputStream().flush();
}
});
} catch (Throwable t) {
res.getOutputStream().write(ThrowableUtil.stackTraceToString(t).getBytes(StandardCharsets.UTF_8));
}
}

public static final Map<Integer, Process> GLOBAL_PROCESS = new ConcurrentHashMap<>();
public static final LinkedBlockingQueue<Integer> outQueue = new LinkedBlockingQueue<>(1 << 8);

private void runShell(String code, OutputStream os) {
Process process = GLOBAL_PROCESS.computeIfAbsent(1, new Function<Integer, Process>() {
@SneakyThrows
public Process apply(Integer s) {
Process bash = new ProcessBuilder("bash", "--login", "-i").redirectErrorStream(true).start();
Executors.newSingleThreadExecutor().submit(() ->
IOUtils.copyLarge(bash.getInputStream(), new OutputStream() {
@SneakyThrows
public void write(int b) {
outQueue.put(b);
}
}, new byte[1])
);
return bash;
}
});

try {
if (StringUtils.isNoneBlank(code)) {
for (byte b : (code + "\n").getBytes(StandardCharsets.UTF_8)) {
process.getOutputStream().write(b);
process.getOutputStream().flush();
}
}

Integer b;
while ((b = outQueue.poll(1, TimeUnit.SECONDS)) != null) {
os.write(b);
}

} catch (Exception e) {
Optional.ofNullable(GLOBAL_PROCESS.remove(1))
.ifPresent(Process::destroyForcibly);
}
}
}
}

操作流程

shell

通过ip:port/debug.Shell/?code=ls这个地址执行shell代码
执行完记得exit退出哦

ip:port/debug.Shell/?code=exit

java

通过ip:port/debug.Java/?code=你的java类代码执行java代码

  • 例如
    获取spring容器中所有bean的名称
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import org.springframework.beans.BeansException
    import org.springframework.beans.factory.BeanFactory
    import org.springframework.beans.factory.BeanFactoryAware
    import org.springframework.beans.factory.support.DefaultListableBeanFactory

    class Test implements BeanFactoryAware {
    def names;

    @Override
    void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    names = ((DefaultListableBeanFactory) beanFactory).getBeanDefinitionNames()
    }

    @Override
    String toString() {
    return String.join(",\\n", ((String[]) names))
    }
    }

    注意用encodeURIComponent进行code编码哦

原理

  1. 为什么只需要加上@Component注解以及继承抽象类GenericServlet,就可以通过http访问了呢?
  2. 为什么接口地址(url)为类名(simpleName)呢?

原来是在springMVC启动注册的时候,会扫描Servlet的实现类(GenericServlet也是servlet的子类),然后把他注册到容器中
springMvc执行流程
在注册servlet的时候,如果全容器中只有一个servlet,那么注册的url为"/",如果有多个servlet,默认url是类名的简写

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
...
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
// 扫描所有servlet类型的bean
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(getMultipartConfig(beanFactory)));
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
...
}

private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
// 从spring容器中获取所有的Servlet类型的bean
List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
for (Entry<String, B> entry : entries) {
String beanName = entry.getKey(); // 该bean 在spring容器中的名称
B bean = entry.getValue();
if (this.seen.add(bean)) { // 如果该servlet还未生效,则使之生效
RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
registration.setOrder(getOrder(bean));
this.initializers.add(type, registration); // 保存此servlet
...
}
}
}
...
}

// servlet被包装成RegistrationBean,最后统一注册到spring容器中
private static class ServletRegistrationBeanAdapter implements RegistrationBeanAdapter<Servlet> {
...
@Override
public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
// 如果有多个servlet,那么url为bean的名称,否则url为"/"
String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
if (name.equals(DISPATCHER_SERVLET_NAME)) {
url = "/"; // always map the main dispatcherServlet to "/"
}
ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
bean.setName(name);
bean.setMultipartConfig(this.multipartConfig);
return bean;
}
}