java-sec-code中的文件上传

2023-12-19 22:08:44

java-sec-code中的文件上传
这里仅讨论文件上传,不讨论后续利用(中间件解析漏洞等不做讨论)
任意文件伤害上传

any-->uploada.html-->upload

访问any路由时,会出现upload.html的上传文件界面,指向upload路由

@GetMapping("/any")
public String index() {
    return "upload"; // return upload.html page
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<h3>file upload</h3>

<form method="POST" th:action="upload" enctype="multipart/form-data">
    <input type="file" name="file" /><br/><br/>
    <input type="submit" value="Submit" />
</form>

</body>
</html>
private static final String UPLOADED_FOLDER = "D:\\alibaba\\upload\\";
@PostMapping("/upload")
    public String singleFileUpload(@RequestParam("file") MultipartFile file,
                                   RedirectAttributes redirectAttributes) {
        if (file.isEmpty()) {//接受请求数据包中的file参数,如果为null,则输出Please select a file to upload
            redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
            return "redirect:/file/status";
        }
        try {
            // Get the file and save it somewhere
            byte[] bytes = file.getBytes();//读取上传的文件内容
            Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());//这里的UPLOADED_FOLDER是开头定义的保存文件路径,与上传的文件名拼接。
            Files.write(path, bytes);//在拼接完成的文件路径内写入数据

            redirectAttributes.addFlashAttribute("message",
                    "You successfully uploaded '" + UPLOADED_FOLDER + file.getOriginalFilename() + "'");//输出上传成功信息

        } catch (IOException e) {
            redirectAttributes.addFlashAttribute("message", "upload failed");
            logger.error(e.toString());
        }

        return "redirect:/file/status";
    }

代码段并未对用户上传的文件进行任何过滤,导致任意文件上传漏洞

只允许上传图片

 pic-->uploadPic.html-->/upload/picture
@GetMapping("/pic")
    public String uploadPic() {
        return "uploadPic"; 
    }
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<h3>file upload only picture</h3>

<form method="POST" th:action="@{upload/picture}" enctype="multipart/form-data">
    <input type="file" name="file" /><br/><br/>
    <input type="submit" value="Submit" />
</form>

