远程实时执行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"); 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编码哦
原理
- 为什么只需要加上
@Component
注解以及继承抽象类GenericServlet
,就可以通过http访问了呢?
- 为什么接口地址(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) { 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) { List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen); for (Entry<String, B> entry : entries) { String beanName = entry.getKey(); B bean = entry.getValue(); if (this.seen.add(bean)) { RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size()); registration.setOrder(getOrder(bean)); this.initializers.add(type, registration); ... } } } ... }
private static class ServletRegistrationBeanAdapter implements RegistrationBeanAdapter<Servlet> { ... @Override public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) { String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/"; if (name.equals(DISPATCHER_SERVLET_NAME)) { url = "/"; } ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url); bean.setName(name); bean.setMultipartConfig(this.multipartConfig); return bean; } }
|