New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mongodb正则表达式搜索bug #16

Open
ma6174 opened this Issue Jun 13, 2015 · 1 comment

Comments

2 participants
@ma6174
Owner

ma6174 commented Jun 13, 2015

当正则表达式搜索条件中包含\x00(或者\u0000,\0,null)这个特殊字符时,可能得到非预期的结果,我们可以一步一步来验证:

1. \x00是一个合法的Value

mongodb对文档的定义可以参考http://docs.mongodb.org/manual/core/document/#field-value-limit,这里面提到,key不能包含null,但是value是可以的,实际测试下来也是这样的:

$ mongo
MongoDB shell version: 3.0.3
connecting to: test
> db.test.save({"a\x00":"a0"}) // key contains \x00 is invalid
2015-06-13T10:46:41.223+0800 E QUERY    Error: JavaScript property (name) contains a null char which is not allowed in BSON. {}
    at addToOperationsList (src/mongo/shell/bulk_api.js:602:29)
    at insert (src/mongo/shell/bulk_api.js:649:14)
    at DBCollection.insert (src/mongo/shell/collection.js:243:18)
    at DBCollection.save (src/mongo/shell/collection.js:493:21)
    at (shell):1:9 at src/mongo/shell/bulk_api.js:602
> db.test.save({"_id":"a\x00"}) // value contains \x00 is ok
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "a\u0000" })
> db.test.find()
{ "_id" : "a\u0000" }
>

2. 为了方便对比测试再添加几个文档

> db.test.save({"_id":"a\x00b"})
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "a\u0000b" })
> db.test.save({"_id":"ab"})
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "ab" })
> db.test.save({"_id":"a"})
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "a" })
> db.test.find()
{ "_id" : "a\u0000" }
{ "_id" : "a\u0000b" }
{ "_id" : "ab" }
{ "_id" : "a" }
>

3. 使用简单查询可以正确取到文档

> db.test.find({_id:"a\x00"})
{ "_id" : "a\u0000" }
> db.test.find({_id:"a\x00b"})
{ "_id" : "a\u0000b" }
> db.test.find({_id:"ab"})
{ "_id" : "ab" }
> db.test.find({_id:"a"})
{ "_id" : "a" }

4. 使用正则表达式搜索会返回非预期文档

  • 搜索以a\x00为前缀的文档,{ "_id" : "a" }不应该出现
> db.test.find({_id: {"$regex" : "^a\x00"}})
{ "_id" : "a" }
{ "_id" : "a\u0000" }
{ "_id" : "a\u0000b" }
{ "_id" : "ab" }
>
  • 搜索以\x00开头,期望不返回任何文档,结果是返回所有文档
> db.test.find({_id: {"$regex" : "^\x00"}})
{ "_id" : "a" }
{ "_id" : "a\u0000" }
{ "_id" : "a\u0000b" }
{ "_id" : "ab" }
>
  • 搜索以\x00RandomString开头,期望不返回任何文档,结果是返回所有文档
> db.test.find({_id: {"$regex" : "^\x00RandomString"}})
{ "_id" : "a" }
{ "_id" : "a\u0000" }
{ "_id" : "a\u0000b" }
{ "_id" : "ab" }
>
  • 搜索\x00RandomString,期望不返回任何文档,结果是返回所有文档
> db.test.find({_id: {"$regex" : "\x00RandomString"}})
{ "_id" : "a" }
{ "_id" : "a\u0000" }
{ "_id" : "a\u0000b" }
{ "_id" : "ab" }
>

5. 正则表达式是可以正确匹配\x00