</body>
</html>
@PostMapping("/upload/picture")
    @ResponseBody
    public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception {//从请求参数中获取file的属性值,并将其赋值给multifile
        if (multifile.isEmpty()) {
            return "Please select a file to upload";
        }//检测是否为空

        String fileName = multifile.getOriginalFilename();//获取文件名
        String Suffix = fileName.substring(fileName.lastIndexOf(".")); // 获取文件后缀名
        String mimeType = multifile.getContentType(); // 获取MIME类型
        String filePath = UPLOADED_FOLDER + fileName;//拼接上传路径和文件名
        File excelFile = convert(multifile);//将multifile传入convert方法中  在最下面


        // 判断文件后缀名是否在白名单内  校验1
        String[] picSuffixList = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
        boolean suffixFlag = false;
        for (String white_suffix : picSuffixList) {
            if (Suffix.toLowerCase().equals(white_suffix)) {
                suffixFlag = true;
                break;
            }
        }//将后缀名转换为小写与白名单中的数据进行比对
        if (!suffixFlag) {//如果不在白名单中,则进入方法中
            logger.error("[-] Suffix error: " + Suffix);
            deleteFile(filePath);
            return "Upload failed. Illeagl picture.";
        }


        // 判断MIME类型是否在黑名单内 校验2
        String[] mimeTypeBlackList = {
                "text/html",
                "text/javascript",
                "application/javascript",
                "application/ecmascript",
                "text/xml",
                "application/xml"
        };
        for (String blackMimeType : mimeTypeBlackList) {
            // 用contains是为了防止text/html;charset=UTF-8绕过
            if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType)) {//replaceSpecialStr方法在最下面,去除MIME类型中除数字小写字母.点/斜杠-破折号之外的其他字符
                logger.error("[-] Mime type error: " + mimeType);
                deleteFile(filePath);
                return "Upload failed. Illeagl picture.";
            }
        }

        // 判断文件内容是否是图片 校验3
        boolean isImageFlag = isImage(excelFile);//使用ImageIO类的read方法,检测是否为JPEG、PNG、GIF 等图片格式文件
        deleteFile(randomFilePath);

        if (!isImageFlag) {
            logger.error("[-] File is not Image");
            deleteFile(filePath);
            return "Upload failed. Illeagl picture.";
        }


        try {
            // Get the file and save it somewhere
            byte[] bytes = multifile.getBytes();//获取文件内容
            Path path = Paths.get(UPLOADED_FOLDER + multifile.getOriginalFilename());//拼接完整路径
            Files.write(path, bytes);//写入文件内容
        } catch (IOException e) {
            logger.error(e.toString());
            deleteFile(filePath);
            return "Upload failed";
        }

        logger.info("[+] Safe file. Suffix: {}, MIME: {}", Suffix, mimeType);
        logger.info("[+] Successfully uploaded {}", filePath);
        return String.format("You successfully uploaded '%s'", filePath);
    }

    private void deleteFile(String filePath) {
        File delFile = new File(filePath);//新建一个file类
        if(delFile.isFile() && delFile.exists()) {//检测对应目录下是否有相应文件
            if (delFile.delete()) {//进行删除
                logger.info("[+] " + filePath + " delete successfully!");
                return;
            }
        }
        logger.info(filePath + " delete failed!");
    }

    /**
     * 为了使用ImageIO.read()
     *
     * 不建议使用transferTo,因为原始的MultipartFile会被覆盖
     * https://stackoverflow.com/questions/24339990/how-to-convert-a-multipart-file-to-file
     */
    private File convert(MultipartFile multiFile) throws Exception {
        String fileName = multiFile.getOriginalFilename();//获取文件名
        String suffix = fileName.substring(fileName.lastIndexOf("."));//获取后缀名
        UUID uuid = Generators.timeBasedGenerator().generate();//生成一个唯一的字符串标识uuid
        randomFilePath = UPLOADED_FOLDER + uuid + suffix;
        // 拼接路径,uuid和后缀名
        File convFile = new File(randomFilePath);//创建一个新的对象,路径由randomFilePath指定
        boolean ret = convFile.createNewFile();//创建一个新的文件
        if (!ret) {
            return null;
        }//检测创建文件是否成功
        FileOutputStream fos = new FileOutputStream(convFile);//创建一个新的FileOutputStream用于向convfile中写入内容
        fos.write(multiFile.getBytes());//写入文件内容
        fos.close();
        return convFile;
    }

    /**
     * Check if the file is a picture.
     */
    private static boolean isImage(File file) throws IOException {
        BufferedImage bi = ImageIO.read(file);
        return bi != null;
    }
}
public static String replaceSpecialStr(String str) {
    StringBuilder sb = new StringBuilder();
    str = str.toLowerCase();
    for(int i = 0; i < str.length(); i++) {
        char ch = str.charAt(i);
        // 如果是0-9
        if (ch >= 48 && ch <= 57 ){
            sb.append(ch);
        }
        // 如果是a-z
        else if(ch >= 97 && ch <= 122) {
            sb.append(ch);
        }
        else if(ch == '/' || ch == '.' || ch == '-'){
            sb.append(ch);
        }
    }

    return sb.toString();
}

表面上使用了多种方式验证了用户上传的文件,但实际上存在诸多问题
一,如果用户上传的是其他类型文件(例如jsp文件),虽然过不了后缀名白名单和ImageIO.read 方法过滤,但是/upload/picture方法开头有个convert方法,在未经任何过滤的前提下,进行了文件写入,虽然有个uuid使得上传后的文件名随机,但实际上的文件内容的确被写入到随机命名的jsp文件中,演示
创建一个名为test.jsp的文件,内容随机,进行上传,对应目录下生成的新文件内容和test.jsp一模一样,如果存在目录遍历漏洞则可以进行组合利用,
修改方案:在代码107行修改为 deleteFile(randomFilePath);在后缀名不符合时,删除临时文件即可,
在这里插入图片描述
二,deleteFile方法的错误利用
首先在convert方法中,上传的文件被重新命名,但deleteFile方法获取的filePath参数是方法开头直接获取的用户上传的文件名,然后直接进行删除
,在

String[] picSuffixList = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
        boolean suffixFlag = false;
        for (String white_suffix : picSuffixList) {
            if (Suffix.toLowerCase().equals(white_suffix)) {
                suffixFlag = true;
                break;
            }
        }
        if (!suffixFlag) {
            logger.error("[-] Suffix error: " + Suffix);
            deleteFile(filePath);
            return "Upload failed. Illeagl picture.";
        }

这段代码中,如果用户上传的文件后缀名不符合这个要求,则进入deleteFile方法,删除filePath文件,假设上传文件夹中有个名为test.jsp的文件,当用户上传的文件也命名为test.jsp时,程序就会删除上传目录中的
test.jsp文件
演示
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
至于是否存在任意文件删除的问题,这里不做讨论
由于ImageIO.read必须要读取到文件,所以只能够先进行上传操作,等待检验完成,在进行删除操作

文章来源:https://blog.csdn.net/qq_54030686/article/details/135085489
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。