Java审计之文件操作
写在前面
之前有写过文件上传审计相关的小结,现在来学习下其他类型的文件操作,因为之前觉得文件上传是应该单独拿出来的一个点去学习,所以没有放在文件操作里面。
任意文件读取/下载
@WebServlet("/FileRead")
public class FileRead extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//以当前get请求的路径+filename参数值作为File对象
File file = new File(this.getServletContext().getRealPath("/") + req.getParameter("filename"));
FileInputStream in = new FileInputStream(file);
ServletOutputStream sos = resp.getOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
sos.write(buffer, 0, len);
}
in.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
Web.xml
/etc/hosts
之前在SSRF中也有提到文件下载和文件读取。其实下载和读取的差别不大,只是多加了一条请求头header
resp.setHeader("content-disposition", "attachment;fileName=" + filename);
任意文件写入
@WebServlet("/FileWriteServlet")
public class FileWriteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
File file = new File(req.getParameter("f"));
FileOutputStream fos = new FileOutputStream(file);
fos.write(req.getParameter("c").getBytes());
fos.flush();
fos.close();
ServletOutputStream sos = resp.getOutputStream();
sos.println(file.getAbsoluteFile() + "\t" + file.exists());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
这里默认是写入到了tomcat/bin
目录下,实际情况可能还需要找一下可解析jsp的项目路径配合../
跨目录去写入webshell。当然这里应该也不局限于webshell,只要可以通过../
也是可以写其他文件去进行骚操作。
任意文件删除
@WebServlet("/FileDeleteServlet")
public class FileDeleteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
File file = new File(req.getParameter("filename"));
PrintWriter writer = resp.getWriter();
if (file.exists()){
writer.println(file.getName() + "文件已删除!");
file.delete();
}else {
writer.println("文件不存在!");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
那么这里抛出一个疑问,因为上面创建File
对象时我们对其的整个路径都是可控的(File file = new File(req.getParameter("filename"));
),那自然是可以达到删除任意路径下的文件的,那么如果我们将其改写为File file = new File(this.getServletContext().getRealPath("/") + req.getParameter("filename"));
就需要../
配合跨目录去完成任意文件删除,而不是向上面那样可以直接指定绝对路径。因为在下面这种情况中,前半部分的路径this.getServletContext().getRealPath("/")
是写死的,我们只能通过后半部分我们可控的参数去目录穿越再来达到任意文件删除。
@WebServlet("/FileDeleteServlet")
public class FileDeleteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("gbk");
File file = new File(this.getServletContext().getRealPath("/") + req.getParameter("filename"));
PrintWriter writer = resp.getWriter();
writer.println(this.getServletContext().getRealPath("/"));
writer.println(this.getServletContext().getRealPath("/") + req.getParameter("filename"));
if (file.exists()){
writer.println(file.getName() + "文件已删除!");
file.delete();
}else {
writer.println("文件不存在!");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
任意文件复制
这个漏洞比较好的机制在于没有Filter的限制下可以帮助我们重命名任意文件并移动到任意目录下完成getshell。
@WebServlet("/FileCopyServlet")
public class FileCopyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("gbk");
Path path = Files.copy(Paths.get(req.getParameter("source")), Paths.get(req.getParameter("dest")));
PrintWriter writer = resp.getWriter();
writer.println(path);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
任意文件重命名
配合白名单限制的很死的文件上传,如只能上传jpg,png的地方,配合文件重命名可getshell
@WebServlet("/FileReNameServlet")
public class FileReNameServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String fileName1 = req.getParameter("source");
String fileName2 = req.getParameter("dest");
File file1 = new File(fileName1);
File file2 = new File(fileName2);
file1.renameTo(file2);
PrintWriter writer = resp.getWriter();
writer.println(file2.getAbsolutePath() + "\t" + file2.exists());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
文件目录遍历
@WebServlet("/DirListServlet")
public class DirList extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("gbk");
String[] files = new File(req.getParameter("dir")).list();
PrintWriter writer = resp.getWriter();
for (String file : files) {
writer.println(file);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
小结
其实从上面可以看出来关于文件操作类的审计点还是蛮多的,同时因为JDK1.7新增了如下类
JDK1.7新增的基于NIO非阻塞异步读取文件的
java.nio.channels.AsynchronousFileChannel
类;JDK1.7新增的基于NIO读取文件的
java.nio.file.Files
类。常用方法如:Files.readAllBytes
、Files.readAllLines
;
但可能并不会常见。常见的可能还是java.io自带的处理文件的类,审计时也可重点关注如:
java.io.FileInputStream
java.io.FileOutputStream
org.apache.commons.io.FileUtils
- ...
当然也可以关注一下比如SSM中可能存在开发自己写的Utils
工具类,观察是否有封装操作文件的类,审计思路大致都是一样的。