mongodb使用的正则表达式是Perl compatible regular expressions (i.e. “PCRE” ) version 8.36 with UTF-8 support(http://docs.mongodb.org/manual/reference/operator/query/regex/)

  • 使用pcretest测试

下载地址:http://sourceforge.net/projects/pcre/?source=typ_redirect

编译参数:./configure --enable-utf8 --enable-unicode-properties

pcre-8.36$ ./pcretest -C
PCRE version 8.36 2014-09-26
Compiled with
  8-bit support
  UTF-8 support
  Unicode properties support
  No just-in-time compiler support
  Newline sequence is LF
  \R matches all Unicode newlines
  Internal link size = 2
  POSIX malloc threshold = 10
  Parentheses nest limit = 250
  Default match limit = 10000000
  Default recursion depth limit = 10000000
  Match recursion uses stack

pcre-8.36$ ./pcretest -d
PCRE version 8.36 2014-09-26

  re> "^\x61\x00"
------------------------------------------------------------------
  0   8 Bra
  3     ^
  4     a\x00
  8   8 Ket
 11     End
------------------------------------------------------------------
Capturing subpattern count = 0
Options: anchored
No first char
No need char
data> a
No match
data> a\x00
 0: a\x00
data> a\x00b
 0: a\x00
data> ab
No match
data>
  • 使用mongodb shell测试
$ mongo
MongoDB shell version: 3.0.3
connecting to: test
> /^a\x00/.test("a")
false
> /^a\x00/.test("a\x00")
true
> /^a\x00/.test("a\x00b")
true
> /^a\x00/.test("ab")
false
>
  • 使用nodejs进行测试:
$ node
> var reg = new RegExp("^a\x00")
undefined
> reg.test("a")
false
> reg.test("a\x00")
true
> reg.test("a\x00b")
true
> reg.test("ab")
false
>

6. 确认mongodb shell正确将请求发送给mongodb

抓取这个请求的包

> db.test.find({_id: {"$regex" : "^a\x00b"}})
{ "_id" : "a" }
{ "_id" : "a\u0000" }
{ "_id" : "a\u0000b" }
{ "_id" : "ab" }
>

结果:

$ sudo tcpdump -i lo0 -X dst port 27017
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo0, link-type NULL (BSD loopback), capture size 65535 bytes
11:27:01.302450 IP localhost.64032 > localhost.27017: Flags [P.], seq 2309521406:2309521476, ack 2695537044, win 11950, options [nop,nop,TS val 1556359160 ecr 1556325546], length 70
    0x0000:  4500 007a 6867 4000 4006 0000 7f00 0001  E..zhg@.@.......
    0x0010:  7f00 0001 fa20 6989 89a8 7ffe a0aa a194  ......i.........
    0x0020:  8018 2eae fe6e 0000 0101 080a 5cc4 27f8  .....n......\.'.
    0x0030:  5cc3 a4aa 4600 0000 5600 0000 0000 0000  \...F...V.......
    0x0040:  d407 0000 0000 0000 7465 7374 2e74 6573  ........test.tes
    0x0050:  7400 0000 0000 0000 0000 2000 0000 035f  t.............._
    0x0060:  6964 0016 0000 0002 2472 6567 6578 0005  id......$regex..
    0x0070:  0000 005e 6100 6200 0000                 ...^a.b...
11:27:01.303661 IP localhost.64032 > localhost.27017: Flags [.], ack 105, win 11946, options [nop,nop,TS val 1556359161 ecr 1556359161], length 0
    0x0000:  4500 0034 9232 4000 4006 0000 7f00 0001  E..4.2@.@.......
    0x0010:  7f00 0001 fa20 6989 89a8 8044 a0aa a1fc  ......i....D....
    0x0020:  8010 2eaa fe28 0000 0101 080a 5cc4 27f9  .....(......\.'.
    0x0030:  5cc4 27f9                                \.'.
11:27:01.304142 IP localhost.64032 > localhost.27017: Flags [P.], seq 70:149, ack 105, win 11946, options [nop,nop,TS val 1556359161 ecr 1556359161], length 79
    0x0000:  4500 0083 f7af 4000 4006 0000 7f00 0001  E.....@.@.......
    0x0010:  7f00 0001 fa20 6989 89a8 8044 a0aa a1fc  ......i....D....
    0x0020:  8018 2eaa fe77 0000 0101 080a 5cc4 27f9  .....w......\.'.
    0x0030:  5cc4 27f9 4f00 0000 5700 0000 0000 0000  \.'.O...W.......
    0x0040:  d407 0000 0000 0000 7465 7374 2e24 636d  ........test.$cm
    0x0050:  6400 0000 0000 ffff ffff 2900 0000 0169  d.........)....i
    0x0060:  734d 6173 7465 7200 0000 0000 0000 f03f  sMaster........?
    0x0070:  0166 6f72 5368 656c 6c00 0000 0000 0000  .forShell.......
    0x0080:  f03f 00                                  .?.
11:27:01.304442 IP localhost.64032 > localhost.27017: Flags [.], ack 299, win 11940, options [nop,nop,TS val 1556359161 ecr 1556359161], length 0
    0x0000:  4500 0034 63bb 4000 4006 0000 7f00 0001  E..4c.@.@.......
    0x0010:  7f00 0001 fa20 6989 89a8 8093 a0aa a2be  ......i.........
    0x0020:  8010 2ea4 fe28 0000 0101 080a 5cc4 27f9  .....(......\.'.
    0x0030:  5cc4 27f9                                \.'.

0x0070行能看到6100 620x61a0x62b,中间的字符是\x00,确认mongodb shell正确将查询请求发送给了mongodb,mongodb处理有问题,返回了非预期的结果。

7. 简单分析

mongodb在正则表达式处理上面有问题,\x00这个字符有可能直接将整个正则表达式截断了,使用不完整的正则表达式进行匹配得到错误的结果。

根据bsonspec对正则表达式的定义:

regexp  ::= "\x0B" e_name cstring cstring
cstring ::= (byte*) "\x00"

正则表达式是由4部分组成:

  • 第一部分\x0B,一个标志位。
  • 第二部分e_name是正则表达式的名字。
  • 第三部分cstring是正则表达式字符串。
  • 第四部分cstring是正则表达式的标志位。

再看cstring的定义,就是一个字节数组,以\x00结束。官方也明确说cstring是不能包含\x00字符的:

Zero or more modified UTF-8 encoded characters followed by '\x00'. The (byte*) MUST NOT contain '\x00', hence it is not full UTF-8.

如果mongodb使用bson来存储正则表达式,看起来是天然有问题的,首先正则表达式是可以使用\x00字符,但是bson的正则表达式不允许使用\x00字符,如果使用了整个正则表达式会被截断。

如果正则表达式部分使用string而不是cstring存储就不会有问题,看一下string的定义:

string  ::= int32 (byte*) "\x00"

string的组成里面,第一部分是一个int32的数,表示整个字符串长度,因为长度明确,那么即使中间出现\x00也不会被截断。

这个问题终归到底看起来是bson的设计问题,正则表达式应该使用string存储而不是cstring存储

mongodb自身也没处理好,如果不允许使用 \x00,应该明确指出,在用户使用的时候需要报一个错,而不是返回错误的结果。

8. 风险

如果用户输入中包含\x00字符,有可能会取得非预期结果,有可能会获取整个collection里面的所有内容,这个还是需要重视的。测试会影响所有版本的mongodb。

9. 如何规避风险

在mongodb修复这个bug之前,可以采取几种措施:

  1. 对用户的输入严格检查,禁止用户使用\x00字符。
  2. 如果必须包含\x00字符,需要对mongodb返回的结果用标准正则表达式再匹配一次。

10. 其他

已经向mongodb官方报bug:https://jira.mongodb.org/browse/SERVER-18824,欢迎围观。

@pynixwang

This comment has been minimized.

pynixwang commented Sep 1, 2015

这都被你发现了。。。。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment