
1. JSON注入當SQL注入穿上“新衣”在Web安全領域SQL注入SQL Injection早已是“老生常談”的漏洞類型但凡有點經驗的開發者或安全測試人員都能對‘ or ‘1’‘1這類經典Payload倒背如流。然而隨著前后端分離架構和RESTful API的普及JSONJavaScript Object Notation格式的數據交互已成為絕對的主流。攻擊面也隨之發生了遷移——傳統的SQL注入攻擊開始披上了一件名為“JSON”的新外衣。這就是我們今天要深入探討的JSON注入。簡單來說JSON注入是SQL注入的一種特殊形式。它的攻擊原理與傳統SQL注入并無二致核心都是“將用戶輸入的數據當作代碼來執行”。區別在于攻擊載荷Payload的載體和注入點發生了變化從傳統的application/x-www-form-urlencoded或multipart/form-data格式的表單參數變成了application/json格式的HTTP請求體。很多開發者在處理JSON數據時安全意識還停留在“解析出參數再過濾”的舊思維或者過度依賴框架的“自動綁定”功能從而在參數解析的源頭就留下了安全隱患。對于攻擊者而言這扇“新門”可能比舊門防守更松懈。這篇文章我將從一個實戰滲透測試工程師的角度帶你徹底拆解JSON注入。我們不僅會講清楚它的原理、與普通SQL注入的異同更會通過模擬真實漏洞場景手把手演示如何發現、利用和防御這種漏洞。無論你是正在學習Web安全的初學者還是想鞏固自身防御體系的開發人員相信這篇超過5000字的深度解析都能讓你有所收獲。2. 核心原理JSON數據流中的SQL指令“走私”要理解JSON注入我們必須先厘清一個典型的數據處理流程。在現代Web應用中一個基于JSON的API接口處理用戶請求時數據流通常經歷以下環節客戶端構造請求前端或API調用方將一個JSON對象序列化為字符串放入HTTP請求的Body中并設置Content-Type: application/json。服務器接收與解析后端服務如Java Spring、Python Flask、PHP Laravel、Node.js Express等的Web框架接收到請求調用內置或配置的JSON解析器如Jackson、Gson、json_decode、body-parser將字符串還原為內存中的對象或字典。參數提取與綁定框架或開發者代碼從解析后的對象中提取出具體的參數值例如username、searchKeyword、productId等。數據庫查詢將這些參數值拼接到SQL語句中發送給數據庫執行。JSON注入的致命漏洞就潛伏在第3步到第4步之間。關鍵在于開發者是否在參數值被拼接到SQL語句前對其進行了充分的、上下文相關的安全處理2.1 漏洞產生的典型場景讓我們看一個極度危險但非常常見的錯誤代碼示例以PHP為例但思想通用// 錯誤示例直接拼接JSON解析后的參數 $input json_decode(file_get_contents(php://input), true); // 解析JSON請求體 $username $input[username]; $password $input[password]; $sql SELECT * FROM users WHERE username $username AND password $password; $result mysqli_query($conn, $sql);在這段代碼中開發者直接從$_POST超全局數組轉向了從json_decode()的結果中取參數。他們可能認為“我已經用了json_decode這是標準解析沒問題。” 或者“框架的自動綁定如Spring的RequestBody會幫我處理好類型。” 這種想法是致命的。json_decode只負責語法解析將字符串{username: admin, password: 123456}變成關聯數組它絕不負責安全過濾。假設攻擊者發送以下JSON請求體{ username: admin, password: OR 11 }經過json_decode后$password的值就是字符串‘ OR ‘1’‘1。當這個值被直接拼接到SQL語句中后最終的查詢語句就變成了SELECT * FROM users WHERE username admin AND password OR 11由于‘1’‘1‘恒為真這條語句將繞過密碼驗證返回用戶表中的所有數據或第一條數據導致認證被繞過。這就是最經典的JSON注入。2.2 與普通SQL注入的異同點特性傳統SQL注入JSON注入攻擊載荷載體URL查詢字符串、POST表單字段、HTTP頭如CookieHTTP請求體Body中的JSON字符串Content-Typeapplication/x-www-form-urlencoded,multipart/form-dataapplication/json服務器處理通常由Web服務器或框架自動解析到$_GET、$_POST等超全局變量需要顯式調用JSON解析器如json_decode或依賴框架自動綁定WAF/IDS檢測難度相對容易規則成熟對URL和表單參數掃描深入可能更難早期WAF可能不深入掃描JSON Body或解析JSON格式有誤開發者盲點警惕性較高普遍知道需要過濾$_GET和$_POST警惕性較低容易認為“JSON是結構化數據更安全”或過度信任框架漏洞本質完全相同未對用戶輸入進行正確的過濾、轉義或參數化導致輸入被解釋為SQL代碼。關鍵認知JSON注入不是一種新的漏洞類型而是SQL注入在新時代、新數據格式下的“新皮膚”。防御的核心思想沒有絲毫改變——永遠不要信任用戶輸入。3. 實戰挖掘如何發現并利用JSON注入點知道了原理我們該如何在實戰中尋找這樣的漏洞呢這個過程可以系統性地分為信息收集、漏洞探測、漏洞利用和權限提升四個階段。3.1 信息收集與目標識別接口枚舉使用爬蟲工具如Burp Suite的爬蟲、OWASP ZAP或主動掃描收集應用的所有API端點。重點關注那些使用POST、PUT、PATCH方法且Content-Type為application/json的接口。功能點分析哪些功能最可能拼接SQL語句登錄/認證/api/login,/auth/token搜索/查詢/api/search,/query/data, 帶過濾條件的列表查詢用戶資料更新/api/user/update訂單/商品操作/api/order/create,/product/list參數推測通過正常業務操作抓取請求包分析JSON結構。常見的注入參數名包括id,username,keyword,name,title,orderBy,sort等。3.2 漏洞探測與指紋識別探測的核心是向JSON參數中插入各種SQL注入測試Payload并觀察應用響應。這里有幾個關鍵技巧使用工具Burp Suite的Intruder或Repeater模塊是絕佳選擇。將請求發送到Repeater手動修改JSON值進行測試。測試Payload基礎探測在參數值后添加單引號‘、雙引號“、反斜杠\觀察是否出現數據庫錯誤如MySQL、PostgreSQL、SQL Server的特定錯誤信息。這是判斷是否存在注入點的最快方法。請求{id: 1‘}觀察響應是否包含“You have an error in your SQL syntax...”或程序返回了異常的500狀態碼。邏輯測試使用AND ‘1’‘1和AND ‘1’‘2測試布爾邏輯。請求1{search: apple‘ AND ‘1’‘1}請求2{search: apple‘ AND ‘1’‘2}觀察兩個請求返回的結果集是否不同。如果不同則存在布爾盲注的可能。時間盲注測試對于無錯誤回顯的情況使用sleep()或benchmark()函數。請求{id: 1‘ AND SLEEP(5)-- }觀察響應時間是否明顯延遲了大約5秒。實操心得在修改JSON時務必保證修改后的JSON語法仍然是正確的。例如如果原值是字符串你注入1‘ AND ‘1’‘1后整體還是合法的JSON字符串。如果原值是數字你注入1 AND 11可能破壞JSON格式因為1 AND 11不是合法數字此時需要將參數值類型從數字改為字符串即“1 AND 11”。很多新手在這里會踩坑。3.3 漏洞利用從注入到數據獲取一旦確認注入點利用方式就和傳統SQL注入一模一樣了。我們以基于錯誤的注入為例演示如何獲取數據庫信息。場景一個用戶查詢接口/api/user/profile接受JSON{“userId”: 123}。測試發現userId參數存在數字型注入。判斷列數ORDER BY發送{“userId”: “1 ORDER BY 5-- “}注意將數字改為字符串如果返回正常ORDER BY 6--時出錯則說明有5列。聯合查詢獲取數據UNION SELECT假設有5列我們想讓第2、3列顯示在頁面上。發送{“userId”: “-1 UNION SELECT 1, database(), user(), version(), 5-- “}觀察響應可能直接顯示出當前數據庫名、用戶和版本信息。獲取表名、列名利用數據庫的元數據表。以MySQL為例{“userId”: “-1 UNION SELECT 1, table_name, 3, 4, 5 FROM information_schema.tables WHERE table_schemadatabase() LIMIT 0,1-- “}通過遍歷LIMIT參數可以爆出所有表名如users,admins,passwords。拖取數據假設我們找到了users表猜測有username和password列。{“userId”: “-1 UNION SELECT 1, username, password, 4, 5 FROM users LIMIT 0,1-- “}注意事項在JSON中進行聯合查詢注入時要特別注意數據類型。如果后端代碼期望userId是整數而你用UNION SELECT返回了一個字符串在對應位置可能會導致類型轉換錯誤或查詢失敗。此時需要嘗試NULL或轉換函數如{“userId”: “-1 UNION SELECT 1, CONCAT(username, ‘:’, password), 3, 4, 5 FROM users-- “}并將注入點參數改為字符串類型。3.4 自動化工具與繞過技巧sqlmap這款神器同樣支持JSON注入。你需要使用--data參數指定JSON數據并用*標記注入點。sqlmap -u http://target.com/api/user/profile --data{userId:*} --headersContent-Type: application/json --batchsqlmap會自動處理JSON格式和注入點標記進行全自動探測和利用。WAF繞過針對JSON格式一些特殊的繞過技巧可能生效JSON語法變異插入多余的逗號、換行符、制表符。某些蹩腳的WAF解析JSON的規則可能比應用服務器更嚴格。{id:1,/*注釋*/username:admin-- }Unicode轉義將關鍵字符進行Unicode轉義。例如單引號‘可以寫成\u0027。這取決于后端在JSON解析后是否對字符串進行解碼。{username:admin\u0027 OR \u00271\u0027\u00271}參數污染在JSON中提交同一個鍵的多個值雖然不符合標準如{id:1, id: 1‘ AND SLEEP(5)”}。不同語言的解析庫處理方式不同可能造成WAF和應用解析結果不一致。4. 防御之道從源頭杜絕JSON注入防御JSON注入必須采取縱深防御策略在數據流的每一個環節都設置關卡。4.1 第一道防線使用參數化查詢預編譯語句這是唯一從根本上解決SQL注入的方法。它的原理是將SQL代碼與數據分離。你提前定義好帶有占位符如?、name的SQL語句模板然后將用戶輸入的數據作為參數單獨傳遞給數據庫驅動。數據庫驅動會確保參數值只被當作數據來處理絕不會被解釋為SQL代碼。各語言示例PHP (PDO):$stmt $pdo-prepare(SELECT * FROM users WHERE username :username AND password :password); $stmt-execute([ :username $input[username], // 直接使用無需轉義 :password $input[password] ]); $user $stmt-fetch();Python (sqlite3 / MySQLdb):cursor.execute(SELECT * FROM users WHERE username %s AND password %s, (username, password))Java (JDBC):PreparedStatement stmt conn.prepareStatement(SELECT * FROM users WHERE username ? AND password ?); stmt.setString(1, username); stmt.setString(2, password); ResultSet rs stmt.executeQuery();Node.js (mysql2):connection.execute(SELECT * FROM users WHERE username ? AND password ?, [username, password], (err, results) { ... });核心要點無論你的參數來自$_POST、$_GET還是JSON解析后的對象都必須使用參數化查詢。這是鐵律。4.2 第二道防線使用安全的ORM框架對象關系映射ORM框架如Java的MyBatis需配合#{}、Hibernate Python的SQLAlchemy PHP的Eloquent Node.js的Sequelize、TypeORM等在正確使用時內部也是基于參數化查詢。關鍵區別安全MyBatis的#{}語法是參數化查詢而${}是字符串拼接存在注入風險。!-- 安全 -- select idfindUser resultTypeUser SELECT * FROM user WHERE username #{username} /select !-- 危險 -- select idfindUser resultTypeUser SELECT * FROM user WHERE username ${username} /select切勿在ORM中拼接字符串即使使用ORM也絕對不要用字符串格式化或拼接的方式來構造查詢條件。4.3 第三道防線嚴格的輸入驗證與類型轉換在將數據送入SQL之前進行嚴格的業務邏輯驗證。類型強制轉換如果參數應該是數字在代碼中盡早將其轉換為整型。$userId (int) $input[userId]; // 非數字會變成0或1 // 或者進行校驗 if (!is_numeric($input[userId])) { throw new InvalidArgumentException(Invalid user ID); }白名單驗證對于像排序字段orderBy、狀態碼這類有限集合的參數使用白名單。allowed_order_fields {id, name, created_at} order_by request.json.get(orderBy, id) if order_by not in allowed_order_fields: order_by id長度與格式限制對用戶名、郵箱、手機號等設置合理的長度和格式正則校驗。4.4 第四道防線最小權限原則與數據庫加固應用數據庫賬戶為Web應用配置的數據庫連接賬戶只授予其完成業務所需的最小權限通常是SELECT,INSERT,UPDATE,DELETE堅決不要授予DROP,CREATE,ALTER,FILE等高危權限。存儲過程對于復雜查詢可以考慮使用存儲過程但同樣要注意在存儲過程內部避免動態SQL拼接。定期更新與漏洞掃描保持數據庫、Web框架、JSON解析庫等所有組件的更新。使用DAST動態應用安全測試工具定期對API接口進行安全掃描。4.5 Web應用防火墻WAF的定位WAF可以作為最后一道檢測和緩解的防線但絕不能作為主要的防御手段。一個配置得當的WAF可以攔截大量已知的、自動化的攻擊Payload。但對于精心構造的、針對特定業務邏輯的注入WAF很可能被繞過。安全的核心永遠是“安全編碼”WAF只是“安全帶”不是“安全駕駛技術”。5. 案例復盤一個真實的JSON注入漏洞挖掘記錄去年在一次授權滲透測試中我遇到了一個非常典型的JSON注入案例。目標是一個新興的SaaS平臺管理后臺其API全部采用JSON通信。初步探測通過Burp抓包我發現一個查詢日志的接口POST /api/admin/queryLog其JSON結構為{“filters”: {“operator”: “eq”, “field”: “username”, “value”: “admin”}, “page”: 1}。這看起來是一個通用的查詢構建器。漏洞發現我嘗試修改value值為admin‘請求后返回了詳細的MySQL錯誤信息直接暴露了表結構的一部分。確認存在基于錯誤的注入。利用過程由于是POST請求我直接在Burp Repeater中操作。我首先嘗試了聯合查詢但發現頁面只返回了data字段其他字段不顯示。于是轉向基于錯誤的注入利用extractvalue()函數進行報錯回顯。Payload:{“filters”: {“operator”: “eq”, “field”: “username”, “value”: “admin‘ AND extractvalue(1, concat(0x7e, (SELECT database()), 0x7e)) AND ‘1’‘1”}}響應中包含了XPATH syntax error: ‘~saas_platform_prod~‘成功獲取數據庫名。數據獲取隨后我通過同樣的報錯注入逐步獲取了所有表名、列名并最終拖出了管理員用戶表的哈希密碼。整個過程中后端沒有對field和value參數做任何過濾直接拼接到了WHERE子句中。漏洞根源與開發團隊溝通后得知他們為了實現靈活的動態查詢自己編寫了一個SQL構建器。這個構建器雖然對field做了白名單映射防止查詢非預期字段但對value的值完全信任直接使用字符串拼接導致了注入漏洞。修復建議我向他們提供了三個方案1. 重構查詢構建器對value也采用參數化查詢2. 放棄部分靈活性將operator和field的組合固定為有限的幾種查詢模式3. 使用更安全的、經過審計的查詢構建庫如Java的QueryDSL。他們最終選擇了方案1和3的結合。6. 針對特定框架的注意事項不同的Web框架在處理JSON和數據庫交互時有各自的特點和坑點。Spring Boot (Java)RequestBody自動綁定非常方便但綁定后的對象屬性如果直接用于拼接SQL同樣危險。務必使用JPA的Query配合參數索引?1或命名參數:name或使用JdbcTemplate的NamedParameterJdbcTemplate。警惕SpEL表達式注入它與SQL注入不同但同樣危險。Express (Node.js)使用body-parser或Express內置的express.json()中間件后數據在req.body中。避免使用字符串模板或操作符拼接SQL。堅持使用mysql2的execute或query的?占位符或ORM的參數化方法。Laravel (PHP)Eloquent ORM和查詢構造器默認是參數化的非常安全。但是如果你使用了DB::raw()或whereRaw()等方法就必須手動處理參數綁定。// 危險 $users DB::table(users)-whereRaw(username . $request-input(name) . )-get(); // 安全 $users DB::table(users)-whereRaw(username ?, [$request-input(name)])-get();Django (Python)Django的ORM是高度安全的幾乎消除了SQL注入的可能。唯一的風險點是使用extra()或RawSQL()時需要格外小心確保用戶輸入不被直接放入SQL字符串。JSON注入的威脅真實而普遍它利用了開發者在新技術棧下的思維滯后。防御的關鍵不在于識別數據是來自表單還是JSON而在于時刻銘記“數據即代碼”的威脅模型并在任何數據與SQL指令交匯的地方堅定不移地使用參數化查詢。作為開發者請在每一次編寫數據庫查詢代碼時都條件反射般地思考我用的方法是拼接字符串還是傳遞參數作為安全人員在測試現代Web應用時請務必把你的測試Payload塞進那個看似規整的JSON Body里去看看。