部越權(quán)防護(hù)指南:從權(quán)限最小化到日志審計的落地實踐)
MySQL如何防止內(nèi)部員工越權(quán)查看數(shù)據(jù)_實施嚴(yán)格的日志審計策略1. 先搞清楚“內(nèi)部越權(quán)”到底長什么樣1.1 越權(quán)不是黑客專利多數(shù)事故其實是“順手”干的很多人一想到數(shù)據(jù)安全腦子里冒出來的都是“外部攻擊”“拖庫”“注入”但實際上真正讓人半夜被電話叫醒的往往不是外面的黑客而是自己人。我干過幾年數(shù)據(jù)庫運維也處理過不少內(nèi)部數(shù)據(jù)泄露的事件。舉幾個最常見的場景客服崗位的員工利用系統(tǒng)權(quán)限偷偷查詢明星或大客戶的訂單記錄財務(wù)部同事拿著高權(quán)限賬號把全公司工資表導(dǎo)出后發(fā)到了沒有權(quán)限的同事群里開發(fā)人員為了排查線上問題順手SELECT了整張用戶表還復(fù)制到了本地甚至出現(xiàn)過離職員工在交接期把核心業(yè)務(wù)表數(shù)據(jù)打包帶走的情況。這些事有一個共同點都不是外部攻擊而是內(nèi)部人員“越權(quán)”訪問了本不該看的數(shù)據(jù)。這里的“越權(quán)”不一定是繞過系統(tǒng)更多時候是“權(quán)限過大”導(dǎo)致的超范圍訪問。你給了業(yè)務(wù)人員一個能查訂單的賬號他沒查訂單反而把用戶地址和手機(jī)號全拉走了這就是越權(quán)。1.2 內(nèi)部數(shù)據(jù)泄露的幾個常見場景我把實際工作中遇到的內(nèi)部越權(quán)場景整理了一下基本可以歸為幾類場景類型典型行為后果好奇型越權(quán)員工利用權(quán)限查看明星、熟人、競爭客戶的隱私數(shù)據(jù)隱私泄露企業(yè)被投訴甚至起訴利益型越權(quán)離職前批量導(dǎo)出客戶資料、價格體系、核心報表賣給對手商業(yè)機(jī)密流失直接損失巨大誤操作型越權(quán)開發(fā)或DBA執(zhí)行了沒有WHERE條件的UPDATE、DELETE或全表SELECT數(shù)據(jù)被破壞審計時無法定位責(zé)任人憑證復(fù)用型越權(quán)多個員工共用一個高權(quán)限賬號密碼常年不換出事后無法追責(zé)只能“集體背鍋”你可能覺得這些場景離自己很遠(yuǎn)但說實話只要公司里MySQL賬號有這兩個特征——賬號多人共用、權(quán)限明顯大于崗位需要——那離出事就只差一個“手滑”或者“好奇”。1.3 為什么單靠MySQL自帶的權(quán)限表擋不住人MySQL的權(quán)限體系做得很細(xì)從全局、庫、表、列到存儲過程級別都有權(quán)限控制。但問題是權(quán)限控制解決的是“能不能做”的問題解決不了“做了什么”和“是誰做的”的問題。MySQL原生的權(quán)限表mysql.user、mysql.db、mysql.tables_priv這些只能告訴你這個賬號能查哪些庫、哪些表但不會記錄這個賬號實際查了什么、什么時候查的、查了多少行。更麻煩的是如果公司圖省事給所有人共用一個賬號那就算查到了是哪個賬號執(zhí)行了SELECT也無法定位到具體的人。所以要防內(nèi)部越權(quán)不能只靠“把權(quán)限鎖死”還得讓每一次訪問都“留痕”。留痕這件事就是日志審計的核心。2. 事前防線把賬號權(quán)限做“小”比做“嚴(yán)”更管用2.1 最小權(quán)限原則落地到MySQL的賬號設(shè)計很多人理解的“權(quán)限控制”是一刀切能不給就不給。但生產(chǎn)環(huán)境里業(yè)務(wù)要跑、人要干活完全不給權(quán)限不現(xiàn)實而且會逼著員工用管理員賬號“湊合著查”反而更不安全。我做賬號規(guī)劃時習(xí)慣遵循一個思路讓每個賬號剛好處在“能完成本職工作但多一步越權(quán)都做不了”的位置上。這其實就是最小權(quán)限原則。給賬號授權(quán)時不要順手GRANT ALL要精確到庫、表、甚至字段。舉個例子客服人員只能查訂單表里的訂單號和狀態(tài)不能查客戶的手機(jī)號、地址那就這樣授權(quán)-- 創(chuàng)建客服專用賬號密碼務(wù)必用強(qiáng)密碼并定期更換 CREATE USER cs_query10.0.% IDENTIFIED BY 強(qiáng)密碼; -- 只給訂單表的特定列授予SELECT權(quán)限不授予整表權(quán)限 GRANT SELECT (order_id, order_status, create_time) ON trade_db.t_order TO cs_query10.0.%; -- 不加這個客服賬號連其他庫都看不到 REVOKE ALL PRIVILEGES ON *.* FROM cs_query10.0.%; FLUSH PRIVILEGES;這樣操作后查詢端根本過不了“客戶隱私字段”這一關(guān)連SELECT都執(zhí)行不了。權(quán)限在源頭就斷了這比事后盯著審計日志找問題要高效得多。2.2 一人一賬號避免“公共賬號”背鍋另一個必須堅持的原則是“一人一賬號”。我知道很多小團(tuán)隊的習(xí)慣是建一個root_bi、dev_all之類的公共賬號十幾個開發(fā)共用。這樣確實省事可一旦出現(xiàn)數(shù)據(jù)泄露你根本沒法確認(rèn)當(dāng)時坐在電腦前的是誰只能從業(yè)務(wù)系統(tǒng)登錄日志里慢慢查如果業(yè)務(wù)系統(tǒng)也沒日志那這事就成了懸案。一人一賬號的操作不復(fù)雜就是給每個人單獨建賬號按崗位授權(quán)。真正麻煩的是賬號多了之后的維護(hù)所以配套要做兩件事第一賬號命名規(guī)則要能對應(yīng)到人。比如用“姓名拼音業(yè)務(wù)線”命名zhangsan_bi一看就知道是張三在報表線。第二定期清理離職和調(diào)崗賬號。季度巡檢時跑一條SQL就能把長期不用的賬號撈出來SELECT user, host, db, account_locked, password_expired FROM mysql.user WHERE account_locked N AND password_expired N;再結(jié)合運維側(cè)的登錄記錄和業(yè)務(wù)工單系統(tǒng)一一核對哪些賬號該禁用、哪些賬號該降權(quán)。這個過程很枯燥但我被“離職同事三個月后還能登錄數(shù)據(jù)庫”這種事嚇過好幾次真不能偷懶。2.3 用存儲過程包一層把查詢?nèi)肟谑照鰴?quán)限控制時我遇到的另一個尷尬情況是業(yè)務(wù)人員確實需要查敏感數(shù)據(jù)但又不能給他們直接操作表的權(quán)限。比如客服要查訂單但只能查自己負(fù)責(zé)的那部分訂單不能查全庫。這時候最好的辦法是“存儲過程收口”。把查詢邏輯寫進(jìn)存儲過程只給業(yè)務(wù)人員執(zhí)行存儲過程的權(quán)限不給他們直接SELECT表或UPDATE表的權(quán)限。舉個例子做個帶商戶ID校驗的查詢存儲過程DELIMITER $$ CREATE PROCEDURE trade_db.sp_query_order_by_id( IN p_emp_id VARCHAR(32), IN p_order_id VARCHAR(64) ) BEGIN -- 員工編號用于權(quán)限校驗確保只能查歸屬自己的訂單 SELECT order_id, order_status, order_amount FROM trade_db.t_order WHERE order_id p_order_id AND emp_id p_emp_id; -- 硬性歸屬校驗防止橫向越權(quán) END$$ DELIMITER ;授權(quán)時只給EXECUTE權(quán)限GRANT EXECUTE ON PROCEDURE trade_db.sp_query_order_by_id TO cs_query10.0.%;這樣設(shè)計之后業(yè)務(wù)人員完全沒有直接操作表的權(quán)限只能通過存儲過程查而且在存儲過程里可以內(nèi)置各種限制條件從源頭堵住越權(quán)查詢。這個方案我實際用了很久效果是立竿見影的唯一的缺點是需要把常用查詢都整理成存儲過程前期要花一些開發(fā)時間。3. 中間環(huán)節(jié)開審計你要知道的三條路徑3.1 企業(yè)版審計插件最省事但要花錢權(quán)限做得再小也只能減少越權(quán)面不可能完全杜絕。比如你有權(quán)限查A表但你出于好奇去查相鄰的B表權(quán)限上又不違規(guī)這種事權(quán)限層面是攔不住的。這時候就需要“審計”來兜底。MySQL官方提供的企業(yè)版審計插件MySQL Enterprise Audit是最“正統(tǒng)”的審計方案。它基于MySQL插件架構(gòu)實現(xiàn)可以審計連接、查詢、登錄失敗等信息還能按用戶、按訪問源、按操作類型做過濾審計日志也支持XML和JSON格式方便對接外部日志分析平臺。企業(yè)版插件的好處是性能好、功能全、官方長期維護(hù)但需要購買MySQL企業(yè)版授權(quán)很多中小公司不一定會為此單獨付費。我自己在客戶現(xiàn)場接觸到的大多是社區(qū)版所以下面的內(nèi)容會重點講社區(qū)版能落地、成本低、效果也還不錯的方案。如果你公司正好有企業(yè)版授權(quán)開審計也就是改個參數(shù)的事# my.cnf plugin-load-addaudit_log.so audit-logFORCE_PLUS_PERMANENT audit-log-formatJSON audit-log-policyALL3.2 社區(qū)版怎么開binlog保住變更記錄社區(qū)版MySQL雖然沒有官方審計插件但自帶的binlog二進(jìn)制日志就是一套天然的日志審計工具只是很多人只拿它做主從復(fù)制和數(shù)據(jù)恢復(fù)忽略了它的審計價值。binlog記錄的是所有改變數(shù)據(jù)的操作INSERT、UPDATE、DELETE等只要把binlog_format設(shè)置為ROW模式還能記錄每一行數(shù)據(jù)變更前后的值。這意味著只要誰改了數(shù)據(jù)都能從binlog里找到蛛絲馬跡。開啟binlog要改配置文件這是MySQL 8.0的常見配置# my.cnf server-id 1003306 log-bin /data/mysql/logs/mysql-bin binlog_format ROW # binlog過期時間線上建議7天起步這個按企業(yè)合規(guī)要求來 binlog_expire_logs_seconds 604800 max_binlog_size 512M改完重啟MySQL后可以執(zhí)行下面這條命令確認(rèn)binlog已經(jīng)開啟并且是ROW格式SHOW VARIABLES LIKE log_bin; SHOW VARIABLES LIKE binlog_format;有了binlog之后當(dāng)需要排查某個時間段的UPDATE操作時可以直接用mysqlbinlog工具把日志拉出來mysqlbinlog --base64-outputDECODE-ROWS -v \ --start-datetime2024-06-01 00:00:00 \ --stop-datetime2024-06-01 23:59:59 \ /data/mysql/logs/mysql-bin.000088 | grep -A 20 DELETE FROM trade_db.t_order但要注意binlog本身不記錄“誰執(zhí)行了這條SQL”它記錄的是server-id、線程id、時間戳和執(zhí)行位置的元信息。所以binlog必須配合“一人一賬號”的賬號策略才能把操作追溯到具體的人。3.3 general_log和init-connect低成本補(bǔ)上查詢審計binlog只能看到數(shù)據(jù)變更操作查詢操作SELECT它是不記錄的。而恰恰是SELECT最容易發(fā)生越權(quán)——因為“看一眼”不留痕嘛。想查“誰在什么時候查了什么”就得靠general_log通用查詢?nèi)罩净蛘咦越▽徲嫳?。general_log是MySQL記錄所有客戶端請求的日志包括連接、斷開、每一條SQL不管你有沒有改數(shù)據(jù)它都記。開起來也簡單-- 動態(tài)開啟不用重啟 SET GLOBAL general_log ON; SET GLOBAL general_log_file /data/mysql/logs/general_query.log;看到這里你可能會想那直接把general_log一開不就完事了先別急general_log有個很大的問題它會把所有庫的所有SQL全都記下來生產(chǎn)環(huán)境一旦開著日志文件可能幾個小時就沖到幾十個G很容易把磁盤寫滿導(dǎo)致數(shù)據(jù)庫直接不可用。我見過不止一次因為誤開general_log把生產(chǎn)搞掛的案例所以線上一般不建議長時間全量開啟。那怎么平衡“要審計”和“不能把磁盤打爆”我自己常用的替代方案是init-connect。init-connect是MySQL在每次客戶端建立連接時自動執(zhí)行的一小段初始化SQL。我們可以利用它往一張審計表里記一條“誰、從哪來、以什么賬號、什么時候建立了連接”的記錄。這個方案能覆蓋“哪個賬號在哪個時間點連過數(shù)據(jù)庫”雖然記不了每一條SQL但對于絕大多數(shù)內(nèi)部越權(quán)場景已經(jīng)足夠定位問題了先定位到人再結(jié)合binlog或業(yè)務(wù)系統(tǒng)日志還原具體操作。4. 自建審計表被問到“誰查了什么”時能立刻答上來4.1 審計表結(jié)構(gòu)設(shè)計init-connect這塊我用得比較多每次在培訓(xùn)里講完大家都覺得不錯這里把完整方案寫出來可以直接抄作業(yè)。首先要有一張存放連接審計記錄的表。這張表單獨放在一個沒有業(yè)務(wù)數(shù)據(jù)的庫里比如審計庫audit_db避免被業(yè)務(wù)DROP掉CREATE DATABASE IF NOT EXISTS audit_db DEFAULT CHARSET utf8mb4; USE audit_db; CREATE TABLE IF NOT EXISTS audit_db.access_log ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, connect_time DATETIME NOT NULL COMMENT 連接建立時間, user_host VARCHAR(64) NOT NULL COMMENT 賬號和來源主機(jī), thread_id BIGINT UNSIGNED NOT NULL COMMENT 線程ID, server_ip VARCHAR(32) NOT NULL COMMENT 應(yīng)用服務(wù)器IP, query_content VARCHAR(1024) NULL COMMENT init_connect記錄的最終SQL不一定有 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT登錄行為審計表;這里我再說明一下訪問IP的獲取在MySQL 8.0里可以直接用SUBSTRING_INDEX(USER(), , -1)拿到客戶端IP如果前面有代理層會拿到代理IP這點生產(chǎn)環(huán)境要注意最好在業(yè)務(wù)系統(tǒng)的應(yīng)用層也記錄真實用戶IP兩邊對照。4.2 init-connect結(jié)合審計表的具體配置表建好之后關(guān)鍵的一步是配置init-connect。先在my.cnf里加上下面這行# my.cnf init_connect INSERT INTO audit_db.access_log (connect_time, user_host, thread_id, server_ip) VALUES (NOW(), USER(), CONNECTION_ID(), SUBSTRING_INDEX(USER(), , -1))這里有個坑必須提醒大家init-connect在執(zhí)行時如果當(dāng)前賬號對audit_db.access_log表沒有INSERT權(quán)限那么連接就會失敗相當(dāng)于把人擋在數(shù)據(jù)庫外面。所以要把所有需要審計的賬號都單獨授予這INSERT權(quán)限GRANT INSERT ON audit_db.access_log TO cs_query10.0.%; GRANT INSERT ON audit_db.access_log TO app_read10.0.%; -- 其他賬號同理逐個授權(quán)另外管理員賬號root不會執(zhí)行init-connect這是MySQL的一個安全設(shè)計也算合理畢竟管理員一旦寫壞init-connect至少能留個口子進(jìn)去修復(fù)。配置好后重啟MySQL用任何普通賬號連一次稍等一會查一下審計表SELECT connect_time, user_host, thread_id, server_ip FROM audit_db.access_log ORDER BY id DESC LIMIT 10;能看到每次連接都有記錄之后這個“最低成本的連接審計”就算生效了。這套方案的性能開銷很小因為它只在建立連接時執(zhí)行一次INSERT不會像general_log那樣每條SQL都寫盤。4.3 手動記錄敏感表查詢的存儲過程示例init-connect解決的是“誰來過”但對“具體查了什么”還是空白。對于特別敏感的表比如客戶信息表、訂單表、薪資表我建議再加一層“手動審計”。具體做法是把所有對敏感表的訪問收口到帶審計邏輯的存儲過程里。前面已經(jīng)提過用存儲過程收權(quán)限這里再往前推一步存儲過程里不僅做權(quán)限校驗順帶把“誰查了什么、查了多少行、什么時間查的”都寫進(jìn)審計表。舉個實際的例子給財務(wù)部做一個“按月份查詢工資匯總”的入口DELIMITER $$ CREATE PROCEDURE finance_db.sp_query_salary_summary( IN p_emp_no VARCHAR(32), IN p_query_month VARCHAR(6) ) BEGIN DECLARE v_result_count INT DEFAULT 0; DECLARE v_emp_name VARCHAR(64); -- 從業(yè)務(wù)系統(tǒng)表取當(dāng)前賬號對應(yīng)的員工信息 SELECT real_name INTO v_emp_name FROM hr_db.t_employee WHERE emp_no p_emp_no; -- 查詢工資匯總這里可以按需求寫業(yè)務(wù)邏輯 SELECT dept_name, SUM(salary) AS total_salary FROM finance_db.t_salary WHERE month p_query_month GROUP BY dept_name; -- 記錄審計信息 INSERT INTO audit_db.salary_query_log ( query_time, emp_no, emp_name, query_month ) VALUES ( NOW(), p_emp_no, v_emp_name, p_query_month ); END$$ DELIMITER ;這樣一來每次有人查詢薪資數(shù)據(jù)審計表里就會留下痕跡。配合前面的一人一賬號方案一旦出問題查出來就是具體的人、具體的時間、具體查的哪個月份責(zé)任劃分清清楚楚。這個方法的核心思路是把審計能力嵌進(jìn)業(yè)務(wù)入口里而不是事后翻日志。它的好處是審計信息更精確壞處是需要開發(fā)配合改造不是所有查詢都能收口。所以我的建議是最重要的幾個敏感場景一定要收口改造一般場景就用init-connect加binlog做兜底。5. 日志審計要形成閉環(huán)定期審、定時清、定人盯5.1 審計日志日常巡檢的幾個SQL日志開了表建好了如果只是放著不管那審計等于白做。真正有價值的審計必須形成“記錄—檢查—處置”的閉環(huán)。日常巡檢我一般會做三件事。第一定期檢查登錄審計表看看有沒有異常的連接記錄比如凌晨三點有人連接數(shù)據(jù)庫、某個賬號在短時間內(nèi)頻繁連接等。-- 排查凌晨時段的登錄記錄 SELECT connect_time, user_host, thread_id, server_ip FROM audit_db.access_log WHERE HOUR(connect_time) BETWEEN 0 AND 5 ORDER BY connect_time DESC LIMIT 50;第二通過binlog檢查敏感表的變更情況尤其是非業(yè)務(wù)高峰期有沒有人手動改數(shù)據(jù)。排查DELETE操作比較好用的SQL是直接分析binlog事件但對多數(shù)人來說可能太底層所以我一般先把binlog導(dǎo)出成文件再用grep去匹配目標(biāo)表名和關(guān)鍵操作這樣更直觀。第三定期梳理賬號權(quán)限看有沒有“權(quán)限過大”的賬號一直沒人管。不通用的SQL是查詢每個賬號擁有的全局權(quán)限SELECT user, host, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Alter_priv, Drop_priv, Grant_priv FROM mysql.user WHERE account_locked N ORDER BY user;把有Grant_priv授權(quán)權(quán)限的賬號重點圈出來這些賬號一旦被濫用可以給別人授權(quán)是內(nèi)部越權(quán)里風(fēng)險最高的一類。5.2 日志的保留、輪轉(zhuǎn)和歸檔策略審計日志如果一直寫不清理再大的磁盤也會被撐爆。所以日志管理一定要提前定好策略我一般建議按“3-2-1”原則設(shè)計日志類型保留時間歸檔方式備注binlog7~30天轉(zhuǎn)儲到對象存儲或備份一體機(jī)按合規(guī)要求確定至少覆蓋審計周期general_log1~3天壓縮后歸檔一般不長期開臨時排查用access_log審計表180天定期導(dǎo)出CSV歸檔清理舊數(shù)據(jù)配合等保和內(nèi)部合規(guī)要求存儲過程審計表180天同上敏感業(yè)務(wù)重點留存binlog的自動清理可以在MySQL里配置前面提過的binlog_expire_logs_seconds就是干這個的。MySQL 8.0里也可以在執(zhí)行PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;來手動清理不過生產(chǎn)環(huán)境我更推薦用配置參數(shù)自動過期避免人為操作失誤。access_log這張表我是定期手動或者用計劃任務(wù)清理的比如每個季度導(dǎo)出一次CSV后執(zhí)行-- 刪除90天前的審計記錄注意先導(dǎo)出備份再刪 DELETE FROM audit_db.access_log WHERE connect_time NOW() - INTERVAL 90 DAY;提示DELETE大表會帶來主從延遲和表碎片建議用TRUNCATE按月分表的思路或者直接按月份建分區(qū)表。簡單場景下也可以先RENAME舊表再建一張結(jié)構(gòu)相同的新表這樣既保留了舊數(shù)據(jù)又不會影響寫入。5.3 權(quán)限重審和離職賬號清理日志審計的閉環(huán)里還有一個很容易忽視的環(huán)節(jié)權(quán)限重審。我見過不少公司的MySQL賬號列表里躺著幾十個“歷史遺留賬號”有些賬號的主人早就離職了但賬號還在權(quán)限還在這種賬號就是最大的安全隱患。權(quán)限重審我建議每季度做一次重點看三塊離職和轉(zhuǎn)崗賬號是否已禁用、長期不用的賬號是否已鎖定、高權(quán)限賬號是否還是“必要的人”在用。-- 查看所有賬號信息包括鎖定狀態(tài)和密碼過期策略 SELECT user, host, account_locked, password_expired, password_last_changed FROM mysql.user ORDER BY user;然后拿著這份清單和HR、部門負(fù)責(zé)人核對。發(fā)現(xiàn)離職的人直接鎖定賬號ALTER USER zhangsan_bi10.0.% ACCOUNT LOCK;發(fā)現(xiàn)某個崗位不需要這么高權(quán)限的逐步降權(quán)REVOKE SELECT ON sensitive_db.* FROM zhangsan_bi10.0.%;這一步看著簡單但執(zhí)行起來最大的阻力其實是“業(yè)務(wù)部門說還要用”。我的經(jīng)驗是別硬扛做成流程每次權(quán)限變更都要走審批每季度和業(yè)務(wù)團(tuán)隊一起過一遍清單把“你還需要這些權(quán)限嗎”變成例行話題。一旦習(xí)慣養(yǎng)成了后面就會順暢很多。6. 踩坑實錄關(guān)于審計這件事我吃過哪些虧6.1 general log開著忘關(guān)磁盤被日志打爆這是我早年踩過最狠的坑。當(dāng)時為了排查一個慢查詢問題隨手執(zhí)行了SET GLOBAL general_log ON然后就去忙別的了。結(jié)果第二天早上數(shù)據(jù)庫突然連不上了一看磁盤100%被general_query.log占滿數(shù)據(jù)目錄所在分區(qū)直接寫滿數(shù)據(jù)庫整個不可用。后來我給自己定了幾條規(guī)矩排查問題需要開general_log時一定要先確認(rèn)日志寫入路徑的剩余磁盤空間。開完general_log后設(shè)個提醒半小時內(nèi)必須關(guān)閉并檢查日志大小。線上敏感環(huán)境不要全量開general_log優(yōu)先用init-connect加審計表替代。注意general_log不能存放在數(shù)據(jù)目錄所在磁盤的根分區(qū)最好單獨掛載一塊盤或者指向磁盤空間較大的路徑否則磁盤寫滿后MySQL會直接拒絕寫入影響正常業(yè)務(wù)。6.2 binlog格式選錯審計線索斷了有一次排查數(shù)據(jù)被篡改的問題我興沖沖地打開binlog想找出誰改了某張核心表的數(shù)據(jù)結(jié)果發(fā)現(xiàn)binlog_format是STATEMENT日志里只記錄了一條UPDATE語句的原文沒有舊值和新值。數(shù)據(jù)被改成什么樣子、原來是什么值全都沒法還原。從那以后我所有環(huán)境都統(tǒng)一要求binlog_format必須設(shè)置成ROW。ROW模式雖然日志會大一些恢復(fù)和審計的時候真的方便太多它能清清楚楚告訴你哪一行從什么值變成了什么值審計能力完全不在一個級別。配上mysqlbinlog命令排查數(shù)據(jù)變更的體驗是這樣的mysqlbinlog --base64-outputDECODE-ROWS -v \ --start-datetime2024-06-01 09:00:00 \ --stop-datetime2024-06-01 10:00:00 \ /data/mysql/logs/mysql-bin.000112 | less輸出里能看到SQL執(zhí)行時的thread_id如果當(dāng)時用的是一人一賬號就能結(jié)合thread_id反查access_log鎖定具體是誰。6.3 只審計不追責(zé)審計就白做了還有一類問題是管理層面的。辛辛苦苦把日志審計做起來了結(jié)果某天真的查到一個員工在凌晨批量導(dǎo)出了客戶數(shù)據(jù)公司層面因為“沒有明確的處罰制度”最后只是口頭警告連權(quán)限都沒收回。這大概是做數(shù)據(jù)安全人最無奈的時刻。審計的威懾力不取決于日志記了多全而取決于“查出來之后會怎樣”。我在給公司搭審計體系的時候一定會同步推動一件事讓管理層明確內(nèi)部數(shù)據(jù)泄露的處置辦法。沒有問責(zé)機(jī)制審計日志就是一堆占地兒的文本文件時間長了大家看都不會看。6.4 “開了審計就萬事大吉”的誤區(qū)說句大實話沒有任何一個方案能一勞永逸地解決內(nèi)部越權(quán)問題。日志審計是最后一道防線真正起作用的還是前面那幾步權(quán)限最小化、一人一賬號、存儲過程收口、定期權(quán)限重審。我把這四件事比喻成一道門禁系統(tǒng)權(quán)限最小化是門鎖一人一賬號是門禁卡存儲過程收口是安保人員日志審計是監(jiān)控攝像頭。門鎖再結(jié)實、門禁卡再嚴(yán)格如果攝像頭是壞的出了事照樣抓不到人但反過來如果門鎖形同虛設(shè)攝像頭拍得再清楚也只能是事后補(bǔ)救。所以做MySQL數(shù)據(jù)安全真別指望一項技術(shù)搞定所有問題。每次有人問我“到底該上什么方案”我都會反問一句你的賬號權(quán)限管住了嗎如果連一人一賬號都沒做到我建議先別急著上復(fù)雜的審計系統(tǒng)把最基礎(chǔ)的賬號體系理清楚比什么都管用。7. 聊一些更實操的補(bǔ)充想法7.1 日志審計如何與等保合規(guī)對齊順便說一句國內(nèi)很多行業(yè)都有數(shù)據(jù)安全合規(guī)要求比如電力物聯(lián)網(wǎng)場景里就有關(guān)于數(shù)據(jù)安全分級保護(hù)的標(biāo)準(zhǔn)核心思想都是“分等級保護(hù)、分權(quán)限訪問、全流程審計”。做MySQL日志審計時如果能提前對齊這幾個原則后面過合規(guī)評審會省很多事。具體落到操作上要做到這幾點敏感數(shù)據(jù)分級打標(biāo)知道哪些表是高危的高危表單獨建審計策略訪問必須留痕日志留存時間至少覆蓋一個審計周期定期生成審計報表證明你在持續(xù)做這件事。這套東西不復(fù)雜但堅持做下去不容易。7.2 團(tuán)隊規(guī)模小審計怎么做才不累人團(tuán)隊里如果只有三五個人沒有專職DBA做一套完整審計方案確實費勁。這里我給自己小團(tuán)隊的讀者一個“最小可行方案”第一步全部賬號改成一人一賬號關(guān)掉公共賬號。第二步所有業(yè)務(wù)賬號按崗位最小授權(quán)敏感表一律走存儲過程。第三步開啟binlog格式用ROW。第四步配置init-connect建access_log審計表。第五步每季度花兩小時做一次權(quán)限清單核對和審計表抽查。就這五步不用采購任何商業(yè)軟件不增加太多工作量就能覆蓋90%以上內(nèi)部越權(quán)場景的追責(zé)需求。我自己在多個項目里都是用這套組合拳落地的效果穩(wěn)定維護(hù)成本也可控。最后再分享一個實際的小技巧別把審計日志和業(yè)務(wù)數(shù)據(jù)放在同一個磁盤分區(qū)哪怕覺得“應(yīng)該夠用”也要分開放。磁盤被日志寫滿導(dǎo)致業(yè)務(wù)停擺的案例我見得太多了這個簡單隔離能幫你省掉無數(shù)次半夜救火的痛苦。