0x00 前提
自学Java 代码审计,主要自己一个人学习,有点闭门造车,搜索引擎学习法,但是还是记录一下,也分享一下,也便于将来的总结和反思,如果我能终能学到什么,我也会重新梳理思路,为那些自学者提供一个好的思路,所以有了下面的系列文章java代码审计自学篇。
0x01 文件路径穿越
简述:
- 许多的文件漏洞都是来源于文件路径的问题,好多时候也是路径可控,再加上一下程序员奇怪的逻辑。
- 如果漏洞路径可控提供很多其他突破的方法
- 攻击者利用
../
可以上传至任意指定目录或者目录穿越。
示例代码:
中间有../
可以造成文件路径的不安全
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());
}
}
潜在的目录穿越:
一个文件被打开,然后读取文件内容,这个文件名来自于一个输入的参数。如果没有过滤这个传入的参数,那么本地文件系统中任意文件都会被读取。
文件读取有问题,别在里面拼接
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原始的流操作上传
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();
//创建file对象 转化为BufferedOutputStream对象
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.";
}
}
框架常用的封装类上传
private static String UPLOADED_FOLDER = "/tmp/";
public String FileUpload(@RequestParam("file") MultipartFile file,RedirectAttributes redirectAttributes) {
//检测文件是否存
if (file.isEmpty()) {
// 赋值给uploadStatus.html里的动态参数message
redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
return "redirect:/file/status";
}
try {
// 获取文件,上传
// 获取字节流,放入数字
byte[] bytes = file.getBytes();
//获取文件路径,目录拼接 /TMP/ + Filename
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类这些封装好的图片类,直接传进去试试
BufferedImage bi = ImageIO.read(file);
0x02文件读取
简述:
Java其实读写是一体的,都是流的输入和输出
这个漏洞主要是要结合第一个,路径穿越的情况
代码:
@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()函数可以处理。
代码:
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); //获取其file对象
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);
}
}
}