0x00 前提 自学Java 代码审计,主要自己一个人学习,有点闭门造车,搜索引擎学习法,但是还是记录一下,也分享一下,也便于将来的总结和反思,如果我能终能学到什么,我也会重新梳理思路,为那些自学者提供一个好的思路,所以有了下面的系列文章java代码审计自学篇。
0x01 文件路径穿越 简述:
许多的文件漏洞都是来源于文件路径的问题,好多时候也是路径可控,再加上一下程序员奇怪的逻辑。
如果漏洞路径可控提供很多其他突破的方法
攻击者利用../
可以上传至任意指定目录或者目录穿越。
示例代码: 中间有../
可以造成文件路径的不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 package file; import java.io.File; import java.io.IOException; public class filepath { public static void main(String[] args) throws IOException { File file = new File("../../file/123.txt"); System.out.println(file.getAbsolutePath()); System.out.println(file.getCanonicalPath()); System.out.println(file.exists()); } }
潜在的目录穿越: 一个文件被打开,然后读取文件内容,这个文件名来自于一个输入的参数。如果没有过滤这个传入的参数,那么本地文件系统中任意文件都会被读取。
1 2 3 4 5 6 文件读取有问题,别在里面拼接 val result = Source.fromFile("public/" + value).getLines().mkString // Weak point 修复:要在外面拼接好 filename = "public/" + FilenameUtils.getName(value) val result = Source.fromFile(filename).getLines().mkString
0x02文件上传 简述: 文件上传过程中,因为未校验上传文件后缀类型,导致用户可上传jsp和jspx等一些webshell文件。
代码审计时可重点关注对上传文件类型是否有足够安全的校验。
漏洞示例: 没有任何过滤
jdk原始的流操作上传
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 public String FileUpload (MultipartFile file) { String fileName = file.getOriginalFilename(); if (fileName==null ) { return "file is error" ; } String filePath = "/static/images/uploads/" +fileName; if (!file.isEmpty()) { try { byte [] bytes = file.getBytes(); BufferedOutputStream stream = new BufferedOutputStream (new FileOutputStream (new File (filePath))); stream.write(bytes); stream.close(); return "OK" ; } catch (Exception e) { return e.getMessage(); } } else { return "You failed to upload " + file.getOriginalFilename() + " because the file was empty." ; } }
框架常用的封装类上传
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 private static String UPLOADED_FOLDER = "/tmp/" ;public String FileUpload (@RequestParam("file") MultipartFile file,RedirectAttributes redirectAttributes) { if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message" , "Please select a file to upload" ); return "redirect:/file/status" ; } try { byte [] bytes = file.getBytes(); Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); Files.write(path, bytes); redirectAttributes.addFlashAttribute("message" , "You successfully uploaded '" + UPLOADED_FOLDER + file.getOriginalFilename() + "'" ); } catch (IOException e) { redirectAttributes.addFlashAttribute("message" , "upload failed" ); e.printStackTrace(); return "redirect:/file/status" ; } return "redirect:/file/status" ; }
审计函数 java中文件操作的函数特别多,有的是原始的字节字符流
java都是基于流的,还有好多都是后面有封装的,感觉如果不熟就直接 搜索file吧,再检查 过滤条件
JDK原始的java.io.FileInputStream
JDK原始的 BufferedOutputStream
JDK原始的各种OutputStream,流操作都可以
Apache Commons IO提供的org.apache.commons.io.FileUtils类
参考园长文章
修复方案
使用白名单校验上传文件类型、大小限制、MIME类型
白名单fileName.substring(fileName.lastIndexOf(“.”)); 检查后缀名
还有一个BufferedImage类、Image类、Graphics类这些封装好的图片类,直接传进去试试
1 BufferedImage bi = ImageIO.read(file);
0x02文件读取 简述: Java其实读写是一体的,都是流的输入和输出
这个漏洞主要是要结合第一个,路径穿越的情况
代码: 1 2 3 4 5 6 7 8 9 10 @GetMapping("/path_traversal/") public String getImage (String filepath) throws IOException {File f = new File (filepath);if (f.exists() && !f.isDirectory()) { byte [] data = Files.readAllBytes(Paths.get(filepath)); return new String (Base64.encodeBase64(data)); } else { return "File doesn't exist or is not a file." ; }
审计函数
JDK原始的java.io.RandomAccessFile类
JDK原始的inputsteam类
Apache Commons IO提供的org.apache.commons.io.FileUtils类
JDK1.7新增的基于NIO非阻塞异步读取文件的java.nio.channels.AsynchronousFileChannel
类
JDK1.7新增的基于NIO读取文件的java.nio.file.Files
类
常用方法如:Files.readAllBytes
、Files.readAllLines
参考园长文章
修复方案: 过滤目录穿越关键字
0x01 目录遍历 简述: 目录遍历,主要看逻辑吧,能不能回显
有专门file.listFiles()函数可以处理。
代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package file;import java.io.File;import java.io.FileFilter;public class filepath { public static void main (String[] args) { String path = "/Users/zy/Desktop/java_rmi/src/main/java/" ; File file = new File (path); func(file); } private static void func (File file) { File[] fs = file.listFiles(); for (File f:fs){ if (f.isDirectory()) func(f); if (f.isFile()) System.out.println(f); } } }