1.序列化定义
2.序列化目的
序列化的最终目的是为了对象可以跨平台存储,和进行网络传输。
因为没有规则的字节数组我们是没办法把对象的本来原本面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候在以这种规则把对象还原回来(反序列化)。
数据库里面,是无法保存一个对象或者数组的,那么这个时候我们可以将数组进行序列化处理转化为字符串,字符串是可以保存到数据库的。
3.PHP反序列化:
php序列化函数
序列化函数:PHP serialize() 函数
//将一个对象转换成一个字符串
serialize()函数用于序列化对象或者数组,并且返回一个字符串。
serialize()是用于序列化对象后,可以很方便的将他传递到其他地方,其结构类型不会发生改变。
语法
string serialize ( mixed $value )
参数说明:
返回值:返回一个字符串。
案例:
$sites = array('sb','nima');
$serialized_data = serialize($sites);
echo $serialized_data
?>
返回
a:2:{i:0;s:2:"sb";i:1;s:4:"nima";}
格式解析:
例如:O:4:"user":2:{s:3:"age";i:18;s:4:"name";s:3:"LEO";}
O代表对象;4代表对象名长度;2代表2个成员变量;
unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。
语法
mixed unserialize ( string $str )
参数说明:
返回值:
返回的是转换之后的值,可为 integer、float、string、array 或 object。
如果传递的字符串不可解序列化,则返回 FALSE,并产生一个 E_NOTICE。
例如:
$sites = 'a:2:{i:0;s:2:"sb";i:1;s:4:"nima";}';
$serialized_data = unserialize($sites);
echo $serialized_data;
print_r($serialized_data);
?>
//echo只输出了类型
print_r表示以数组的形式输出
案例演示1:认识序列化
PHP在线执行:http://www.dooccn.com/php/
代码:
$a=array('a'=>'Apple','b'=>'banana','c'=>'Coconut');
//序列化数组
$s=serialize)($a);
echo $a;
//输出结果 :
a:3:{s:1:"a";s:5:"Apple";s:1:"b";s:6:"banana";s:1:"c";s:7:"Coconut";}
?>
案例演示2:本地实例
源码:test.php
error_reporting(0);
include "flag.php";
$KEY = "xiaodi";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
show_source(__FILE__);
?>
输入str =s:6:“xiaodi”;成功拿到flag。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k37Pj0ZO-1680605088739)(22-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.assets/202304041610622.png)]
Bugku CTF题目:https://ctf.bugku.com/challenges#flag.php,提示hint
常规思路:
打开场景后,是个登录框,发现无法登录
这个登录框没啥用,根据提示在url后面加?hint=111,系统返回源代码
获取到代码进行分析:
从代码中可以分析出大概流程为:
首先判断hint这个请求中数据,不为空则进行显示。
如果为空给然后用反序列化后的cookie中的数据和$KEY进行比对。
最后得到flag。
审计源代码,发现反序列化漏洞。
漏洞利用:
<1>所以我们传入Cookie的值应该为:
使用php在线执行工具,将key序列化
<2>构造请求,将反序列化后的key放入cookie中,
<3>放行请求,发现并未返回flag。为什么呢?原因是源码这里有2个坑:
这时候发现问题:
不显示原因有两点:
1.没有将hint参数进行移除导致,后续的判断不进行执行,也就无法打印出flag。
2.cookie判断的KEY值是在后面进行的,因为代码执行顺序问题,也就是这里key并没有按照下面的来。
这里的KEY应该是为空。
以下,将请求中的hint参数删除,并将cookie值改为序列化后的空值,放行请求,成功拿到flag。
魔术方法是一种特殊的方法,像函数但又不是,当对对象执行某些操作时会覆盖 PHP 的默认操作。
参考网址:
https://m.php.cn/article/486923.html
触发:unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法:
_construct() //创建对象时触发
_destruct() //对象被销毁时触发
_call() //在对象上下文中调用不可访问的方法时触发
_callStatic() //在静态上下文中调用不可访问的方法时触发
_get() //用于从不可访问的属性读取数据
_set() //用于将数据写入不可访问的属性
_isset() //在不可访问的属性上调用isset()或empty()触发
__sleep() //使用 serialize 序列化时自动调用
__wakeup() //使用 unserialize 反序列化时自动调用
测试代码
class ABC{
public $test;
function __construct(){
$test = 1;
echo '调用了构造函数
';
}
function __destruct(){
echo '调用了析构函数
';
}
function __wakeup(){
echo '调用了苏醒函数
';
}
}
echo '创建对象a
';
$a = new ABC;
echo '序列化
';
$a_ser=serialize($a);
echo '反序列化
';
$a_unser=unserialize($a_ser);
echo '对象快要死了!';
?>
//运行结果
创建对象a<br>调用了构造函数<br>
序列化<br>反序列化<br>调用了苏醒函数
<br>对象快要死了!调用了析构函数<br>
地址:https://www.ctfhub.com/#/challenge
进入源码分析:
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
//弱类型判断,仅判断数值,op 赋值数字2或字符串' 2'也成立
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]:
";
echo $s;
}
function __destruct() {
/析构函数,销毁类时执行,也就是在最后执行
if($this->op === "2")
/强类型比较,判断数值+类型,可以使用数字2或字符串' 2'绕过判断
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
//反序列化,所以要先序列化。
}
}
首先由ctf命名及代码函数unserialize判断本题考察反序列化知识点:
第一:获取flag存储flag.php
第二:两个魔术方法__destruct __construct
第三:传输str参数数据后触发destruct,存在is_valid过滤
第四:__destruct中会调用process,其中op=1写入及op=2读取
第五:涉及对象FileHandler,变量op及filename,content,进行构造输出
涉及:反序列化魔术方法调用,弱类型绕过,ascii绕过
使用该类对flag进行读取,这里面能利用的只有__destruct函数(析构函数)
__destruct函数中if(this−>op==="2")代码,对op进行了===判断(强类型)并且op值为字符串2时会赋值为1,process函数中if(
this->op == "2")代码,使用==判断(弱类型)(op值为2的情况下才能读取内容),
因此这里存在弱类型比较,可以使用数字2或字符串' 2'绕过判断。
is_valid函数还对序列化字符串进行了校验。
因为PHP序列化的时候,若成员被private和protected修饰,会引入不可见字符\x00,这些字符对应的ascii码为0,这是个ASCII不在32到125之间的字符,经过is_valid函数以后会返回false,导致无法执行到反序列函数。
经过测试,在PHP7.2+的环境中,使用public修饰成员并序列化,反序列化后成员也会被public覆盖修饰。因此可以改成public来绕过is_valid函数校验。
payload:
先序列化:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzq8JIBd-1680773391610)(22-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.assets/image-20230405172552539.png)]
反序列化中如果写入后门,sql注入等危害会极大。
Java中的API实现:
位置:java.objectOuputStream
java.io.ObjectInputStream
序列化 :objectOutputStream类 -->writeObject()
住:该方法对参数指定的obj对象进行序列化,对字节序列写到一个目标输出流中。
按JAVA的标准约定是给文件一个.ser扩展名
反序列化: objectInputStream类–>readObject()
住:该方法从一个源输入流中读取字节序列,在·把他们反序列化为一个对象,并将其返回。
#序列化和反序列化
序列化(Serialization): 将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。
反序列化:从存储区中读取该数据,并将其还原为对象的过程,成为反序列化。
java反序列化代码测试
序列化过程:使用writeobject方法,将Person对象数据转化为txt乱码数据;
反序列化过程:使用readobject方法,将txt乱码数据转化为Person对象数据。
命令执行代码测试
结合java反序列化+代码执行,将会给系统带来不可预料的危害。
没有回显就要用到反弹shell。
WebGoat漏洞环境下载:https://github.com/WebGoat/WebGoat/releases
启动环境命令:java -jar webgoat-server-8.2.2.jar
Java反序列化验证工具:https://github.com/frohoff/ysoserial/releases
centos7上安装漏洞环境教程:
1、安装Docker
# 1、yum 包更新到最新
yum update
# 2、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2
# 3、 设置yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4、 安装docker,出现输入的界面都按 y
yum install -y docker-ce
# 5、 查看docker版本,验证是否验证成功
docker -v
2、设置仓库:
yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
3、启动并加入开机启动
systemctl start docker
systemctl enable docker
4、验证是否安装成功
docker version
5、下载靶场启动
jiang@ubuntu:~$ docker pull webgoat/webgoat-8.0
jiang@ubuntu:~$ docker run -d -p 8080:8080 webgoat/webgoat-8.0:latest
外部访问:
(http://192.168.31.131:8080/WebGoat/login)
注册登录靶机:
2、使用jd-gui工具或者IDEA工具(推荐)打开webgoat-server-8.2.2.jar,分析源代码
找到反序列化函数
反序列化这里可以执行对象中的代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5lSjfaZV-1680773391617)(22-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.assets/1375459-20211227193653541-320246282.png)]
3、此时,基本可以 确定存在反序列化漏洞。如何利用?
本题目页面上的数据以rO0AB开头,可以确定这串就是JAVA序列化base64加密的数据。
所以我们在构造payload时,需要如下几步:
构造含有命令(比如ipconfig)的对象–>序列化–>base64–>最终payload(rO0AB开头字符串)
但是攻击时,还需要考虑回显问题,若系统不回显命令执行后的结果,即使我们攻击成功,也没用。因此我们一般需要构造反弹shell。
这个过程挺复杂,手工的话,需要一个一个尝试,效率低且不一定成功。因此我们可以使用工具生成payload。
Java反序列化验证工具
如下图,使用ysoserial工具生成payload。这里由于源代码中有hibernate组件,因此我们可以执行命令调用该组件生成payload。
手册使用教程地址:
(39条消息) 反序列化工具ysoserial使用介绍_反序列化利用工具_莫语闲语的博客-CSDN博客
启动:
java -jar ysoserial.jar
使用ysoserial工具生成payload。这里由于源代码中有hibernate组件,因此我们可以执行命令调用该组件生成payload。
calc.exe表示计算器
payload如下
这里我们还需要将payload进行base64加密(ysoserial只能进行反序列化,它不负责base64加密)
写一个python脚本,执行,实现base64加密
import base64
filename = input("输入需要base64编码的文件名:")
s = open(filename, "rb").read() #文本默认模式读取文件内容rt
base64_str = base64.urlsafe_b64encode(s)
#文本默认模式写入文件内容wt
open("base64.txt", "wt",encoding="utf-8").write(base64_str.decode())
生成payload.txt
全选,复制粘贴到本题目输入框,点击提交,弹出计算器。
CTFHub官网:
https://www.ctfhub.com/\#/challenge
根据提示附件进行javaweb代码审计,判断可能存在sql注入。
页面打开如下
见面没有什么,打开burpsuite抓包试试: