查看: 16022|回复: 7

正则表达式的学习与实例运用

[复制链接]
发表于 2016-6-4 23:31:07 | 显示全部楼层 |阅读模式
      以后少写渗透实战类的帖子了,多点安全分析、知识介绍类的。

      首先,要真正了解并应用正则表达式,需要你具备一点脚本编程、脚本分析的能力。如果你没有一点脚本基础并且也志不在此的话,那么这篇文章就可以略过了。本文仅同对脚本编程、脚本渗透有兴趣的人分享。本文内容包括基础介绍和实例分析两部分:前面的基础介绍有参考网上的相关资料,有自己的分析总结,后面将会结合实际运用举出三个实例,相信可以让不熟悉正则表达式的人能基本了解这个东东。

      在开始正文之前建议大家边看边准备一个正则测试工具,也可以使用在线正则测试,网上搜一下很多,这玩意儿和编程一样需要多动手,不同的是它简单多了,正常人都能懂。
      正则表达式的重要性,不懂的人可能觉得就是个匹配规则、无关紧要。但就我本人来看非常重要,它的作用是非常大的,实际编程中,正则表达式的使用频率非常高,正则是一个程序员必备的技能。在渗透测试领域,waf的过滤、exp的编写、CMS的过滤等,一般都是使用正则来匹配过滤的。

      用一句比较抽象的话来说:正则表达式就是一种描述匹配规则的工具。很多人应该都用过windows操作系统的搜索功能,对windows搜索里的通配符(*)、(.)应该不陌生,比如(*)匹配任意数量字符,比如你要搜索磁盘中全部的php文件,那么搜索(*.php)就OK了,这里的通配符本质上和正则表达式是一样的,也是描述一种匹配规则,只不过后者要更加丰富、更加复杂,当然匹配精准度也更高。

      首先来一个简单的例子,让大家对正则表达式有个最基本的认识。
      待匹配内容:I come from ihonker!
      匹配目标:ihonker
      这里要匹配出ihonker这个单词,那么最简单的就是直接把ihonker这个单词作为正则表达式即可了,看实际测试:

    01.jpg

      上面是正则表达式最简单的应用,聪明的你还能发散一下:如果待匹配目标是from的话,那么正则表达式换成from即可,同理可以推测其他单词比如come等等。但是在实际运用中待匹配的内容样式百出,匹配目标也是千奇百怪,仅仅使用单词来匹配就远远不够了。

      我把上面的内容稍微调整一下:
      待匹配内容:
      I come from ihonker! Our website is www.ihonker.org.
      If we remove point of the URL,it becomes as wwwihonkerorg.
待匹配目标:ihonker(单独的单词)
    我们看看实际的匹配结果,可以看到匹配出了3个结果,它把前后两个url中的ihonker也匹配出来了。但我们目的是仅仅匹配出单个存在ihonker单词,也就是第一个ihonker,所以这个时候就需要深入的了解正则表达式的语法了。(当然我这里可以提前告诉你,正确的匹配规则是:\sihonker\b。它是怎么得出来的?请搬个小凳子慢慢看)
       02.jpg
当然,上面这段字符也还是非常简单的,我们再来一个更加复杂点的例子。待匹配文本是下面这段代码,总共创建了4个函数,现在需要从这4个函数中匹配出所有参数都为可控参数的函数,把符合规则的函数所有内容都匹配出来,也就是说需要匹配出第2个和第4个函数的全部内容。
    这个就需要动点脑筋了,我也花了点时间才搞出来(水平同样很次),当然这里也提前把结果给出来:\bfunction\b[^{]*?\(\$([^\),]*?\)|[^\)]*?\){2})[^}]*?}
    就是上面这个表达式,不懂的阶级兄弟千万不要被上面的各种符号唬倒了,其实只要你看了下面的内容,相信会很容易理解,或许还能整出更高效的正则式(正则表达式本身比较灵活,理论上匹配同样的内容可以写出无限种匹配规则)。实际编程中对正则表达式应用难度只在其上不在其下,所以要学的东西还是很多的~
[PHP] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
    function GetVar(a=2,b=3)
    {
        global $_vars;
        return isset($_vars[$k]) ? $_vars[$k] : '';
    }
    function Control                      //测试函数
    ($a)
                                          //This is a test!
    {
        global $dsql;
        $this->tpl = isset($this->tpl)? $this->tpl : new DedeTemplate();
        $sqltype = "DedeSql";
        if ($GLOBALS['cfg_mysql_type'] == 'mysqli' && function_exists("mysqli_init")) $sqltype = "DedeSql";
        else $sqltype = "DedeSqli";
        $this->dsql = isset($dsql)? $dsql : new $sqltype(FALSE);
    }
    function SetTemplate               //测试函数
     
    ($tplfile,                        //测试函数
     
    b=3)
    {
        $tplfile = DEDEAPPTPL.'/'.$this->style.'/'.$tplfile;
        $this->tpl->LoadTemplate($tplfile);
    }
    function                         //测试函数
                                     //This is a test!
    mytest($tplfile,contest($_GET["id"]))
    {
        $tplfile = DEDEAPPTPL.'/'.$this->style.'/'.$tplfile;
        $this->tpl->LoadTemplate($tplfile);
    }
?>


元字符

学习正则表达式首先要理解元字符,元字符是正则表达式的一个很重要的内容。我本人对它的理解:元字符是用来匹配某一特定类型的字串,它的作用是缩小正则表达式的体积,减轻我们的工作量。在没有元字符的情况下,如果你要匹配一个文本中所有的汉字,那么你可能需要准备一张汉字码表,然后把所有的汉字都放到正则表达式中去,我想这样的规则一定会让所有人都叹为观止!
    常用的元字符都在下面这张表列出来了,还有更多的就不一一列出,有人可能一看到这么多的符号就头晕了。我要告诉你的是我和你一样,我刚开始的时候也是很头疼记不住,但多次使用练习后,就慢慢记住了。所以,别担心,慢慢来。
       03.jpg
下面挑几个典型的元字符做示例分析
    元字符:\b
\b匹配一个单词的边界,换句话说它不匹配任何字符,它只匹配一个位置。在编程中,一个单词通常由空格、换行符以及其他特殊符号(比如”(”、”)”等)分隔开来,\b正是对应这些分割符号,但它又不会把这些符号匹配出来。如果不好理解的话,下面用实例来说明。
有一行代码如下:function test($str,$_GET[“id”],utest($act),$testus=2),我们要匹配出它的函数名也就是test。先观察这个字串,里面包括3个test,其中后两个test分别包含在utest($act)和$testus=2两个字串中,要剔除这两个字串,我们的匹配规则就需要区隔掉字符’u’、’(‘和’$’。其中u是正常的小写字母,而’(‘和’$’则是特殊符号。\b不会对应’u’,但是会对应’(‘和’$’两个特殊符号!@#¥看到这里,你会不会想既然\b会对应’(‘和’$’,哪该怎么剔除呢?
仔细分析一下:第一个test前后都是符号(前面是空格,后面是’(‘),后两个test只有一边是符号,那么这样的话匹配规则就呼之欲出了:\btest\b。这个正则的意思就是匹配单词test,test前后都为符号时即满足此规则,若前后有有任一个不为符号即不满足。你可以试一下\btest或者test\b这两种匹配结果。
       04.jpg
如果把代码改为如下:function test($str,$_GET[“id”],utest($act),$test=2)。那么前面的规则就不太灵光了,它也会把第三个test匹配出来,这时候就需要另外的匹配规则了,可以自己思考一下。
顺便说一下,看了\b的作用,再结合上面的那张表,我想你可以试着理解前面的第2个例子了。
元字符:\w
\w匹配任意数字、大小写字母、下划线(\w匹配汉字是.net环境下的正则表达式引擎提供的,本文不涉及)
字符串:我a是C#7_おは0مرحبا。这段字符包括了汉字、大小写字母、数字、下划线、日语、希伯来语和#(特殊符号),我们看看它的匹配结果。这个比较容易理解就不多解释了。可以和\b对比一下,你会发现\w有自己的匹配内容,但\b并没有。
       05.jpg
元字符之数量匹配符:*+?{n}{n,m}
数量匹配符本身并没有匹配内容,它跟在匹配值的后面,使该匹配值重复匹配指定次数。(*)重复匹配0到任意次,(+)重复匹配1到任意次,(?)重复匹配0或1次,{n}重复匹配n次,{n,m}重复匹配n到m次。
注:如果匹配值后面没有跟数量匹配符的话,那么它只能匹配一次。
列出几个简单示例
\d+:匹配任意数字1至任意次
\w*:匹配任意大小写字母、数字、下划线0至任意次
\s{5,8}:匹配任意空白符5到8次
A?:A字母重复匹配0次或1次
元字符:[^n]
     [^n]匹配除了n以外的任意一个字符,[^a-z]匹配任意一个非小写字母(其他可联想A-Z0-9)
     [^n]里的^和单独的^使用情况要区分开来,单独的^对应输入字符串的开头,而方括号中的^则是反义的意思。
元字符:|
     |的作用是将该符号前后的两个规则都适用。
     \d{2,5}|[a-z]{5,8}:匹配任意2到5位的数字,或者匹配任意5到8位的小写字串。
   待匹配内容:<div>test1</div>bb<div>test2</div>
   匹配目标:所有的尖括号及其里面的内容
   匹配规则:\W\w+\W|\W{2}\w+\W(当然也可以精简为\W{1,2}\w+\W,还有其他无数种方法这里就不一一列举了)
元字符:()
    (\d{5}):匹配并获取任意一个5位数字。
   待匹配内容:<div>test1</div>bb<div>test2</div>
   匹配目标:所有的尖括号里面的内容
   匹配规则:<(\W?\w+)>
     ()在实际编程中经常用到,用上面的例子来运行一下:
   从运行结果还可以看出来,()内的匹配规则是重复匹配的,直到把所有符合规则的内容匹配出来,并返回一个数组。并列的规则,通常也用()括起来。
      06.jpg
元字符:/i/g/e/m
     /i:忽略大小写
    /g:
    /e:
    /m:
贪婪模式与非贪婪模式
数量匹配符后面跟?的匹配模式就是非贪婪模式,不跟?的模式是贪婪模式。贪婪模式在同等情况下匹配尽可能多的结果,而非贪婪模式则匹配尽可能少的结果。
待匹配内容:asdabaecdbadefdlsdbsdlfbdalfdbdfadfdb
匹配规则:分别用a\w*b和a\w*?b两个正则匹配
结果1:asdabaecdbadefdlsdbsdlfbdalfdbdfadfdb
结果2:
asdab
aecdb
adefdlsdb
alfdb
adfdb
在爬虫程序中,(.*?)这个正则出现的概率非常高,就是运用了非贪婪模式。
好了,正则表达式的常见的基础应用差不多就是这些了,我写的比较简单,真正要记住还是得以点带面。
到了这里,我想你也可以试着去读懂前面的第三个例子了。可能还是有点不好理解,不过没关系,看了下面的实例分析相信你就可以了。

实例分析
实例运用一:利用正则表达式编写自己的expdedecms路径泄漏漏洞
在开始之前,还是要啰嗦一下:正则表达式是一个工具,它是用来辅助我们其他工作的。比如在本例的exp编写,需要的基础是python脚本编程和dedecms路径泄漏漏洞原理研究,然后再辅之以正则匹配,那么一个exp就可以成型了。
首先简单介绍一下dedecms的路径泄漏:在url地址后面加上member/inc/config_pay_alipay.php可以爆出网站物理路径(当然如果网站配置得当就爆不出来了),那么我们exp的目的有二个:一是检测网站是否存在路径泄漏;二是把物理路径匹配出来。
    看看下面的这个例子。
       07.jpg
   它的源码如下:
[HTML] 纯文本查看 复制代码
1
2
3
4
<br />
<b>Warning</b>:  require_once(DEDEMEMBER/paycenter/alipay/alipay_config.php) [<a href='function.require-once'>function.require-once</a>]: failed to open stream: No such file or directory in <b>/home/seo3c5s5e4o93dc/wwwroot/member/inc/config_pay_alipay.php</b> on line <b>11</b><br />
<br />
<b>Fatal error</b>:  require_once() [<a href='function.require'>function.require</a>]: Failed opening required 'DEDEMEMBER/paycenter/alipay/alipay_config.php' (include_path='.:/usr/local/lib/php') in <b>/home/seo3c5s5e4o93dc/wwwroot/member/inc/config_pay_alipay.php</b> on line <b>11</b><br />
现在开始第一步:如何检测网站存在路径泄漏?我们知道采用CMS程序的网站,主体代码都是一致的,具体到这里可以判断两个报错提示:”Warning”和” Fatal error”是一定会出现的。So,我们可以判断页面的返回内容是否包含这两个单词,若包含则可推断存在路径泄漏,否则没有。
正则表达式的任务就是从文本中匹配出这两个单词,再来观察一下这两个单词的特征,发现都被<b></b>一对标签框住,Warning是纯字母,Fatal error包含字母和空格,这样的话我们用一个基准匹配值加上数量匹配符,基准匹配值要同时能匹配字母和空格([\w\s]意为匹配任意一个字母、数字、下划线或空白符),这样也会把<b>11</b>匹配进来,但后面再接数量限制就可以排除了,数量匹配符在7和11之间({7,11})。完整的的正则表达式:<b>([\w\s]{7,11})</b>
里面加了(),是为了后期让程序从中取出匹配的单词内容(回过头去看看()的作用),加以验证是否和预设单词一致,从而判断网站是否存在路径泄漏。这样第一步工作就完成了。
开始第二步:如何获取网站物理路径?从源码可知物理路径为字符串”/home/seo3c5s5e4o93dc/wwwroot/member/inc/config_pay_alipay.php”,它同样包含在一对<b></b>中,字符内容包括反斜杠、字母、数字、下划线和小数点(别的网站也可能会包含其他特殊字符、空白符、中文、日文、火星文等,网站物理路径总是千奇百怪的)。聪明的你可能会想到把基准匹配值规则设定为匹配任意字符,然后后面加上数量匹配符就可以了。但我这里的回答是正确但又不合理的,说正确是因为这个匹配规则的确可以匹配出物理路径,说不合理是因为这个规则也会把前面的warning和fatal error这两个单词也匹配出来,这样就产生混淆了。
这里的匹配规则要能匹配出物理路径,同时区别于前面的报错单词。物理路径的一大特点就是它包含斜杠,我们可以用这个反斜杠进行区隔。本例的物理路径是linux下的,它的首字符固定是一个斜杠,但windows下的物理路径类似D:/phpweb/www/include/test.php,反斜杠前面有字母和特殊符号。这里可以试着先写出一点:[^/]{0,10}?,意思是以非贪婪模式匹配任意非/字符0次到10次,如果路径的/字符前面存在其他字符,那么就匹配n次,否则就不匹配(0次)。然后再加一个/,正则式就变成了[^/]{0,10}?/,意思是匹配/字符一次,因为源码中只有路径会必然包含这个字符,所以只要匹配一次,确定字符中存在/那么就可以判断该字符为路径了。再后面就匹配任意字符,当然须不包括<>,这样可以避免匹配到</b>后面的字符(代码中存在多个</b>,如果不加<>会匹配错误),这样我们的正则式就变成了这样:[^/]{0,10}?/[^<>]{0,150}?,新加上的部分意思是以非贪婪模式匹配任意非<>字符0次到150次,这样这个匹配路径的正则式就完成了。再加上前后的区隔判断,最终完整的正则式为:<b>([^/]{0,10}?/[^<>]{0,150}?)</b>
好了,看完了这个实例分析,我想前面的第三个例子应该会有点头绪了吧。
下面要说点题外话,这里的实例最终要以py脚本的形式完成。而在编程中有数组的概念,具体到这个例子就是正则表达式的()里面的内容,会以数组的形式返回给程序,那么在编写程序的时候可以取巧:我们只用匹配出任意<b></b>之间的内容,然后利用数组索引取出响应的值来进行判断。对应的正则式为:<b>[\s\S]{0,150}?</b>,[\s\S]的意思为匹配任意字符一次。
看看它的匹配结果,返回一个包含6个元素的数组,程序中取出第1或第4个元素,进行判断就OK了。
    共找到 6 处匹配:
    <b>Warning</b>
    <b>/home/seo3c5s5e4o93dc/wwwroot/member/inc/config_pay_alipay.php</b>
    <b>11</b>
    <b>Fatalerror</b>
    <b>/home/seo3c5s5e4o93dc/wwwroot/member/inc/config_pay_alipay.php</b>
    <b>11</b>
    据此,我们最终的exp脚本为
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import re
import requests
 
#判断网站是否存在路径泄漏
def check(WholeUrl):
    try:
        response = requests.get(url=WholeUrl,timeout=15)
        content = response.content
        global result
        result = re.findall(Judge,content)
        if len(result) == 6:                                #判断获取的数组是否包含6个元素,如果不是则判断不存在漏洞
            continue
        else:
            return
        if result[0] == 'Warning' and result[3] == 'Fatal error':     #判断数组的第0个和第3个元素是否对应上warning和fatal error
            return True
        else:
            return False
    except Exception as e:
        return False
 
#获取网站物理路径
def match(WholeUrl):
    Status = check(WholeUrl)
    if Status == True:
        WebPath = result[1]
        PathJudge = re.compile(r'/[\s\S]{0,20}?/')          #判断数组的第1个元素是否是物理路径,这一步也可以不要。不过编程的一大原则是考虑各种容错,所以还是判断一下保险。
        PathResult = re.findall(PathJudge,WebPath)
        if len(PathResult) != 0:
            print '%s 存在物理路径泄漏' % DomainUrl
            print '%s 物理路径为:%s' % (DomainUrl,WebPath)
        else:
            print '%s不存在物理路径泄漏' % DomainUrl
    else:
        print '%s不存在物理路径泄漏' % DomainUrl
            
if __name__=="__main__":
    DomainUrl = raw_input('请输入待检测地址:')
    exp_url = '/member/inc/config_pay_alipay.php'
    Judge = re.compile(r'<b>([\s\S]{0,150}?)</b>',re.DOTALL)   #获取所有的<b></b>之间的字符,返回一个数组
    WholeUrl = DomainUrl + exp_url      #合并url生成http://localhost/member/inc/config_pay_alipay.php
    match(WholeUrl)

  看看执行效果:
   09.jpg
  如果能做到这一步的话,那么你就可以尝试向bugscan等在线漏洞扫描平台提交插件了,当然插件代码要根据平台的统一第三方库和其他规范进行调整,不过那就不是难题了。

实例运用二:打造自己的代码审计规则库(基于seay代码审计软件)

  很多刚接触代码审计的人可能都用过seay代码审计软件,这款软件的一个最基础也是最有用的功能是它的自动审计。自动审计的原理是用正则表达式把web程序的最容易产生漏洞的地方匹配出来,比如上传函数move_uploaded_file()就有可能产生上传漏洞,那么就用正则表达式匹配出该函数,算是进行了初次审计,真正确定漏洞还需要后期的人工验证。
   10.jpg
  该软件的匹配规则位于config/rule.bin文件
    11.jpg
可以看到软件自带了一些常见的简单漏洞,如果你对代码审计有研究,那么可以自己在文件下面添加新的匹配规则,我们这里来添加php的反序列化函数unserilize()来试一下。
Php的反序列化漏洞原理这里就不多解释了,该函数接受1个参数,如果这个参数是变量的话,那么就有可能产生反序列化漏洞(当然还得结合其他条件,不过那就不是正则表达式的任务了)。这里就用正则表达式匹配出带变量的unserilize函数,然后再辅助人工验证。
首先分析一下,我们需要匹配出两个东西:一是unserilize函数,二是它的变量参数。Unserilize函数很好匹配,直接匹配这个单词就可以了。变量参数则需要分成两种情况:一是单一的变量,比如unserilize($str)这类的;二是unserilize(test($str))这类的,也就是函数的执行结果作为变量参数,当然里面的函数也要接受至少一个变量参数。
下面再进一步分析unserilize()函数可能的表现形式:按照Php的语法规范,一个函数的标准写法就是unserilize($str)这样的形式(函数名后紧跟括弧,括弧里面只包括一个变量),但同时还有其他很多形式也可以正常执行,下面把这些形式总结一下。
       12.jpg
最后二种形式虽然也是合规的,但实已达到令人惨绝人寰的无语地步,能写出这种函数的人一定是从火星来的。我们是地球人,还是以地球人的思维习惯来解决问题,只考虑前面3种形式(其实第三种形式虽然不至于远到火星级别,但也达到月球级了)。
首先匹配unseriize这个单词,很好匹配:\bunserilize\b就可以了。后面的变量部分根据前面的分析要分成两种可能,所以需要用并列匹配,也就是(pattern01|pattern02)这种形式。
单一的变量匹配:\(\s{0,10}\$\w{0,30}\s{0,10}\)。简单说明一下,首先匹配左括弧,也就是\(,然后考虑括弧和变量之间可能存在空白符(没办法,有时候程序员手误就多打了一个空格),所以用\s{0,10},匹配10个空白符(绰绰有余了),然后匹配变量,php变量在开头都有一个$符号,所以用\$匹配变量符号,然后匹配变量的单词部分也就是\w{0,30}(应该不会出现超过30个字母的变量单词吧),再然后再一次匹配空白符\s{0,10},最后匹配右括弧\)。
函数变量匹配:\(\s{0,10}\w{0,30}\s{0,10}\(\s{0,10}[^\$]{0,30}?\$[^\)]{0,50}\)\s{0,10}\)。简单说明一下,首先匹配左括弧\(,然后匹配可能存在的空白符\s{0,10},再匹配函数名\w{0,30},再匹配函数名和左括弧之间可能的空白符\s{0,10},接着匹配左括弧\(,再匹配空格\s{0,10},再匹配函数内部的参数,因为里面的这个函数参数可能不止一个,也可能不全是变量,所以先以非贪婪模式匹配变量之前的可能的非$字符[^\$]{0,30}?,接着匹配变量符号\$,然后匹配后面的字符(只要确定不是右括弧就行了)[^\)]{0,50},再匹配里面函数的右括弧\),匹配最后两个右括弧之间的空格\s{0,10},最后匹配最后一个外面函数右括弧\)。
那么完整的正则表达式就是:\bunserilize\b\s{0,10}(\(\s{0,10}\$\w{0,30}\s{0,10}\)|\(\s{0,10}\w{0,30}\s{0,10}\(\s{0,10}[^\$]{0,30}?\$[^\)]{0,50}\)\s{0,10}\))
可以把这个玩意儿添加到匹配规则里面,下一次自动审计的时候就可以挖掘可能的反序列化漏洞了。到这里,再回头去看前面的第三个例子,是不是就感觉没什么难度了。

实例运用三:代码审计之某CMS正则表达式漏洞挖掘
前面说过,正则表达式非常灵活,同样的匹配内容理论上有无限种匹配规则。它的灵活性一方面可以极大方便编程人员写出高效的正则式,另一方面也可以极大的方便漏洞的产生~!@#。
来看看下面这段正则表达式。
[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)|into(?\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|(?:declare|set|select)\\b.*@|union\\b.*(?:select|all)\\b|(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)
初看这个表达式,会让人涌起一种深深的无力感,难到无从下手。但是既然是研究技术,那就得迎难而上。这里说一下面对这种很长的表达式的分析方法:1.先分解,再分析;2.直接用正则测试工具,各种情况测试。这里既然是教程,那么就先拆解一下这个正则吧,其实我也很头疼。
根据正则中常见的分隔符|符号和()符号,我们把上面这段正则拆分成几个部分:
[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|   第一段正则
[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)| 第二段正则
into(?\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|  第三段正则
\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|   第四段正则
benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|  第五段正则
(?:declare|set|select)\\b.*@|    第六段正则
union\\b.*(?:select|all)\\b|     第七段正则
(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|  第八段正则
(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)  第九段正则
这样拆分一下,大概有点思路了,这个正则就是上面9个短正则用|合并的,|作为并列合并符号,只要符合前后其中一个正则,那么就成立了。
为了便于分析,我们先写一个简单的php脚本做正则测试,对于这种超长的正则还是边分析边测试的好。
[PHP] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
    $value="[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)|into(?:(\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|(?:declare|set|select)\\b.*@|union\\b.*(?:select|all)\\b|(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)";
    $str=$_GET["id"];
     
    if (preg_match("/".$value."/is",$str)==1||preg_match("/".$value."/is",urlencode($str))==1)
    {
        print "参数不合法,重新输入!"."<br/>"."注入参数为:".$str;
        exit();
    }
    else
    {
        print "参数合法,成功绕过!"."<br/>"."注入参数为:".$str;
    }
?>
Preg_math()函数利用了/is,其中i表示大小写同时匹配,s模式下.匹配所有的字符,包括换行符,同时对待匹配参数也用了urlencode()函数编码匹配。这段php脚本的作用是,当我们输入的参数触发了正则表达式时,返回参数不合法,否则参数合法。
这段正则存在的问题还是比较多的,看了一下绕过的姿势多的让人乍舌。这里简单的分析几个吧。
先从简单的开始分析,从最后一个开始。这是个防sqlserver数据库注入的正则,天知道怎么会放在Mysql+php环境的正则里面……另外解释一下里面的两个符号(?,之前说过()会把匹配的内容获取并返回,也就是会默认放进指定的缓存中,待后来调用,而?:的作用就是不保存缓存,这样的话就能不被调用了。\\表示匹配\。其他就不用多解释了,这个正则匹配sqlserver的关键词,这里不用多看。
(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)
看第8个正则。整个正则有两个()中间用\\b.*串联(匹配任意符号边界),最后再加一个左括弧(,也就是说同时满足select ascci(、select concat(、update char(,这样的即视为触发正则。
(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(
我们来试试,果然过滤了。
13.jpg
但如果我们绕一下呢?比如用select concat (也就是在cancat和左括弧之间加上空格呢(同样可以被mysql解析)?看看执行结果,这就绕过了!!!!就是这么简单!!!@#¥%……
14.jpg
再看第七个正则:union\\b.*(?:select|all)\\b。这个就是匹配Union select、union all这两个语句,其中每个单词后面都加了边界界定符。这个正则在单词的前后加参数绕过均不能成功,为什么?自己分析一下就知道了,这里就不再细说。但是有一种方法可以绕过,就是往单词中间加参数!对sql注入比较了解的应该都知道了,这里不方便细说,因为这个方法已经可以把这几个正则全部绕过了,有兴趣自己研究吧。
第六个正则有点莫名其妙:(?:declare|set|select)\\b.*@。原意是要过滤select|declare|set这三个关键词,但因为后面加了@的缘故,导致这个正则没有什么用。随便加上任何一个关键词都可以通过。
15.jpg
其他的几个正则显然也存在漏洞,但是这里就不多说了,要写完的话篇幅太大,写了这么多也挺累的,并且这个漏洞目前还没有公开,写的太多容易造成一些不必要的破坏。有兴趣自己去研究绕过吧。实际中要利用这些漏洞,还要结合具体的输入参数处理流程,辅之以人工审计才可,但确定是存在漏洞的,不止一处。
最后我再告诉你,这个正则表达式出自于国内一个使用非常广泛、非常知名的CMS。今天是2016年6月4日,就是现在,它仍然用在其最新版本的程序中。你会不会觉得学习正则表达式非常有必要了?
写这篇文章就是两个目的:一是作为正则表达式的教程供大家参考;二是说明正则表达式非常重要,掌握了它可以干很多事情。


正则表达式的学习与实例运用.pdf

1.49 MB, 下载次数: 21, 下载积分: i币 -1

评分

参与人数 1i币 +5 收起 理由
小圈圈 + 5 支持原创

查看全部评分

回复

举报

发表于 2016-6-5 01:35:16 | 显示全部楼层
可以的,知识面越来越宽了

回复 支持 反对

举报

发表于 2016-6-5 10:44:14 | 显示全部楼层
就喜欢这样的帖子!
回复 支持 反对

举报

发表于 2016-6-5 14:45:51 | 显示全部楼层
终于在论坛看到有点意思的文章了
回复 支持 反对

举报

发表于 2016-6-5 16:14:30 | 显示全部楼层
这文章还是30分钟的正则
回复 支持 反对

举报

发表于 2016-6-6 10:07:20 | 显示全部楼层
就喜欢这种原理文章
回复 支持 反对

举报

发表于 2016-9-24 19:19:38 | 显示全部楼层
感谢分享,学习了
回复 支持 反对

举报

发表于 2016-10-19 13:52:13 | 显示全部楼层
楼主辛苦了
回复 支持 反对

举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

旗下站点

邮箱系统

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2025-6-2 06:31 , Processed in 0.100666 second(s), 35 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.