CTF-WEB:字符串和正则匹配
匹配过滤
在有的 Web 题目中会使用字符串匹配或正则表达式过滤传入的变量,此时看懂匹配规则构造正确的变量就很重要。
JavaScript match() 方法
match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。语法如下,函数的返回值为存放匹配结果的数组,该数组的内容依赖于 regexp 是否具有全局标志 g。
stringObject.match(searchvalue)
stringObject.match(regexp)
参数 | 说明 |
---|---|
searchvalue | 必需,规定要检索的字符串值。 |
regexp | 必需,规定要匹配的模式的 RegExp 对象。 |
PHP preg_match() 函数
preg_match 函数用于执行匹配正则表达式,搜索 subject 与 pattern 给定的正则表达式的一个匹配。函数返回 pattern 的匹配次数,值是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后 将会停止搜索。preg_match_all() 不同于此,它会一直搜索subject 直到到达结尾。如果发生错误 preg_match() 返回 FALSE。函数语法为:
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
参数 | 说明 |
---|---|
pattern | 要搜索的模式,字符串形式。 |
subject | 被正则匹配的字符串。 |
matches | 如果提供了参数matches,它将被填充为搜索结果。matches[0] 将包含完整模式匹配到的文本,$matches[1] 将包含第一个捕获子组匹配到的文本以此类推。 |
flags | 可以被设置为标记值。 |
offset | 搜索从目标字符串的开始位置开始。 |
PHP ereg() 函数
ereg() 函数用于正则表达式匹配,以区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串。函数语法如下:
ereg ( string $pattern , string $string [, array &$regs ] ) : int
如果找到与 pattern 中圆括号内的子模式相匹配的子串并且函数调用给出了第三个参数 regs,则匹配项将被存入 regs 数组中。\(regs[1] 包含第一个左圆括号开始的子串,\)regs[2] 包含第二个子串以此类推,$regs[0] 包含整个匹配的字符串。如果在 string 中找到 pattern 模式的匹配则返回 所匹配字符串的长度,如果没有找到匹配或出错则返回 FALSE。如果没有传递入可选参数 regs 或者所匹配的字符串长度为 0,则本函数返回 1。
- 使用 Perl 兼容正则表达式语法的 preg_match() 函数通常是比 ereg() 更快的替代方案。
例题:bugku-字符?正则?
源码如下,目的很明确,传入一个符合正则匹配的参数 id 即可得到 flag。
<?php
highlight_file(\'2.php\');
$key =\'KEY{********************************}\';
$IM = preg_match("/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i", trim($_GET["id"]), $match);
if( $IM ){
die(\'key is: \'.$key);
}
?>
接下来分析匹配的正则表达式,所用的表达式语法如下:
正则表达式 | 匹配内容 |
---|---|
. | 匹配除 “\n” 之外的任何单个字符 |
* | 匹配它前面的表达式 0 次或多次,等价于{0,} |
.{4,7} | 匹配 4 到 7 个任意字符 |
/ | 匹配 /,\ 是为了转义 |
[a-z] | 匹配所有小写字母 |
[:punct:] | 匹配任何标点符号 |
/i | 表示不分大小写 |
根据以上规则,构造出一个符合要求的字符串,提交获得 flag。
?id=keyakeyaaaakey:/a/akeya;
例题:bugku-ereg 正则 %00 截断
源码如下,这题首先要求通过第一个分支语句,使用 ereg() 实现一个正则匹配,匹配的内容是一个以上的数字或字母。第二个分支语句要判断输入的字符串长度是否小于 8,同时它的值要大于 9999999。最后还要判断字符串是否有包含“-”,如果包含则输出 flag。
<?php
$flag = "xxx";
if (isset ($_GET[\'password\']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET[\'password\']) === FALSE)
{
echo \'You password must be alphanumeric\';
}
else if (strlen($_GET[\'password\']) < 8 && $_GET[\'password\'] > 9999999)
{
if (strpos ($_GET[\'password\'], \'*-*\') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die(\'Flag: \' . $flag);
}
else
{
echo(\'*-* have not been found\');
}
}
else
{
echo \'Invalid password\';
}
}
?>
这里需要实现的内容很多,首先需要传入的字符串长度小于 8,也就是说最大传入的数字不会大于 9999999。这里可以用科学技术法来替代,10000000 的科学计数法的表达为 1e7。接着在字符串中要包含字符 “–”,但是第一个分支的正则匹配会阻止继续,这时可以使用 “%00” 来阶段字符串,使得正则匹配只匹配到前面的部分,而字符 “–” 就放在 “%00” 的后面。综上所述,构造 payload:
?password=1e7%00*-*
例题:bugku-数字验证正则绕过
题目的源码如下,首先要用 preg_match() 一个正则表达式匹配,[: graph:] 表示任意一个可打印字符,此处要求 password 长度大于 12。接下来要用 preg_match_all() 进行全局匹配,每匹配成功一次就加 1,一直匹配到字符串结束。这要求 password 中必须包含标点符号、数字、大写字母、小写字母等,并且被检测到 6 次以上才能绕过。
<?php
error_reporting(0);
$flag = \'flag{test}\';
if ("POST" == $_SERVER[\'REQUEST_METHOD\'])
{
$password = $_POST[\'password\'];
if (0 >= preg_match(\'/^[[:graph:]]{12,}$/\', $password)) //preg_match — 执行一个正则表达式匹配
{
echo \'flag\';
exit;
}
while (TRUE)
{
$reg = \'/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/\';
if (6 > preg_match_all($reg, $password, $arr)){
break;
}
$c = 0;
$ps = array(\'punct\', \'digit\', \'upper\', \'lower\');
//[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3)
break;
//>=3,必须包含四种类型三种与三种以上
if ("42" == $password)
echo $flag;
else
echo \'Wrong password\';
exit;
}
}
?>
最后要判断 password 是否等于 42,注意到这是使用 “==” 进行判断的,可以使用熟悉的弱类型。综上所述,password 的长度应该大于 12,同时包含数字、大写字母、小写字母和符号,而且开头是 42 后接字母,使得弱类型转换时可以转换为 42。此时可以使用 HackBar 的 POST 提交:
password= 42Aaaaaaaaaa+
例题:攻防世界-NaNNaNNaNNaN-Batman
打开文件,看到一堆乱码,根据 “script” 标签判断这是 JavaScript 代码。
<script>
_=\'function $(){e=getEleById("c").value;
length==16^be0f23233ace98aa$c7be9){tfls_aie}na_h0lnrg{e_0iit\\'_ns=[t,n,r,i];
for(o=0;o<13;++o){ [0]);
.splice(0,1)}}} \\'<input id="c">< οnclick=$()>Ok</>\\');delete _var ","docu.)match(/"];/)!=null=[" write(s[o%4]buttonif(e.ment\';
for(Y in $=\' \') with(_.split($[Y]))_=join(pop());
eval(_)
</script>
修改后缀为 “.html”,打开文件是一个输入框,此时我们要知道需要输入什么东西。
首先我们先看懂上述代码,注意到代码定义了一个参数 “_”,然后给出了一个字符串看上去是个函数,然后用 eval() 函数可计算字符串,执行其中的的 JavaScript 代码。这里可以把 eval() 函数改为 alert() 函数,该函数可以显示一段文本,从而进行程序的调试,我们可以把这段 JavaScript 代码的正确形式显示出来。
<script>
_=\'function $(){e=getEleById("c").value;
length==16^be0f23233ace98aa$c7be9){tfls_aie}na_h0lnrg{e_0iit\\'_ns=[t,n,r,i];
for(o=0;o<13;++o){ [0]);
.splice(0,1)}}} \\'<input id="c">< οnclick=$()>Ok</>\\');delete _var ","docu.)match(/"];/)!=null=[" write(s[o%4]buttonif(e.ment\';
for(Y in $=\' \') with(_.split($[Y]))_=join(pop());
alert(_)
</script>
修改后打开网页,就能得到正确的 JavaScript 代码如下。在正则中 “^” 表示开头,“$” 表示末尾,在这里用 match() 函数匹配在输入框里输入的值要满足输入的字符串长度必须为 16 个字符。字符串的开头必须要匹配 “be0f23”,字符串的结尾必须要匹配 “e98aa”,字符串中要能匹配到 “233ac” 和 “c7be9”。
function $()
{
var e = document.getElementById("c").value;
if(e.length == 16)
if(e.match(/^be0f23/)!=null)
if(e.match(/233ac/)!=null)
if(e.match(/e98aa$/)!=null)
if(e.match(/c7be9/)!=null)
{
var t=["fl","s_a","i","e}"];
var n=["a","_h0l","n"];
var r=["g{","e","_0"];
var i=["it\'","_","n"];
var s=[t,n,r,i];
for(var o = 0; o < 13; ++o)
{
document.write(s[o%4][0]);
s[o%4].splice(0,1)
}
}
}
document.write(\'<input id="c"><button onclick=$()>Ok</button>\');
delete _
因为限制了字符串的长度,因此这里要利用重叠来构造长度为 16 且满足所有正则表达式的字符串为 “be0f233ac7be98aa”,输入就可以获得 flag。或者是把下面的代码拿到浏览器运行,也能跑出需要的字符串。
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it\'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o)
{
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}