题目:
我仿佛见到了一位故人。。。也难怪,题目就是ZJCTF
按要求提交/?text=data://,I have a dream&file=next.php后:
......不太行,好像得用file=php://filter/convert.base64-encode/resource=next.php
耶?那 file=php://filter/convert.base64-encode/resource=next.php 和 file=next.php在这里有啥区别啊
file=php://filter/convert.base64-encode/resource=next.php 和 file=next.php
示例:如果next.php为 <?php echo "hello"; ?>
1.file=next.php: 直接输出hello(include函数会将括号内的文件内容当作php来解析)
2.file=php://filter/convert.base64-encode/resource=next.php:
php://filter
是PHP的流包装器,允许在读取文件时对内容进行过滤处理。convert.base64-encode
过滤器会将文件内容转换为Base64编码格式。- 当使用
include
包含此路径时,PHP会先读取next.php
的内容,经过Base64编码后,再尝试将编码后的内容作为PHP代码执行。
关键点在于base64编码后的东西不再是有效代码,php无法解析,在面对无效代码这种情况下php可能会将编码后的内容作为“原始输出”返回
这样就能直接返回next.php的源代码了
源代码
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;function complex($re, $str) {return preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);
}foreach($_GET as $re => $str) {echo complex($re, $str). "\n";
}function getFlag(){@eval($_GET['cmd']);
}
代码一些地方需要注意:
preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);
preg_replace($要搜索的正则表达式的模式, $用于替换匹配项的字符串, $被进行搜索和替换的字符串); 所以关于这里的正则表达式模块:'/('.$re.')/ei',
注意preg_replace在调用的时候需要将参数用 ' ' 括起来
其中$re代表用户传入的正则表达式模式,通过字符串拼接的方式,将$re嵌入到整体正则表达式中 / 用于表示正则表达式的开始和结束,( 和 ):是捕获组的符号,用于将匹配到的内容捕获起来,以便后续引用,e
修饰符会使 preg_replace
在替换字符串时执行其中的 PHP 代码,i
修饰符表示不区分大小写进行匹配。
strtolower("\\1")
\\1
:这是对捕获组的引用,代表第一个捕获组匹配到的内容。strtolower
用于将字符串转换为小写。所以这句话意思是将捕获到的内容转换成小写
捕获组和反向引用
主要用于引用之前匹配的捕获组的内容。它允许在同一个正则表达式中复用已匹配的文本,常用于模式重复匹配或替换操作。
捕获组:()括起来的内容就是捕获组,比如说(\d+)匹配一个或者多个内容,并记录匹配的内容
反向引用:\n 表示引用第n个捕获组的内容
foreach函数触发漏洞
遍历$_GET数组,键名作为$re,键值作为$str,并将这两个值传入complex函数中
foreach($_GET as $re => $str) {echo complex($re, $str). "\n";
}
对于php中双引号和单引号的区别
双引号:
<?php echo "{${phpinfo()}}"; ?>
会返回phpinfo的界面
因为双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果(允许变量解析)。任何 ${...}
内的表达式会被执行,其返回值作为变量名
具体实现:
phpinfo()
函数会被立即执行,输出PHP配置信息(返回值为true
,即1
)。- PHP尝试解析
${phpinfo()}
的结果(即1
),等价于变量$1
。 - 由于变量名
$1
非法(不能以数字开头),会触发一个 Notice级错误(若未关闭错误提示),最终输出空字符串。
实际效果:
输出 phpinfo()
的完整信息。输出空字符串(因 $1
未定义),并可能伴随错误提示。
单引号:
<?php echo '{${phpinfo()}}'; ?>
单引号内不解析变量,所以内容按字面量输出 {${phpinfo()}}
再返回到next.php中,其基本思路就是get个键名键值都是自己输入的东西&cmd命令,其中get内容要触发getflag()函数进而触发eval()函数,cmd才有用武之地
php会将传入的非法的参数名转成下滑线
当非法字符为首字母时,只有点号会被替换成下划线
上传的$re=$str,传入 .*=${getflag()} 代入代码中就是:
preg_replace('/(.*)/ei','strtolower("\\1")',${getflag()});
getflag()
是一个函数调用,意味着会先执行 getflag
函数,然后将该函数的返回值作为目标字符串传递给 preg_replace
进行处理。
也就是说会这样
好傻啊这个函数,还会先跑去执行一下代码然后再匹配,怪不得e被弃用了
残留几个问题:
1.为什么要加上${ }? ,这个东西有什么用?
${ }用于变量替换、字符串模板、表达式嵌入
这里${getflag()}外面套上一个${ }就表示执行函数,如果不套这个${ }那么就变成
strtolower("getflag()")
此时单纯把这个当成字符串,而不能把他当成函数去解析
2.为什么 \S* 能表示 .? ?
因为大写表示取反操作
这里\s 表示匹配任意空白字符,而\S表示
匹配任意非空白字符(等价于 [^\s]
)
3.捕获组和反向引用干什么用的?
主要是为了匹配重复的字符,比如说
\b(\w+)\b\s+\1\b
\b表示单词边界,\w表示任意一个单词字符,+是个量词,表示前面的 \w 可以出现很多次,\s+表示任意一个或多个空白符号
这里就能匹配到诸如"pig pig"这样的字符串
也可以匹配标签,这里的 < 和 > 都代表真实的<>
<([a-z]+)>(.*?)</\1>
反向引用还在输出!