首页 > 技术文章 > ctf-web:字符串和正则匹配

pursue-security 2021-10-06 21:03 原文

匹配过滤

在有的 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[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)
}

 转载自:https://www.cnblogs.com/linfangnan/p/13588411.html

推荐阅读