编译:代码卫士
2021年4月,ZDI 收到了一名匿名者提供的关于MySQL 数据库中某漏洞的报告,结果是位于 InnoDB memcached 插件中的基于堆的缓冲区溢出漏洞。该漏洞影响MySQL 版本8.0.25及之前版本,可在未认证情况下远程触发。攻击者可利用该漏洞在 MySQL 数据库服务器上执行任意代码。Oracle 公司在7月将其修复并分配编号 CVE-2021-2429。
漏洞简介
如下分析基于 MySQL Community Server 版本8.0.25,位于 memcached GET 命令中。该命令用于从表格中检索数据。在性能方面,GET 命令支持在单个 memcached 查询中提取多键值对。如下:
mysql> SELECT * FROM test.demo_test;
+----+--------------+------+------+------+
| c1 | c2 | c3 | c4 | c5 |
+----+--------------+------+------+------+
| AA | HELLO, HELLO | 8 | 0 | 0 |
+----+--------------+------+------+------+
1 row in set (0.01 sec)
telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get AA AA // query two key 'AA' within one query
VALUE AA 8 12
HELLO, HELLO
VALUE AA 8 12
HELLO, HELLO
END
GET 命令中指定的密钥被 process_get_command() 令牌化,之后在 innodb_get() 中逐个处理。
static ENGINE_ERROR_CODE innodb_get(
/*=======*/
ENGINE_HANDLE *handle, /*!< in: Engine Handle */
const void *cookie, /*!< in: connection cookie */
item **item, /*!< out: item to fill */
const void *key, /*!< in: search key */
const int nkey, /*!< in: key length */
uint16_t next_get) /*!< in: has more item to get */
{
...
if (report_table_switch) { // (1)
char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN];
char *name;
char *dbname;
conn_data =
(innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
cookie);
assert(nkey > 0);
name = conn_data->conn_meta->col_info[CONTAINER_TABLE].col_name;
dbname = conn_data->conn_meta->col_info[CONTAINER_DB].col_name;
#ifdef __WIN__
sprintf(table_name, "%s\%s", dbname, name);
#else
snprintf(table_name, sizeof(table_name), "%s/%s", dbname, name);
#endif // (5)
...
memset(result, 0, sizeof(*result));
assert(conn_data->row_buf_used + strlen(table_name) < REC_BUF_SLOT_SIZE); // (2)
memcpy((char *)(conn_data->row_buf[conn_data->row_buf_slot]) +
conn_data->row_buf_used,
table_name, strlen(table_name)); // (3)
...
result->col_value[MCI_COL_VALUE].value_len = strlen(table_name);
conn_data->row_buf_used += result->col_value[MCI_COL_VALUE].value_len; // (4)
...
}
如GET 命令中的密钥形式为 @@containers.name,则变量 report_table_switch 将被设为 true,满足 (1)处的分支。(3)处的 memcpy 将 table_name 复制到 row_buf 缓冲区。在之心相关 memcpy 之前,(2)处的代码验证 row_buf 中仍然具有足够的空间。然而,这种验证近通过 assert() 执行。由于 assert 是build中而不是在发布build中生成代码的宏,因此它可导致缓冲区溢出漏洞,在运行发布 build 时可触发。
触发条件
InnoDB memcached 插件并非默认启用,必须通过 DWITH_INNODB_MEMCACHED=ON 从源头构建 MySQL。在默认情况下,memcached 守护进程监听 TCP 和 UDP 端口11211。该 pyload 是一个单一的 GET 命令,如下所示。
get @@aaa @@aaa @@aaa ...
@@aaa 是位于 innodb_memcache 数据库中的默认列之一。
mysql> SELECT * FROM innodb_memcache.containers;
+------+-----------+-----------+-------------+---------------+-------+------------+--------------------+------------------------+
| name | db_schema | db_table | key_columns | value_columns | flags | cas_column | expire_time_column | unique_idx_name_on_key |
+------+-----------+-----------+-------------+---------------+-------+------------+--------------------+------------------------+
| aaa | test | demo_test | c1 | c2 | c3 | c4 | c5 | PRIMARY |
+------+-----------+-----------+-------------+---------------+-------+------------+--------------------+------------------------+
1 row in set (0.00 sec)
每个 @@aaa被在innodb_get()函数内的(5)处的表格名 test/demo_test替换,结果溢出内容的形式是 test/demo_testtest/demo_testtest/demo_test....。溢出的长度由攻击者控制。发送 payload 后,mysqld 进程中触发该堆缓冲区溢出漏洞。调用栈如下所示。
#0 innodb_get
#1 process_get_command
#2 process_command
#3 try_read_command
#4 conn_parse_cmd
#5 event_handler
#6 event_persist_closure
#7 event_process_active_single_queue
#8 event_process_active
#9 event_base_loop
#10 worker_libevent
#11 start_thread
#12 clone
补丁
该漏洞已在版本8.0.26中修复,非常直接。它在复制前先检查长度。
+ if (conn_data->row_buf_used + strlen(table_name) >= REC_BUF_SLOT_SIZE) {
+ conn_data->row_buf_slot++;
+
+ /* Limit the record buffer size to 16 MB */
+ if (conn_data->row_buf_slot >= 1024) {
+ err_ret = ENGINE_KEY_ENOENT;
+ goto func_exit;
+ }
+
+ if (conn_data->row_buf[conn_data->row_buf_slot] == nullptr) {
+ conn_data->row_buf[conn_data->row_buf_slot] = malloc(REC_BUF_SLOT_SIZE);
+ }
+
+ conn_data->row_buf_used = 0;
+ }
memset(result, 0, sizeof(*result));
- assert(conn_data->row_buf_used + strlen(table_name) < REC_BUF_SLOT_SIZE);
memcpy((char *)(conn_data->row_buf[conn_data->row_buf_slot]) +
结论
尽管 InnoDB memcached 插件并非默认启用,但尽快应用补丁无疑是明智之举。不久后出现可靠的完整 exploit 应该并不令人惊讶。
【开奖啦!!!!!】
限时赠书|《软件供应链安全—源代码缺陷实例剖析》新书上市
上次的限时赠书活动中奖名单已出炉,恭喜以下同学中奖,请微信后台私信地址,本周我们将陆续发送书籍。
@whyseu @。@HFwuhome@惊蛰 @nimo @XuZ @淡然 @Marco韬 @王孟 @Wecat@nwnλ @MOBE @湘北二两西香葱@※ @搬砖小土妞@云烟过眼 @r00t@小风 @傲雪@最好走的路是套路 @Zhao.xiaojun @浅笑淡然 @X-Star @Erick2013 @小秦同学 @X @王骏 @欢寻 @nbp@Mr. Guo
大家可移步京东电子工业出版社一睹为快!
https://item.jd.com/12927539.html 或直接点击“原文链接”购买。
如下是本书相关讲解:
推荐阅读
PHP源代码后门事件后续:用户数据库遭泄露或是元凶
RigUp 数据库暴露7.6万份美国能源行业文件
【漏洞预警】Squid缓冲区溢出及拒绝服务漏洞安全预警通告
谷歌漏洞披露规则增加30天补丁缓冲期;Reddit 公开漏洞奖励计划
原文链接
https://www.zerodayinitiative.com/blog/2021/9/2/cve-2021-2429-a-heap-based-buffer-overflow-bug-in-the-mysql-innodb-memcached-plugin
题图:Pixabay License
本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的产品线。