使用PCRE regexp在PHP中验证SQL时的灾难性回溯

时间:2016-09-08 22:19:50

标签: php regex pcre backtracking

我正在尝试以一般形式验证一些SQL:

UPDATE `mytable` SET `keyname` = 'keyvalue',
       `a` = 'somestring',
       `b` = 123,
       `c` = NULL
       WHERE `keyname` = 'keyvalue'

还有更多领域。值将是字符串,整数或NULL。

我原来的正则表达式是:

(?ix)

^

\s*
UPDATE \s+ `mytable` \s+
SET \s+ `keyname` \s = \s 'keyvalue'
(, \s+
  `[A-Z_]+`  (?# field name)
  \s+ = \s+  (?# equals value)
  (
  -?[0-9]+         (?# an integer, possibly negative)
  |
  '(\\.|''|[^'])*' (?# a string in single quotes)
  |
  NULL             (?# NULL)
  )
)+    (?# one or more such assignments)

\s+ WHERE \s+ `keyname` \s+ = \s+ 'keyvalue'

$

这很有用。根据{{​​3}},它匹配180个步骤。

不幸的是,真正的SQL比这更长,例如:

UPDATE `mytable`
SET `keyname` = 'keyvalue',
`Markup` =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.
''Quisque vel mattis odio, quis iaculis sem.''
Nulla facilisi.
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere
cubilia Curae; Fusce ut dui venenatis, maximus lorem eget, ornare ex.
Aenean tempus pulvinar est, id fringilla enim sagittis id. Mauris finibus
cursus commodo.\r\n\r\n
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere
cubilia Curae; Fusce ut dui venenatis, maximus lorem eget, ornare ex.
Aenean tempus pulvinar est, id fringilla enim sagittis id. Mauris finibus
cursus commodo.\r\n\r\n\r\n
\'Aenean in augue a est vulputate accumsan.\'
Phasellus nulla diam, laoreet a elit non, mattis finibus magna. Phasellus
faucibus iaculis mi sed pulvinar.\r\n
Aliquam non nisl ultricies, aliquam augue vitae, efficitur sapien.
Etiam viverra, magna a laoreet sollicitudin, ipsum erat tincidunt sem, nec
faucibus enim tortor eget massa.
Nunc nisi orci, lacinia vitae dictum et, vestibulum sed metus. ',
`From_Date` = NULL,
`To_Date` = NULL,
`Foo` = '',
`Box_Colour` = NULL,
`Modification_Date` = '2016-09-08 12:30:47',
`Modified_User` = 1,
`Modified_IP` = '192.168.1.1'
WHERE `keyname` = 'keyvalue'

现在需要4301步。事实上,如果你让Lorem Ipsum变大,我们就会超过20000步。

此外,如果我们引入错误(使其不匹配),例如更改:

`Foo` = '',

`Foo` = ''

它现在因灾难性的回溯而崩溃。

我可以通过使内部组(键/值对)成为原子组来摆脱灾难性的回溯(在某种程度上)。也就是说,改变:

SET \s+ `keyname` \s = \s 'keyvalue'
(, \s+

SET \s+ `keyname` \s = \s 'keyvalue'
(?>, \s+

实际数据上的20000多个步骤导致PHP脚本在我的目标Web服务器上运行时崩溃。我需要将步骤降低到一些更现实的价值。当看起来regexp合理明确时,我不太明白为什么会有这么多的回溯。摆弄占有量词或原子组似乎无能为力,或导致“通过”或“失败”的SQL无法正确匹配。

1 个答案:

答案 0 :(得分:1)

编辑:对于字符串子表达式:
使用@NikiC的展开循环版本,
在需要时将\s替换为\s*
并添加一个额外的原子团,
它可以达到合理的步数。

https://regex101.com/r/yV5xI7/3

编辑:您也可以在没有展开循环的情况下尝试此版本 '(?>[^'\\]+|\\.|'')*'同样的区别。 https://regex101.com/r/yV5xI7/5

 (?si)

 ^ 

 \s* 
 UPDATE \s+ `mytable` \s+ 
 SET \s+ `keyname` \s* = \s* 'keyvalue'

 (?# one or more such key = value  )
 (?>
      \s* , \s* 

      (?# field name )
      ` [A-Z_]+ `  

      (?# equals )
      \s* = \s*  

      (?# value )
      (?>
           (?# an integer, possibly negative )
           -? [0-9]+         
        |  
           (?# or, a string )
           '
           [^'\\]* 
           (?:
                (?: \\ . | '' )
                [^'\\]* 
           )*
           '

           # '
           # (?: [^'\\] | '' | \\ . )*
           # ' 

        |  
           (?# or, literal NULL )
           NULL             
      )
 )+

 \s+ WHERE \s+ `keyname` \s* = \s* 'keyvalue'

 $

您可以尝试将量化核心包装在原子组中 此外,对于对点.的任何引用,您可能需要dot-all修饰符。

并且,如果你在其中一个中匹配它,你应该排除它 字符串子表达式中的替换 众所周知,发动机采取匹配的路径 在这种情况下,[^']也匹配转义,它看起来像
逃避不应该自己存在。
并且,实际上可能存在有效的转义+换行序列,因此(?s) 即使用(?: \\ . | '' | [^'\\] )*

将所有内容放在一起,这里是ideone的一个

/
     (?si)
     ^ \s* UPDATE \s+ `mytable` \s+ SET \s+ `keyname` \s = \s 'keyvalue'
     (?>
          , \s+ ` [A-Z_]+ `
          (?# field name )
          \s+ = \s+ 
          (?# equals value )
          (?:
               -? [0-9]+ 
               (?# an integer, possibly negative )
            |  '
               (?: \\ . | '' | [^'\\] )*
               '
               (?# a string in single quotes )
            |  NULL
               (?# NULL )
          )
     )+
     (?# one or more such assignments )
     \s+ WHERE \s+ `keyname` \s+ = \s+ 'keyvalue' $
/x

一个用于php

'/
     (?si)
     ^ \s* UPDATE \s+ `mytable` \s+ SET \s+ `keyname` \s = \s \'keyvalue\'
     (?>
          , \s+ ` [A-Z_]+ `
          (?# field name )
          \s+ = \s+ 
          (?# equals value )
          (?:
               -? [0-9]+ 
               (?# an integer, possibly negative )
            |  \'
               (?: \\\ . | \'\' | [^\'\\\] )*
               \'
               (?# a string in single quotes )
            |  NULL
               (?# NULL )
          )
     )+
     (?# one or more such assignments )
     \s+ WHERE \s+ `keyname` \s+ = \s+ \'keyvalue\' $
/x'