【愚公系列】2024年08月 《CTF实战:从入门到提升》 010-Web安全入门(PHP代码执行漏洞)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。
🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
PHP代码执行漏洞是一种常见的安全漏洞,也被称为远程代码执行(Remote Code Execution,RCE)漏洞。这种漏洞的发生通常是由于程序员在编写代码时未正确过滤用户输入,导致用户能够将恶意代码插入到应用程序中,然后被解释器执行。攻击者可以利用这种漏洞执行任意代码,从而获取系统权限、窃取数据或者对系统进行进一步的攻击。
以下是一些常见导致PHP代码执行漏洞的原因和防范措施:
-
未过滤用户输入:开发者在使用用户输入时必须进行严格的输入验证和过滤,避免直接将用户输入作为代码执行。
-
eval() 和 exec() 函数:避免使用可执行字符串的函数,如 eval() 和 exec(),因为它们允许执行任意代码。
-
文件包含漏洞:在包含文件时,要确保只包含信任的文件,避免包含用户能够控制的文件。
-
限制文件权限:确保应用程序运行时的权限是最小化的,避免攻击者执行恶意代码时获取系统权限。
-
安全配置:对于PHP的配置,应该限制函数的使用、关闭危险的函数(如 system()、passthru() 等),并且定期更新PHP版本以获取最新的安全补丁。
-
输入验证和输出编码:对于输入数据进行验证和过滤,对输出进行适当的编码,避免XSS等攻击。
避免PHP代码执行漏洞的关键在于良好的编程实践、严格的输入验证和输出过滤,以及对系统和应用程序的安全性进行全面的审查和测试。
🚀一、PHP代码执行漏洞
🔥1.PHP中代码漏洞的概念
如果有开发经验,或者是经常在CTF比赛中做Web题,下面这种样式的代码想必也见得不少:
最后五行是等效的代码。如果把这个代码部署在我们本地的PHP服务器上进行访问的话,会发现给method这个GET参数传入method1即可调用执行method1,输出“Here is method1”,同理,输入method2和method3就会输出名自对应函数的运行结果。我们在编写代码时并不需要做大量的判断,就可以在代码运行时通过传入相应的参数即可调用对应的函数,十分方便,如图所示。
但这种便利存在的同时,其中存在的危险也是不容忽视的。例如,“'eval("(new TestController())->".$ GET['method’]."();");”这段代码,我们如果写一句话木马的话,“<?phpeval($ POST [‘a’]);”(这句话的本质就是允许你本地传输一段PHP代码过来:它进行运行然后返回结果),它把输入的method参数也进行了一个拼接执行然后把结果展示了出来。两者都是把输入当成代码或者代码的一部分去执行了。
如下:
进行/2.php?method=method1();phpinfo的访问,如图所示。
我们会看到除了method1的结果展示出来了以外,phpinfo这个函数的运行结果也被展示出来了。
从代码执行的逻辑上来理解,原本正常访问/?method=method1,eval接收到参数,拼接出来的代码是“(newTestController())->method1();”,正常调用method1。但如果传入的参数是method1();phpinfo,那么拼接出来的代码就是“(new TestController())>method1();phpinfo;”,运行的结果就很有意思了,除了method1被调用了以外,phpinfo也被调用执行了。我们利用这里存在的代码执行漏洞成功进行了一次利用。
总结来说,代码执行漏洞就是在代码中若存在eval、assert等能将所接收的参数作为代码去执行,并且拼接的内容可被访问者控制,也就是把传入的参数给拼接进去了,造成了额外的代码执行也就造成了代码执行漏洞。
🔔2. PHP代码执行漏洞函数
🤔2.1 eval
在 PHP 中,eval()
函数是用于执行字符串中包含的 PHP 代码的函数。eval()
函数接受一个字符串作为参数,该字符串包含要执行的 PHP 代码。下面是关于 eval()
函数参数的详细说明:
- **eval(string code`,其中包含要执行的 PHP 代码。这个参数是必需的,它指定了要执行的代码段。
下面是一个简单的示例,演示了如何使用 eval()
函数来执行一个简单的 PHP 表达式:
$code = 'echo "Hello, World!";';
eval($code);
在这个示例中,$code
变量包含了一个简单的 PHP echo 语句,然后通过 eval()
函数执行这段代码,最终输出 “Hello, World!”。
需要注意的是,传递给 eval()
函数的代码字符串应该是有效的 PHP 代码,否则会导致语法错误或执行失败。同时,出于安全考虑,应该避免在 eval()
中执行不受信任的或未经验证的代码,以防止代码注入和其他安全风险。
注意:代码结尾需要用;
🤔2.2 assert
在 PHP 中,assert()
函数用于检查一个表达式是否为真,如果表达式为假,则会触发一个断言(Assertion)。assert()
函数在调试和测试阶段非常有用,可以帮助开发者验证代码的正确性。下面是关于 assert()
函数参数的详细说明:
-
assert(mixed $assertion [, string $description = “”]):
assert()
函数接受两个参数,第一个参数是要评估的断言表达式,第二个参数是可选的描述信息。-
$assertion
:这是一个要进行断言的表达式,可以是任何返回布尔值的表达式。如果这个表达式的结果为 false,则触发断言。 -
$description
:这是一个可选参数,用于提供关于断言失败的描述信息。如果提供了此参数,断言失败时会将描述信息一并输出。
-
下面是一个示例,演示了如何使用 assert()
函数进行断言:
$value = 10;
assert($value > 0, "Value should be greater than 0");
在这个示例中,如果 $value
的值不大于 0,那么断言就会失败,同时输出描述信息 “Value should be greater than 0”。
需要注意的是,在生产环境中,通常会将断言功能关闭,以避免因为断言检查导致性能下降。可以通过在 php.ini 文件中设置 assert.active = 0
来关闭断言功能。在开发和测试阶段,可以将 assert.active = 1
以及其他相关的配置项设置为开启状态,以便进行调试和测试。
注意:代码结尾不需要用;
🤔2.3 assert
在 PHP 中,call_user_func()
函数用于调用用户自定义函数,可以将函数名作为字符串参数传递,也可以传递一个包含函数名和参数的数组。下面是关于 call_user_func()
函数参数的详细说明以及可能导致漏洞产生的原因:
-
call_user_func(callable $callback [, mixed $parameter [, mixed $… ]]):
call_user_func()
函数接受一个可调用的回调函数作为第一个参数,可以是一个函数名的字符串,也可以是一个包含函数名和参数的数组。可以传递多个参数,根据实际情况传递。-
$callback
:表示要调用的回调函数,可以是一个字符串形式的函数名,也可以是一个包含函数名和参数的数组。如果是一个数组,数组的第一个元素表示函数名,后续元素表示函数的参数。 -
$parameter, $...
:可选参数,表示要传递给回调函数的参数。这些参数可以是任意类型的值,根据回调函数的定义进行传递。
-
下面是一个示例,演示了如何使用 call_user_func()
函数调用一个函数:
function greet($name) {
echo "Hello, $name!";
}
call_user_func('greet', 'John');
在这个示例中,call_user_func()
函数调用了 greet()
函数,并传递了名为 ‘John’ 的参数。
像上面我们的那个例子里:
这样输入,得到参数为[‘TestController’,‘method1’],而如果只是单纯地访问/?req=phpinfo,调用的就是phpinfo。
🤔2.4 create_function
在PHP 7.2.0版本之前,create_function()
是用来创建匿名(即没有指定名称的)函数的一个方法。然而,由于安全性和性能的原因,从PHP 7.2.0版本开始,这个函数已经被弃用,并且在PHP 8.0.0版本中被完全移除。因此,现在推荐使用匿名函数(Closure)来代替 create_function()
的使用。
使用create_function()
的例子(在PHP 7.1及之前版本)
尽管不推荐使用,但为了解释其参数,下面是create_function()
的一个例子:
$func = create_function('$a, $b', 'return $a + $b;');
echo $func(2, 3); // 输出 5
create_function()
函数有两个参数:
-
参数列表 (
string $args
):这是一个字符串,表示函数的参数列表。参数应该以逗号分隔,就像在正常函数定义中的参数列表一样。 -
函数体 (
string $code
):这是另一个字符串,包含了函数的PHP代码。这段代码在函数被调用时执行。
安全问题:
create_function()
使用了 eval()
来创建函数,这意味着它执行的是字符串中的PHP代码。这可能会带来安全风险,因为如果执行的代码是用户提供的,它可能被用来执行恶意代码。这是为什么推荐使用匿名函数来代替的主要原因之一。
就像上面那个例子:
对于参数我们传入method1,则会调用method1。但如果像上面那个eval的例子一样插入“method1();phpinfo’的话,除了会调用method1之外,也会调用phpinfo。
推荐的替代方案:匿名函数
从PHP 5.3.0版本开始,可以使用匿名函数作为 create_function()
的安全和更加灵活的替代方法。匿名函数提供了更好的代码可读性,更简洁的语法,以及闭包的功能。
$func = function($a, $b) {
return $a + $b;
};
echo $func(2, 3); // 输出 5
在现代PHP代码中,应该总是优先使用匿名函数而不是 create_function()
,以确保代码的安全性和维护性。
🤔2.5 array_walk、array_map、array_filter
在PHP中,array_walk()
, array_map()
, 和 array_filter()
是处理数组的三个非常有用的函数。它们各自有不同的用途和参数配置,下面将详细解释它们的用法和参数。
1、 array_walk()
array_walk()
函数用于对数组中的每个元素应用用户自定义函数。这个函数主要用于在数组每个元素上运行一个指定的函数,通常用于修改数组元素或收集信息。
参数:
array &$array
: 要处理的数组,注意是引用传递。callable $callback
: 对每个元素调用的用户自定义函数。回调函数接受当前元素的值作为第一个参数,当前元素的键作为第二个参数,如果提供了第三个参数$userdata
,则作为第三个参数传递给回调。mixed $userdata
(可选): 可选的第三参数,将作为第三个参数传递给$callback
函数。
示例:
$array = [1, 2, 3];
array_walk($array, function(&$item, $key) {
$item *= 2;
});
print_r($array);
2、array_map()
array_map()
函数返回由数组每个元素应用回调函数处理后的数组。与array_walk()
不同,array_map()
可以处理多个数组,并且它不修改原始数组而是返回一个新数组。
参数:
callable|null $callback
: 要对数组中的每个元素调用的回调函数。如果为null
,则直接返回数组。array $array
: 单个数组或多个数组。
示例:
$array = [1, 2, 3];
$result = array_map(function($item) {
return $item * 2;
}, $array);
print_r($result);
3、array_filter()
array_filter()
函数用回调函数过滤数组中的元素,只返回那些返回true
的元素。它通常用于移除数组中不想要的元素。
参数:
array $array
: 输入的数组。callable|null $callback
(可选): 使用的回调函数,如果没有提供或者为null
,则会移除数组中所有等同于false
的元素。int $flag
(可选): 可以用来改变传递给回调函数的参数。默认是0
或ARRAY_FILTER_USE_BOTH
,传递值和键名;ARRAY_FILTER_USE_KEY
只传递键名;ARRAY_FILTER_USE_BOTH
传递键名和值。
示例:
$array = [0, 1, 2, 3, 4];
$result = array_filter($array, function($item) {
return $item > 2;
});
print_r($result);
每个函数都有其特定用途:array_walk()
通常用于元素的修改,array_map()
用于生成基于原始数组修改后的新数组,而 array_filter()
用于根据条件过滤数组元素。根据需要选择适合的函数是很重要的。
这是三个操作数组的函数,我们要利用的是它们的第一/二个参数callback,这里允许我们传入一个函数名,这些函数会将第一/二个参数中数组的每个元素作为参数传入调用函数中。
则等效于:
🤔2.6 $ GET ‘method’
PHP比较灵活。在代码中函数名可以接收一个参数,当你给method这个GET参数传入的不再是?method[]=TestController&method[]=method2,而是?method=phpinfo的话,结果就很明显,是调用了phpinfo。
其实除了以上所提到的函数以外还有usort、ob_start等执行函数,PHP奇妙无比,期待各位读者自己细细体会。
🧙🏻3. 案例解析–虎符网络安全大赛Unsetme
这是“2021年虎符网络安全大赛Unsetme”题。
打开靶机,如图所示:
结合代码和上网搜索可以得出这是F3框架。
把代码复制到本地,打开报错,查看代码,可以一直运行到 lib/base.php 的530行,如图所示。
尝试输出,可以看到unset的代码将a拼接了进来,如图所示。
那么就想办法闭合代码,使其合法即可,如图所示。
然后就可以读取flag了,如图所示。
通过分析,找到了这组代码中存在的代码执行漏洞,插入了我们想要执行的代码。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)