File operation vulnerability of Java audit

File operation vulnerability of Java audit

0x00 Preface

This article intends to describe some file operation vulnerabilities that will be encountered in Java audit. For example, some arbitrary file upload, file download, file read, file delete, these operations file vulnerabilities.

0x01 file upload vulnerability

RandomAccessFile upload file case:

package com.test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/FileUploadServlet")
public class domain extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {
        InputStream inputStream = request.getInputStream();
        String realPath = request.getServletContext().getRealPath("/upload");

        System.out.println(realPath);
        File tempFile = new File(realPath,"temp.tmp");
        if (!tempFile.exists()){
            tempFile.createNewFile();
        }
        FileOutputStream fos = new FileOutputStream(tempFile);
        byte[] buffer = new byte[1024];
        int len = 0;
        while(-1 != (len = inputStream.read(buffer))){
            fos.write(buffer,len);
        }
        RandomAccessFile randomFile = new RandomAccessFile(tempFile,"r");
        randomFile.readLine();
        String contentDisposition = randomFile.readLine();
        String filename = contentDisposition.substring(contentDisposition.indexOf("filename=\""),contentDisposition.lastIndexOf("\""));
        filename = filename.replace("filename=\"","");
// 防止中文乱码
        filename = new String(filename.getBytes("ISO-8859-1"),"UTF-8");
        System.out.println(filename);
        randomFile.seek(0);
        long start = 0;
        int forth = 1;
        while(-1 != (len = randomFile.readByte()) && (forth<=4)){
            if(len == '\n'){
                start = randomFile.getFilePointer();
                forth++;
            }
        }
        fos.close();
        inputStream.close();
        File saveFile = new File(realPath,filename);
        RandomAccessFile randomAccessFile = new RandomAccessFile(saveFile,"rw");
        randomFile.seek(randomFile.length());
        long endPosition = randomFile.getFilePointer();
        int j = 1;
        while((endPosition >= 0) && j <= 2){
            endPosition --;
            randomFile.seek(endPosition);
            if(randomFile.readByte() =='\n'){
                j++;
            }
        }
        randomFile.seek(start);
        long startPoint = randomFile.getFilePointer();
        while(startPoint < endPosition-1){
            randomAccessFile.write(randomFile.readByte());
            startPoint = randomFile.getFilePointer();
        }
        randomAccessFile.close();
        randomFile.close();
        tempFile.delete();
        System.out.println("文件上传成功");
    }
}

There is no verification of any file type here. It is uploaded.

Common fileUpload class upload case:

package com.test;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/FileUploadServlet")


public class domain extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest request,IOException {
        //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
        String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File file = new File(savePath);
        if(!file.exists()&&!file.isDirectory()){
            System.out.println("目录或文件不存在!");
            file.mkdir();
        }
        //消息提示
        String message = "";
        try {
            //使用Apache文件上传组件处理文件上传步骤:
            //1、创建一个DiskFileItemFactory工厂
            DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
            //2、创建一个文件上传解析器
            ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory);
            //解决上传文件名的中文乱码
            fileUpload.setHeaderEncoding("UTF-8");
            //3、判断提交上来的数据是否是上传表单的数据
            if(!fileUpload.isMultipartContent(request)){
                //按照传统方式获取数据
                return;
            }
            //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
            List<FileItem> list = fileUpload.parseRequest(request);
            for (FileItem item : list) {
                //如果fileitem中封装的是普通输入项的数据
                if(item.isFormField()){
                    String name = item.getFieldName();
                    //解决普通输入项的数据的中文乱码问题
                    String value = item.getString("UTF-8");
                    String value1 = new String(name.getBytes("iso8859-1"),"UTF-8");
                    System.out.println(name+"  "+value);
                    System.out.println(name+"  "+value1);
                }else{
                    //如果fileitem中封装的是上传文件,得到上传的文件名称,
                    String fileName = item.getName();
                    System.out.println(fileName);
                    if(fileName==null||fileName.trim().equals("")){
                        continue;
                    }
                    //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:  c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
                    //处理获取到的上传文件的文件名的路径部分,只保留文件名部分
                    fileName = fileName.substring(fileName.lastIndexOf(File.separator)+1);
                    //获取item中的上传文件的输入流
                    InputStream is = item.getInputStream();
                    //创建一个文件输出流
                    FileOutputStream fos = new FileOutputStream(savePath+File.separator+fileName);
                    //创建一个缓冲区
                    byte buffer[] = new byte[1024];
                    //判断输入流中的数据是否已经读完的标识
                    int length = 0;
                    //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
                    while((length = is.read(buffer))>0){
                        //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
                        fos.write(buffer,length);
                    }
                    //关闭输入流
                    is.close();
                    //关闭输出流
                    fos.close();
                    //删除处理文件上传时生成的临时文件
                    item.delete();
                    message = "文件上传成功";
                }
            }
        } catch (FileUploadException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            message = "文件上传失败";
        }
        request.setAttribute("message",message);
        request.getRequestDispatcher("/message.jsp").forward(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request,IOException {
        this.doGet(request,response);
    }

}

Here, it is determined whether the file is empty, but the file type is not determined.

public class UploadHandleServlet1 extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest request,IOException {
        //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
        String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
        //上传时生成的临时文件保存目录
        String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
        File file = new File(tempPath);
        if(!file.exists()&&!file.isDirectory()){
            System.out.println("目录或文件不存在!");
            file.mkdir();
        }
        //消息提示
        String message = "";
        try {
            //使用Apache文件上传组件处理文件上传步骤:
            //1、创建一个DiskFileItemFactory工厂
            DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
            //设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
            diskFileItemFactory.setSizeThreshold(1024*100);
            //设置上传时生成的临时文件的保存目录
            diskFileItemFactory.setRepository(file);
            //2、创建一个文件上传解析器
            ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory);
            //解决上传文件名的中文乱码
            fileUpload.setHeaderEncoding("UTF-8");
            //监听文件上传进度
            fileUpload.setProgressListener(new ProgressListener(){
                public void update(long pBytesRead,long pContentLength,int arg2) {
                    System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead);
                }
            });
            //3、判断提交上来的数据是否是上传表单的数据
            if(!fileUpload.isMultipartContent(request)){
                //按照传统方式获取数据
                return;
            }
            //设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB
            fileUpload.setFileSizeMax(1024*1024);
            //设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB
            fileUpload.setSizeMax(1024*1024*10);
            //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
            List<FileItem> list = fileUpload.parseRequest(request);
            for (FileItem item : list) {
                //如果fileitem中封装的是普通输入项的数据
                if(item.isFormField()){
                    String name = item.getFieldName();
                    //解决普通输入项的数据的中文乱码问题
                    String value = item.getString("UTF-8");
                    String value1 = new String(name.getBytes("iso8859-1"),"UTF-8");
                    System.out.println(name+"  "+value);
                    System.out.println(name+"  "+value1);
                }else{
                    //如果fileitem中封装的是上传文件,得到上传的文件名称,
                    String fileName = item.getName();
                    System.out.println(fileName);
                    if(fileName==null||fileName.trim().equals("")){
                        continue;
                    }
                    //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:  c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
                    //处理获取到的上传文件的文件名的路径部分,只保留文件名部分
                    fileName = fileName.substring(fileName.lastIndexOf(File.separator)+1);
                    //得到上传文件的扩展名
                    String fileExtName = fileName.substring(fileName.lastIndexOf(".")+1);
                    if("jsp".equals(fileExtName)||"rar".equals(fileExtName)||"tar".equals(fileExtName)||"jar".equals(fileExtName)){
                        request.setAttribute("message","上传文件的类型不符合!!!");
                        request.getRequestDispatcher("/message.jsp").forward(request,response);
                        return;
                    }
                    //如果需要限制上传的文件类型,那么可以通过文件的扩展名来判断上传的文件类型是否合法
                    System.out.println("上传文件的扩展名为:"+fileExtName);
                    //获取item中的上传文件的输入流
                    InputStream is = item.getInputStream();
                    //得到文件保存的名称
                    fileName = mkFileName(fileName);
                    //得到文件保存的路径
                    String savePathStr = mkFilePath(savePath,fileName);
                    System.out.println("保存路径为:"+savePathStr);
                    //创建一个文件输出流
                    FileOutputStream fos = new FileOutputStream(savePathStr+File.separator+fileName);
                    //创建一个缓冲区
                    byte buffer[] = new byte[1024];
                    //判断输入流中的数据是否已经读完的标识
                    int length = 0;
                    //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
                    while((length = is.read(buffer))>0){
                        //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
                        fos.write(buffer,length);
                    }
                    //关闭输入流
                    is.close();
                    //关闭输出流
                    fos.close();
                    //删除处理文件上传时生成的临时文件
                    item.delete();
                    message = "文件上传成功";
                }
            }
        } catch (FileUploadBase.FileSizeLimitExceededException e) {
            e.printStackTrace();
            request.setAttribute("message","单个文件超出最大值!!!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
            return;
        }catch (FileUploadBase.SizeLimitExceededException e) {
            e.printStackTrace();
            request.setAttribute("message","上传文件的总的大小超出限制的最大值!!!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
            return;
        }catch (FileUploadException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            message = "文件上传失败";
        }
        request.setAttribute("message",IOException {
        doGet(request,response);
    }
    //生成上传文件的文件名,文件名以:uuid+"_"+文件的原始名称
    public String mkFileName(String fileName){
        return UUID.randomUUID().toString()+"_"+fileName;
    }
    public String mkFilePath(String savePath,String fileName){
        //得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
        int hashcode = fileName.hashCode();
        int dir1 = hashcode&0xf;
        int dir2 = (hashcode&0xf0)>>4;
        //构造新的保存目录
        String dir = savePath + "\\" + dir1 + "\\" + dir2;
        //File既可以代表文件也可以代表目录
        File file = new File(dir);
        if(!file.exists()){
            file.mkdirs();
        }
        return dir;
    }
}

The difference between this code and the above is that it adds a blacklist and a judgment condition, if ("JSP". Equals (fileextname) | "rar" equals(fileExtName)||"tar". equals(fileExtName)||"jar". Equals (file extname), but such a blacklist can be bypassed in the past.

The main audit depends on whether the upload place is a blacklist and how to bypass it if it is a blacklist. If it is a whitelist, you can also use% 00 truncation in the lower version of JDK.

Verify the upload case of MIME type file

public class mimetype {
    public static String main(String fileUrl) throws IOException {
        String type = null;
        

        URL u = new URL(fileUrl);

        URLConnection uc = u.openConnection();
        type = uc.getContentType();
        return type;
    }
}

0x01 arbitrary file read

Arbitrary file reading vulnerability is actually relatively simple. There are basically two methods, one is byte input stream and the other is FileReader character input stream.

InputStream:

@WebServlet("/readServlet")
public class readServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request,IOException {
    this.doGet(request,response);
    }

    protected void doGet(HttpServletRequest request,IOException {
        String filename = request.getParameter("filename");
        File file = new File(filename);
        OutputStream outputStream = null;
        InputStream inputStream = new FileInputStream(file);
        
        int len;
        byte[] bytes = new byte[1024];
        while(-1 != (len = inputStream.read())) {
            outputStream.write(bytes,len);
        }
}}

FileReader:

@WebServlet("/downServlet")
public class readServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request,IOException {
        String filename = request.getParameter("filename");
        String fileContent = "";
        FileReader fileReader = new FileReader(filename);
        
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String line = "";
        while (null != (line = bufferedReader.readLine())) {
            fileContent += (line + "\n");
        }
        }
}

These two methods are the same except that the reading and writing methods are different.

0x02 any file download

In fact, the file reading and downloading are mentioned in the previous SSRF, but the input stream is obtained when a remote request is made, and then output. In any file reading or downloading, you can directly use the IO stream to read and write, and display it to us.

@WebServlet("/downServlet")
public class readServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request,IOException {
        
        String filename = request.getParameter("filename");
        String fileContent = "";
        FileReader fileReader = new FileReader(filename);
        response.setHeader("content-disposition","attachment;fileName=" + filename);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String line = "";
        while (null != (line = bufferedReader.readLine())) {
            fileContent += (line + "\n");
        }
        }
}

It is similar to the previous file reading, except that a response body is set.

0x03 delete any file

@WebServlet("/downServlet")
public class readServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request,IOException {

        String filename = request.getParameter("filename");
        File file = new File(filename);
        PrintWriter writer = response.getWriter();
        if(file != null && file.exists() && file.delete()) {
            writer.println("删除成功");
        } else {
            writer.println("删除失败");
        }
}
}

Reference articles

https://www.cnblogs.com/lcngu/p/5471610.html
https://xz.aliyun.com/t/6986

0x04 end

Some of the codes in this article are actually relatively simple, but in practice, we still need to pay attention to some possible vulnerabilities.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>