MySQL • 原始碼分析 • mysql認證階段漫遊

姬子玉發表於2017-12-13

client發起一個連線請求, 到拿到server返回的ok包之間, 走三次握手, 交換了[不可告人]的驗證資訊, 這期間mysql如何完成校驗工作? 過程(三次握手) 資訊是如何加密的 client: hash_stage1 = sha1(password) hash_stage2 = sh...

client發起一個連線請求, 到拿到server返回的ok包之間, 走三次握手, 交換了[不可告人]的驗證資訊, 這期間mysql如何完成校驗工作?

過程(三次握手)

沒加濾鏡的三次握手

資訊是如何加密的

client:

hash_stage1 = sha1(password) hash_stage2 = sha1(hash_stage1) reply = sha1(scramble, hash_stage2) ^ hash_stage1複製程式碼

server: (邏輯位於sql/password.c:check_scramble_sha1中, 下文亦有提及)

// mysql.user表中, 對應user的passwd實際上是hash_stage2 res1 = sha1(scramble, hash_stage2) hash_stage1 = reply ^ 
res1 hash_stage2_reassured = sha1(hash_stage1) 再根據hash_stage2_reassured == hash_stage2(from mysql.user)是否一致來判定是否合法複製程式碼

涉事函式們

如圖, client發起連線請求, server建立新的執行緒, 並進入acl_authenticate(5.7位於sql/auth/sql_authentication.cc, 5.6位於sql/sql_acl.cc)函式完成資訊驗證, 並把包裡讀出的資訊更新到本執行緒.

流程堆疊:

#0 parse_client_handshake_packet  
#1 server_mpvio_read_packet  
#2 native_password_authenticate 
#3 do_auth_once  
#4 acl_authenticate  
#5 check_connection  
#6 login_connection  
#7 thd_prepare_connection 
#8 do_handle_one_connection 複製程式碼

接下來考察這些函式中做了哪些事. check_connection(sql/sql_connect.cc) 當接收到client的建連線請求時, 進入check_connection, 先對連線本身上下文分析(socket, tcp/ip的v4/6 哇之類的) 當然你用very long的host連進來, 也會在這裡被cut掉防止overflow. 不合法的ip/host也會在這裡直接返回, 如果環境ok, 就進入到acl_authenticate的邏輯中 acl_authenticate: 初始化MPVIO_EXT, 用於儲存驗證過程的上下文; 字符集, 挑戰碼, …的坑位, 上鎖, 根據command進行分派, (新建連結為COM_CONNECT

COM_CONNECT下會進入函式do_auth_once(), 返回值直接決定握手成功與否. 先對authentication plugin做判定, 我們們這裡基於”mysql_native_password”的情況

if (plugin)
 {
 st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info;
 res= auth->authenticate_user(mpvio, &mpvio->auth_info); 
 ...複製程式碼

在mysql_native_password時會進入native_password_authenticate 邏輯:

/* generate the scramble, or reuse the old one */ if (mpvio->scramble[SCRAMBLE_LENGTH])
 create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand);

 /* send it to the client */ if (mpvio->write_packet(mpvio, (uchar*) mpvio->scramble, SCRAMBLE_LENGTH + 1)) 
 DBUG_RETURN(CR_AUTH_HANDSHAKE);

 /* read the reply with the encrypted password */ if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) 
 DBUG_RETURN(CR_AUTH_HANDSHAKE);
 DBUG_PRINT("info", ("reply read : pkt_len=%d", pkt_len));複製程式碼

可見這裡才生成了挑戰碼併傳送到client, 再呼叫mpvio->read_packet等待client回包, 進入server_mpvio_read_packet, 這裡的實現則呼叫常見的my_net_read讀包, 當拿到auth包時, 邏輯分派到parse_client_handshake_packet, 對包內容進行parse, 這裡會根據不同client protocol, 去掉頭和尾, 還對client是否設定了ssl做判定. 接著:

 if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) 
 {
 /* 
 Get the password field.
 */
 passwd= get_length_encoded_string(&end, &bytes_remaining_in_packet,
 &passwd_len);
 }
 else 
 {
 /* 
 Old passwords are zero terminatedtrings.
 */
 passwd= get_string(&end, &bytes_remaining_in_packet, &passwd_len);
 }
 ...複製程式碼

在拿到了client發來的加密串(雖然叫passwd), 暫時存放在記憶體中, 返回native_password_authenticate, 當判定為需要做password check時(萬一有人不設定密碼呢), 進入check_scramble, 這個函式中才實現了對密碼的驗證:

// server decode回包中的加密資訊 // 把上面提到的三個公式包在函式中 my_bool
check_scramble_sha1(const uchar *scramble_arg, const char *message,
 const uint8 *hash_stage2) {
 uint8 buf[SHA1_HASH_SIZE];
 uint8 hash_stage2_reassured[SHA1_HASH_SIZE];

 /* create key to encrypt scramble */
 compute_sha1_hash_multi(buf, message, SCRAMBLE_LENGTH,
 (const char *) hash_stage2, SHA1_HASH_SIZE);
 /* encrypt scramble */
 my_crypt((char *) buf, buf, scramble_arg, SCRAMBLE_LENGTH);

 /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
 compute_sha1_hash(hash_stage2_reassured, (const char *) buf, SHA1_HASH_SIZE);

 return MY_TEST(memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE));
}複製程式碼

native_password_authenticate拿到check_scamble的返回值, 返回OK, 再返回到acl_authenticate, 講mpvio中環境資訊更新到執行緒資訊THD中, successful login~

(所以可以魔改這塊程式碼搞事, 密碼什麼的, 許可權什麼的…. (我就說說, 別當真

原文連結


相關文章