做一道正则表达式的题目,顺便复习一下相关知识
题目:No one knows regex better than me - Bugku CTF
<?php
error_reporting(0);
$zero=$_REQUEST['zero'];
$first=$_REQUEST['first'];
$second=$zero.$first;
if(preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i",$second)){
$key=$second;
if(preg_match("/\.\.|flag/",$key)){
die("Noooood hacker!");
}else{
$third=$first;
if(preg_match("/\\|\056\160\150\x70/i",$third)){
$end=substr($third,5);
highlight_file(base64_decode($zero).$end);//maybe flag in flag.php
}
}
}
else{
highlight_file(__FILE__);
}
可以用gpt修改如下:方便直接在一些php在线工具中测试:
<?php
error_reporting(E_ALL); // Enable all errors for debugging
echo "<p>Debugging Output:</p>";
$zero = 'ZmxhZw==';
$first = 'aaaa|.php';
$second = $zero . $first;
echo "<p>Zero: " . $zero . "</p>";
echo "<p>First: " . $first . "</p>";
echo "<p>Second (concatenated zero and first): " . $second . "</p>";
if (preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i", $second)) {
$key = $second;
echo "<p>Passed first regex check with Key: " . $key . "</p>";
if (preg_match("/\.\.|flag/", $key)) {
die("Noooood hacker!");
} else {
$third = $first;
echo "<p>Third: " . $third . "</p>";
if (preg_match("/\\|\056\160\150\x70/i", $third)) {
$end = substr($third, 5);
echo "<p>End (substr of third): " . $end . "</p>";
$file_to_highlight = base64_decode($zero) . $end;
echo "<p>File to highlight: " . htmlspecialchars($file_to_highlight) . "</p>";
echo $file_to_highlight; // maybe flag in flag.php
}
}
} else {
echo "<p>Did not pass the initial regex check, showing current file.</p>";
}
?>
但是其实对于这个答案,我还是很疑惑,主要问题出在/\\|\056\160\150\x70/i
这个正则表达式上,按照一般的理解,肯定是匹配\
或着.php
,但是实际情况应该是需要匹配|.php
,很迷惑php中对于正则表达式的解析,就答案来看,就是只把后面的|
进行了转义,也就是说将两个\
作为和一个\
等效的形式进行了处理。经过进一步的测试,发现php对于一开始读取的字符串就直接进行了处理,测试在之前preg_match("/\\|\056\160\150\x70/i", $third)
里面的表达式:
<?php
$preg = "/\\|\056\160\150\x70/i";
echo $preg;
?>
结果就是/\|.php/i
,php先把\\
转意成了一个\
,然后再按照正则表达式的语法进行匹配。成功破案!
练习regex101
task 3
# 参考答案
(?![EIOU])[B-Z]
# 主要是我没想到这个能这么用,一般我觉得?!的用法只是
# \d(?!nd) 用来匹配 1st 2nd 3pc 中除了2的其他两个数字
# 但是现在有了更深的理解,匹配的过程是先匹配后面的B-Z,然后把其中以EIOU其中的某一个开头的结果排除掉。
task 7
# 我的答案
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$
# hhh确实有点蠢这个方法,但是当时确实没想到可以用\b
# 好一点的答案
^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|\d{2}|\d)(?:\.|$)){4}\b$
# 抄作业改进自己的
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.|$)){4}\b$
# 再改进一点点,尽力了44个字符,最优解是39
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.?\b){4}$
task 8
# 我的答案
<[^>]*>|^[^<]*>|<.*[^>]$
# 我的想法就是分情况,因为需要额外处理跨行情况下一行中没有<和没有>的两种情况
# 好一点的答案
(?:^|<)[^<>]*>|<[^>]*
# 对其稍加优化(当然也可以去掉?: 不过按照普遍规范,不重复使用时用?:更好)
(?:^|<)[^<]*>|<[^>]*
# 更好的答案
<[^>]*|[^<]*>
# 其实个人觉得有一点投机取巧了,因为这样虽然会得到正确结果 ,但实际上是拼接起来的,只有特定的情况下才能这么用吧
另一个不错的学习入门网站:Regex Learn
task 15
首先思路就是匹配到任意字符.任意字符
的模式然后除掉数字.数字
的情况
任意字符我想到的表示方法就是:\s|.|^|$
所以得到正则表达式为:(?!\d\.\d)(\s|.|^)\.(\s|.|$)
匹配没有太大的问题(所以也是有问题的),但是这道题要求是替换,就不太行了,因为这样默认的单次替换无法处理类似...
的情况。如果将替换目标为$1-$2
,那么...
就会替换为.-.
,按题目要求应该替换为---
所以说,还是应该只匹配到那个点,再对其直接替换。方法就是把之前的表达式拆成两半:
(?<!\d)\.|\.(?!\d)