CVE-2020-13630分析文档
原理:
- 在sqlite3的fts3扩展中,fts3EvalNextRow中存在一个Use-After-Free漏洞,与Snippet函数有关。
fts3扩展是sqlite为了支持快速全文查询而加入的一个扩展,其官方文档里面列出了使用常规查询方法和fts查询方法的耗时对比,如下图:
而Snippet函数是用来支持fts3扩展全文查询的一个辅助函数,返回的是用MATCH
关键字匹配的内容的代码片段(ftsSegment),另外还有两个类似的函数offsets,matchinfo
,这三个辅助函数只在使用FTS表的全文索引的SELECT语句中有用。如果在使用“rowid查询”或“线性扫描”策略的SELECT中使用,那么代码段和偏移量都返回一个空字符串,而matchinfo函数返回一个blob值,大小为零字节。所以使用Snippet函数的一个例子可以这样:
1 | create virtual table t0 use fts3(); |
而在漏洞发现者给出的poc中也看到了相似的查询:
1 | DROP TABLE IF EXISTS t0_content; |
POC中16进制匹配的值的ASCII表示如下(将换行符0x0a替换成了空格0x20):
这里虽然不知道是怎么个原理。但漏洞触发的调用过程可以分析如下:
在执行具有snippet函数的fts全文查询的时候,会调用sqlite3Fts3SegReaderNew
函数来为将要匹配到的内容分配一个空间来存储,这个对象叫做Fts3SegReader
:
然后sqlite会调用fts3BestSnippet
来寻找最合适匹配的Snippet片段,在fts3BestSnippet
调用了fts3SnippetFindPositions
,然后又调用了sqlite3Fts3EvalPhrasePoslist
来寻找具体的位置,在sqlite3Fts3EvalPhrasePoslist
中就调用了漏洞函数fts3EvalNextRow
:
fts3EvalNextRow中会根据传入参数的Fts3Expr.eType(pNear)来进行不同的处理,在处理eType为FTSQUERY_AND的时候,如果当前pNear的左右子节点都被推迟解析了(通过bDeferred判断)的话,则会判断当前是否已经到了结束的地方(bEof),如果当前节点的左右子节点有一个结束且当前节点为FTSQOERY_NEAR的话,则将其左右子节点的文档列表(doclist)内容置为0,这代表了这里就是结束的地方了,所以应该确保其左右子节点的bEof都置为1了,但是在SQLite的版本(0d69f76f0865f962)之前,没有做此处理,这就引发了后续的UAF漏洞:
查找到匹配的内容后就会调用fts3SegReaderCursorFree
Free掉之前分配的Fts3SegReader
对象。上面的搜寻过程会将近似匹配片段存到一个SnippetIter中,之后需要从这一个候选的片段中找到一个最佳的,会调用fts3SnippetNextCandidate
进行迭代,在该函数里面会调用fts3SnippetAdvance
继续遍历下一个Snippet:
然后在之前可能没有正确设置结束的地方的时候,fts3SnippetAdvance遍历会出现访问之前已经释放的地址的内容的情况,造成UAF漏洞:
复现过程:
编译的SQLite版本:3.31.1
configure命令:
1 | TCC+="-DSQLITE_ENABLE_VFSTRACE -DSQLITE_ENABLE_STAT3 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_BATCH_ATOMIC_WRITE -DHAVE_READLINE -DSQLITE_ENABLE_FTS3_PARENTHESIS -DTHREADSAFE=0 -DSQLITE_ENABLE_DESERIALIZE -DSQLITE_ENABLE_DBSTAT_VTAB" ../SQLite-3bfa9cc9/configure |
修改Makefile加上ASAN参数:-fsanitize=address -fno-omit-frame-pointer
根据上面描述的,在sqlite3.c:176720(Malloc的地方), sqlite3:178133(Free的地方), sqlite3:181223(Use again的地方)处下断点,执行poc进行调试:
1. Malloc
首先会在Malloc处申请多个Fts3SegReader,每个大小为0x180:
一共有9个Fts3SegReader.
2. Free
分配,进行匹配完之后,一开始会Free掉最后分配的Fts3SegReader对象,然后把匹配到的值打印出来,最后再Free掉剩下的Fts3SegReader对象:
3. Use
93ce是之前分配的Fts3SegReader里面的内容,属于0x6120000094c0这个对象里的内容:
查看该对象的内容:
而这一部分属于Fts3SegReader的aDoclist。然后继续执行,发现这个地方的内容在不同的Fts3SegReader对象中的内容都一样:
都是“\001\003\004\003\006\003\003”
。
继续执行,返回匹配到的值:
最后Free剩下的Fts3SegReader:
一直执行,没有发现ASAN检测出的错误。
确认的问题:
1. Chrome是否有独立的sqlite3文件?
单从安装在Windows上的Chrome版本来说,其安装目录下并没有独立的sqlite3的独立文件,甚至连和有关sqlite3的相关文件都没有。
2. FTS3扩展自行创建的信息表内容:
这些表叫做“shadow table”,是真实的表,用于存储底层数据。
- t0_content: 会根据创建的虚表的创建而声明,就是虚表的内容。
- t0_segdir, t0_segments: 这两张表是用于存储全文索引的。从概念上讲,这个索引是一个查找表,它将每个词(word)映射到一组docid值,这些docid值对应于% content表中包含一个或多个词的记录。为了检索包含指定术语的所有文档,FTS模块查询该索引以确定包含该术语的记录的docid值集,然后从% content表检索所需的文档。
- t0_stat, t0_docsize: 这两个表是使用FTS4创建虚表的时候才创建的表。
- t0_messageize: 这个表也是没有查到,而且官方文档里面并没有说有这个表。
总结:
整个PoC的调用流程弄清楚了,但是具体的PoC里的那段MATCH的值所代表的的内容是不清楚的,我把那部分加入到PoC中的INSERT语句中才会走到Use(sqlite3.c:178133)的地方。如果是原始的PoC的话,则不会走到这个地方。编译的时候也测试了sqlite3是加入了ASAN编译选项的,但实际执行PoC后确实没有反应。
参考链接:
- https://bugs.chromium.org/p/chromium/issues/detail?id=1080459#c5 漏洞报告者和Chrome开发者的讨论记录
- https://www.sqlite.org/fts3.html#introduction_to_fts3_and_fts4 sqlite的FTS3和FTS4的官方介绍文档
- https://sqlite.org/src/info/0d69f76f0865f962 Patch的记录
- https://sqlite.org/forum/timeline?advm=0&b=2020-10-21+01:58:20&n=50&nsm&ss=v&vfx&y=f SQLiteForm的timeline
注:特意输错命令,sqlite3检测到memory leak错误。