戰(zhàn)解析:從搜索型注入理解SQL語句拼接與閉合原理)
最近在帶新人做安全測試發(fā)現(xiàn)一個(gè)挺有意思的現(xiàn)象很多朋友在Pikachu靶場里做SQL注入練習(xí)特別是那個(gè)“搜索型注入”關(guān)卡能很快用union和%字符型閉合把數(shù)據(jù)查出來但一問到“為什么這里要用%而不是”或者“為什么union查詢的列數(shù)必須對上”得到的回答往往是“教程里就這么寫的”。這其實(shí)錯(cuò)過了一個(gè)更重要的學(xué)習(xí)點(diǎn)SQL注入的本質(zhì)是理解應(yīng)用程序如何“拼接”你的輸入和它原有的SQL語句。你只是在復(fù)現(xiàn)一個(gè)已知的Payload而沒有真正理解背后的“上下文”。今天我們就以Pikachu靶場的“搜索型注入”為例徹底拆解一次。目標(biāo)不是“通關(guān)”而是讓你以后遇到任何形式的輸入點(diǎn)都能自己分析出閉合方式和注入路徑。1. 先別急著丟Payload理解“搜索型”與“普通查詢型”的根本區(qū)別很多人一看到輸入框就下意識地開始嘗試、、or 11這些經(jīng)典測試。但在Pikachu的“SQL-Inject”模塊里特意區(qū)分了“字符型注入”、“數(shù)字型注入”和“搜索型注入”這絕不是隨便分的。1.1 普通查詢型你的輸入通常被當(dāng)作一個(gè)“完整值”回想一下“字符型注入”那個(gè)場景。你輸入一個(gè)用戶名比如admin后端代碼很可能這樣拼接SQLSELECT * FROM users WHERE username 你輸入的內(nèi)容所以當(dāng)你輸入admin or 11時(shí)拼接后的語句變成了SELECT * FROM users WHERE username admin or 11邏輯是先查找username admin或者11這個(gè)永恒為真的條件。這里你輸入的是用來閉合源代碼中那個(gè)等待你輸入的前引號而最后的11則補(bǔ)上了語句末尾應(yīng)有的另一個(gè)引號有時(shí)后端會自動補(bǔ)有時(shí)需要你自己構(gòu)造。這種注入你的輸入是被當(dāng)作一個(gè)完整的字符串值放在等號的右邊。1.2 搜索型你的輸入是模糊匹配模式的一部分搜索型注入完全不同。它的功能是“搜索”比如搜索新聞標(biāo)題包含某個(gè)關(guān)鍵詞的文章。后端代碼的邏輯通常是SELECT * FROM news WHERE title LIKE %你輸入的關(guān)鍵詞%看到LIKE和%了嗎這是關(guān)鍵。%在SQL中是通配符表示“任意多個(gè)字符”。所以當(dāng)你輸入test時(shí)實(shí)際執(zhí)行的語句是SELECT * FROM news WHERE title LIKE %test%意思是找出所有title字段里任意位置包含test這個(gè)詞的記錄。在這種情況下你輸入的內(nèi)容不再是孤立的“值”而是被嵌入到了一個(gè)由%和%包裹的字符串模式中。你的輸入前后已經(jīng)存在了代碼預(yù)設(shè)的字符單引號和百分號。1.3 為什么這個(gè)區(qū)別至關(guān)重要因?yàn)殚]合方式變了。在普通字符型注入里你只需要考慮閉合一個(gè)開頭的單引號。在搜索型注入里你需要“穿越”兩層包裹首先是代碼中拼接好的那個(gè)結(jié)尾的單引號其次是它前面的那個(gè)百分號通配符。不理解這個(gè)你就會困惑為什么我輸入頁面報(bào)錯(cuò)了但我輸入%頁面又正常了你不是在和一個(gè)孤立的戰(zhàn)斗你是在和一段%你的輸入%的固定模式戰(zhàn)斗。你的Payload必須能“嵌”入這個(gè)模式并且讓整個(gè)SQL語句的語法依然正確。2. 手動探路從報(bào)錯(cuò)信息里還原SQL語句的“模子”理論說再多不如親手試錯(cuò)。在Pikachu的搜索型注入關(guān)卡不要一上來就用工具或已知Payload。第一步試探原始閉合輸入一個(gè)簡單的單引號觀察結(jié)果。很大概率你會看到一個(gè)SQL語法錯(cuò)誤頁面。錯(cuò)誤信息可能類似于You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near % at line 1這個(gè)near %是黃金線索。它告訴你程序執(zhí)行到%這個(gè)地方時(shí)出錯(cuò)了。這印證了我們的猜想我們的輸入被放在%和%之間了。輸入后語句變成LIKE %%引號匹配混亂所以報(bào)錯(cuò)。第二步嘗試初步閉合輸入%觀察結(jié)果。如果頁面返回了正常搜索結(jié)果甚至是所有結(jié)果恭喜你第一步推理正確。 我們來拆解你輸入%后端拼接后語句是LIKE % % %??雌饋韥y我們整理一下LIKE %%%。第一個(gè)%是代碼里的前綴通配符你輸入的%中%被當(dāng)作搜索內(nèi)容的一部分則閉合了代碼中那個(gè)等待輸入的后單引號。 此時(shí)語句末尾多了一個(gè)%代碼中原本用來閉合的后綴%。在MySQL中LIKE %something%如果后面再跟一個(gè)%語法可能是錯(cuò)誤的但有時(shí)也可能被“容忍”或因?yàn)榍耙徊糠忠延行ч]合而執(zhí)行。更常見的正確做法需要完全閉合。第三步構(gòu)造完整閉合與注釋輸入% --%作為搜索內(nèi)容的一部分同時(shí)也“抵消”或“成為”模式的一部分。閉合代碼中的后單引號。--這是注釋符--后面有個(gè)空格在URL中常被解釋為空格。它的作用是注釋掉后面所有代碼包括那個(gè)多余的%。拼接后的理想語句是SELECT * FROM table WHERE title LIKE % % -- %。--之后的內(nèi)容被注釋所以實(shí)際執(zhí)行的是LIKE % %。這個(gè)條件% %匹配任何包含一個(gè)空格的標(biāo)題不% %中間的%是你輸入的內(nèi)容它匹配任何包含百分號字符的記錄。通常表中沒有這樣的記錄所以可能返回空或所有記錄取決于數(shù)據(jù)庫特性。但重點(diǎn)是語法正確不報(bào)錯(cuò)。走到這一步你已經(jīng)完成了最重要的一步手動找到了正確的閉合方式%并確定了可以注釋掉后續(xù)語句。這比直接使用Payload更有價(jià)值因?yàn)槟阒懒恕盀槭裁础薄?. Union聯(lián)合注入的精確打擊列數(shù)匹配與信息獲取在確認(rèn)可以注入即能閉合語句并執(zhí)行我們想要的SQL后下一步就是獲取數(shù)據(jù)。UNION SELECT是最常用的方式之一但它有個(gè)鐵律前后兩個(gè)SELECT語句的列數(shù)必須相同。3.1 為什么列數(shù)必須一致UNION操作符用于合并兩個(gè)或多個(gè)SELECT語句的結(jié)果集。想象一下第一個(gè)查詢原新聞搜索查詢返回了5列數(shù)據(jù)比如id, title, content, author, time。如果你試圖UNION一個(gè)只返回2列數(shù)據(jù)的查詢數(shù)據(jù)庫不知道如何把這兩組不同“形狀”的數(shù)據(jù)拼在一起所以會直接報(bào)錯(cuò)。3.2 如何確定原查詢的列數(shù)有兩種經(jīng)典方法ORDER BY排序法% ORDER BY 1 --按第一列排序正常% ORDER BY 2 --按第二列排序正常% ORDER BY 5 --正常% ORDER BY 6 --報(bào)錯(cuò)這說明原查詢返回的列數(shù)最大是5。所以O(shè)RDER BY 5成功而ORDER BY 6失敗表明列數(shù)為5。原理ORDER BY n表示按結(jié)果集的第n列排序。如果n超過了實(shí)際列數(shù)語法就會出錯(cuò)。UNION SELECT試探法% UNION SELECT 1 --列數(shù)不對報(bào)錯(cuò)% UNION SELECT 1,2 --列數(shù)不對報(bào)錯(cuò)...% UNION SELECT 1,2,3,4,5 --列數(shù)正確頁面正常顯示且數(shù)字1,2,3,4,5中某些會顯示在頁面原本顯示數(shù)據(jù)的位置上當(dāng)UNION SELECT后面的數(shù)字個(gè)數(shù)等于原查詢列數(shù)時(shí)語句才能正確執(zhí)行。頁面顯示的數(shù)字就是對應(yīng)列在結(jié)果集中的輸出位置。在Pikachu這個(gè)靶場里通過嘗試我們可以確定原查詢列數(shù)是5。并且假設(shè)我們發(fā)現(xiàn)數(shù)字2和3顯示在了網(wǎng)頁的標(biāo)題和內(nèi)容區(qū)域。3.3 利用顯示位獲取信息知道列數(shù)和顯示位后我們就可以把想要的信息放到對應(yīng)的SELECT位置去。獲取當(dāng)前數(shù)據(jù)庫名和用戶% UNION SELECT 1, database(), user(), 4,5 --這里我們把database()當(dāng)前數(shù)據(jù)庫名放在第2位user()當(dāng)前數(shù)據(jù)庫用戶放在第3位。執(zhí)行后數(shù)據(jù)庫名和用戶名就會顯示在頁面的標(biāo)題和內(nèi)容處。獲取所有數(shù)據(jù)庫名% UNION SELECT 1, group_concat(schema_name),3,4,5 FROM information_schema.schemata --information_schema.schemata是MySQL的系統(tǒng)表存有所有數(shù)據(jù)庫信息。group_concat()函數(shù)把多行結(jié)果合并成一個(gè)字符串方便查看。獲取指定數(shù)據(jù)庫如pikachu的所有表名% UNION SELECT 1, group_concat(table_name),3,4,5 FROM information_schema.tables WHERE table_schemapikachu --獲取指定表如users的所有列名% UNION SELECT 1, group_concat(column_name),3,4,5 FROM information_schema.columns WHERE table_schemapikachu AND table_nameusers --最終獲取數(shù)據(jù)如username和password% UNION SELECT 1, username, password,4,5 FROM pikachu.users --這個(gè)過程就像拼圖確定形狀列數(shù)- 找到接口顯示位- 替換內(nèi)容注入查詢。每一步都建立在之前對SQL語句結(jié)構(gòu)閉合方式的理解之上。4. 從靶場到實(shí)戰(zhàn)思維升級與防御視角在靶場里一切都是已知的。但在真實(shí)環(huán)境中一切都是黑盒。你需要把上面的“試探-分析-構(gòu)造”過程內(nèi)化成一種思維習(xí)慣。4.1 通用探測流程 Checklist面對一個(gè)未知的輸入點(diǎn)可以按這個(gè)順序思考判斷注入類型數(shù)字型嘗試1 and 11/1 and 12看邏輯是否變化。字符型嘗試和兩個(gè)單引號看是否報(bào)錯(cuò)或行為異常。搜索型嘗試、%、% --觀察頁面返回差異。特別注意LIKE語句特有的模式。確定閉合方式與注釋通過報(bào)錯(cuò)信息或布爾邏輯正常/報(bào)錯(cuò)判斷是用、、)還是))等閉合。確定有效的注釋符是--空格重要、#還是/* */。判斷列數(shù)優(yōu)先使用ORDER BY因?yàn)樗词钩鲥e(cuò)有時(shí)也比UNION錯(cuò)誤信息更友好。用UNION SELECT null,null,...null兼容所有類型來最終確認(rèn)并尋找顯示位。信息收集按照數(shù)據(jù)庫版本/用戶 - 數(shù)據(jù)庫列表 - 表名 - 列名 - 數(shù)據(jù)的順序進(jìn)行。information_schema是你的地圖。4.2 開發(fā)者的防御視角為什么參數(shù)化查詢是根本作為攻擊者我們利用的是“字符串拼接”的漏洞。那么防御的核心就是杜絕拼接。錯(cuò)誤做法拼接# 偽代碼 query SELECT * FROM news WHERE title LIKE % user_input % execute(query)這就是Pikachu靶場模擬的情況你的輸入被直接“貼”進(jìn)了SQL語句。正確做法參數(shù)化查詢/預(yù)編譯# 偽代碼以Python為例 query SELECT * FROM news WHERE title LIKE %s execute(query, (% user_input %,))這里%s是一個(gè)占位符。數(shù)據(jù)庫引擎會先編譯SQL語句的結(jié)構(gòu)SELECT ... WHERE title LIKE ?然后將用戶輸入%test%作為純粹的數(shù)據(jù)傳遞給這個(gè)編譯好的結(jié)構(gòu)。即使用戶輸入是% UNION SELECT ... --它也會被整體當(dāng)作一個(gè)字符串去進(jìn)行LIKE匹配而不會成為SQL語法的一部分。根本區(qū)別拼接是把用戶輸入當(dāng)成了“代碼”SQL語法的一部分參數(shù)化是把用戶輸入永遠(yuǎn)當(dāng)作“數(shù)據(jù)”。這是防御SQL注入最有效、最根本的方法。4.3 靶場之外的思考Pikachu這類靶場提供了一個(gè)安全的、理想化的學(xué)習(xí)環(huán)境。但現(xiàn)實(shí)更復(fù)雜WAFWeb應(yīng)用防火墻可能會攔截union、select、information_schema等關(guān)鍵詞你需要嘗試大小寫、雙寫、編碼、注釋符分割等繞過技巧。錯(cuò)誤信息被屏蔽頁面不再顯示詳細(xì)的SQL錯(cuò)誤你的“盲注”能力基于時(shí)間延遲或布爾邏輯的判斷就變得至關(guān)重要。更復(fù)雜的閉合可能遇到)、))、甚至多層嵌套的閉合。因此通關(guān)靶場不是終點(diǎn)而是起點(diǎn)。它給了你一套標(biāo)準(zhǔn)的“解剖”工具閉合、聯(lián)合、報(bào)錯(cuò)、盲注和清晰的解剖對象一個(gè)故意留有漏洞的程序。真正的能力是在沒有地圖、工具受限的情況下依然能通過邏輯推理找到那條注入路徑?;氐介_頭的問題為什么搜索型注入常用%閉合因?yàn)樗獞?yīng)對的是LIKE %[輸入]%這個(gè)預(yù)設(shè)的“模子”。你的Payload必須首先成為這個(gè)模子中合格的一部分然后才能跳出模子執(zhí)行你想做的任何事情。理解了這個(gè)“模子”你面對任何輸入框第一反應(yīng)就不再是機(jī)械地嘗試Payload列表而是會下意識地去想“我的輸入會被放在一段怎樣的SQL上下文中” 這個(gè)問題才是SQL注入攻防的核心。