之前我們說過,黑客可以利用別人的cookie,冒充真實的用戶,在頒發cookie的網站中為所欲為。
后來瀏覽器頒布了一個同源策略,偷cookie的招數就行不通了。

## XSS
只要想辦法把JavaScript代碼注入到目標頁面,就可以繞過同源策略了。
比如可以在 HTML 中的 `< input>`,這個標簽會在瀏覽器中產生一個輸入框,讓用戶輸入數據,我們可以把 JavaScript 代碼當做數據輸入進去, 等到數據提交到服務器端,會保存下來,下次展示頁面的時候不就可以執行了嗎
比如有這么一個網站,可以讓你對某個文章輸入評論
在評論區輸入了這樣的代碼,注意,我們注入了一段'JavaScript'”

等到再次有人訪問這個頁面的時候,就會把cookie顯示出來。

當然不能直接讓cookie這么顯示出來。
那怎么才能為黑客所用呢?
我們再想想,同源策略并不限制`<img>`這樣的標簽從別的網站上**跨域**去下載圖片,我們在注入JavaScript代碼的時候,同時創建用戶不可見的`<imag>`,通過這個標簽把cookie發給黑客。

只要這段代碼被執行,用戶的 cookie 就會發到黑客服務器上 (http://beauty.com/log),我們就等著收取 cookie
我們把這種方法叫做 Cross Site Scripting ,簡稱 CSS,但是CSS已經有其他的含義了,可以叫做 XSS
> 按照 XSS 的分類方法, 上面介紹的叫做存儲性 XSS, 危害最大。 還有反射型 XSS, 基于 DOM 的 XSS,本文不再展開。
瀏覽器也自有應對措施。
人們在網站的Cookie加上了HttpOnly這樣的屬性:
```
Set-Cookie:JSESSIONID=xxxxxx;Path=/;Domain=book.com;HttpOnly
```
cookie一旦加上了HttpOnly,瀏覽器就禁止JavaScript讀取Cookie了,自然無法發回給beauty.com
但是對于黑客而言,既然可以在往指定的頁面注入JavaScript代碼,那么不一定只是用來借Cookei,還可以用JS代碼畫一個假的登錄框,覆蓋到真的上面,這樣一樣可以偷到真實的用戶名和密碼。
或者說通過JavaScript構造GET、POST請求,可以模擬用戶在網站上做手腳,從一個賬號往另一個賬號打錢。
那么我們怎么應對呢?
- 一方面會對輸入進行過濾,發現不符合要求的輸入例如 `<,> `等就會過濾掉,我們的 `<script> `可能會變成'script' 被存到數據庫里。”
- 另一方面有人還會對輸出進行編碼 / 轉義操作,例如會把'<'變成'`<`',把'>' 變成‘`>`’ ,然后再輸出,這樣一來我們的 `<script>` 就會變成`& lt;script>` , 瀏覽器收到以后,就會認為是數據,把 `<script> `作為字符串給顯示出來,而不是執行后面的代碼!
## 跨站請求偽造
現在看上去,可以取到Cookie的路貌似被堵上了。
但是實際上還是有漏洞。
一個用戶的會話Cookie在瀏覽器沒有關閉的時候,是不會被刪除的。
我們換一下思路,不再去偷這個Cookie了,可以在Beauty.com中構造一個領獎頁面,里面包含一個連接,讓用戶去點擊。
比如
```
恭喜你獲得了 iPhone X 一臺, 快來 <a href="www.icbc.com.cn/transfer?toBankId = 黑客三兄弟的賬戶 & money = 金額 "> 領取吧!</a>
```
如果黑客事先知道 icbc.com.cn 的轉賬操作的 url 和參數名稱。如果這個用戶恰好登錄了 icbc.com, 那他的 cookie 還在, 當他禁不住誘惑,點了這個鏈接后,一個轉賬操作就神不知鬼不覺的發生了。

那要是用戶就是不點擊呢?
同樣可以創建一個看不見的圖片,
```
?<img www.icbc.com.cn/transfer?toAccountID = 黑客三兄弟的賬戶 & money = 金額 ">
```
“只要他打開了這個頁面,不用點擊任何東西,就會發生轉賬操作。
怪不得現在有很多郵箱默認不顯示郵件中的圖片呢!
那要是人家 icbc.com.cn 的轉賬操作需要 form 表單,是 POST 操作呢?
黑客可以把這個表單創建起來,放到一個不可見的 iframe 中,用戶只要一訪問,就用 JavaScript 自動提交

只要這個用戶在訪問 icbc.com.cn 的時候, 訪問了黑客的網站,就極有可能中招,黑客只是利用了一下合法的 Cookie,在服務器看來,發出的請求,那就是一次合法的請求
這叫**跨站請求偽造**啊,Cross Site Request Forgery(CSRF)
### 人們的應對方案
那么人類如何應對呢?
- 用戶在 icbc.com.cn 轉賬,顯示轉賬的 form,除了常用的字段之外,額外添加一個 token

這個 token 是** icbc.com 服務器端**生成的,是一個隨機的數字。
- 用戶的轉賬數據發送的服務器端, icbc.com 就會檢查從瀏覽器發過來的數據中有沒有 token, 并且**這個 token 的值是不是和服務器端保存的相等**,如果相等,就繼續執行轉賬操作,如果不相等,那這次 POST 請求肯定是偽造的。
## SQL注入
黑客用 XSS 和 CSRF 這兩個工具獲利頗豐,后來人們在編程中很注意防范,這兩個漏洞越來越少了。
既然前端行不通,那么是否可以從服務器端來入手呢?
比如說SQL注入,那什么是SQL注入呢?
比如網站有一個users表格

這個網站有個功能,根據 id 來查看用戶信息,http://xxxx.com/user?id=xxxx, 對應的 SQL 可能是這樣的:”
```
string sql ="SELECT id , name, age from users WHERE id="+<id>;
```
如果用戶在瀏覽器的 URL 是 http://xxxx.com/user?id=1, 那真正執行的 SQL 就是這樣:
```
SELECT id , name, age from users WHERE id=1
```
也就是

那么,如果黑客輸入了[ http://xxxx.com/user?id=1 or 1=1 ]( http://xxxx.com/user?id=1 or 1=1 )會發生什么狀況?”
SQL變成了:
```
SELECT id , name, age from users WHERE id=1 or 1=1
```
`or 1=1` 會讓 where 字句的值一直是 true, 就可以把所有的 user 數據都給提取出來了!
原理很簡單,但是想用好可不容易,你再試試這個網站:www.badblog.com/viewblog?id=U123, 這個 URL 能顯示 ID 為 U123 的博客摘要。
如果把 url 改為 [www.badblog.com/viewblog?id=U123 or 1=1](www.badblog.com/viewblog?id=U123 or 1=1) , 我們以為最終的 sql 就是: SELECT xxx FROM xxx WHERE id =U123 or 1=1 ,但是實際上,瀏覽器只是提示:“無效的博客 ID”
為什么會這樣呢?
其實吧,你沒有注意到,那個 id 不是一個數字,是一個**字符串** ("U123"),背后的 SQL 可能是這樣的:
```
string sql = "SELECT xxx FROM xxx WHERE id='" + <id> +"'";
```
字符串的話需要用單引號括起來,所以 URL 應該這么寫:www.badblog.com/viewblog?id=U123'or'1'='1'
這樣才能生成有效的 SQL:`SELECT xxx FROM xxx WHERE id ='U123' or '1'='1'`
可是瀏覽器還是沒有把所有博客都顯示出來,還是只顯示了一條, 但不是 U123 對應的那條博客。
這是因為 SQL 執行成功了,但是內部的程序永遠只返回 SQL 結果集的第一行啊。”
### 瘋狂注入SQL
于是可以利用這個這個網站的用戶名和密碼給挖出來。”
假設這個網站是個 Mysql 數據庫,接下來你得懂一點 Mysql 數據庫知識了。我們分三步走,首先獲取這個數據庫的庫名,然后獲取所有的表名,最后找到用戶表,從中 select 數據,也就是不斷地往那個 URL 注入 SQL 語句
第一步,我們已經能猜出那個 URL 對應的 SQL 是:`SELECT xxx FROM xxx WHERE xx=<id>`,并且我們知道,這個 SELECT 出的數據中至少有兩列(標題和內容),現在我們注入數據,形成一個這樣的 sql 出來:
```
SELECT xxx FROM xxx WHERE xx=3 union select 1,2,3,4,5,6,7,8
```
為什么這么做?
因為union 要求兩個結果集的列必須個數相同,現在 union 的第二部分輸入了 8 個 column , 就是猜測 union 的第一個字句也有 8 個 column
如果這個 SQL 執行不正常(界面會有錯誤), 我們就再嘗試,增加或減少列,直到成功為止。
假設試到列數為 3 的時候,SELECT xxx FROM xxx WHERE xx=3 union select 1,2,3
瀏覽器頁面突然顯示了出了兩條新聞, 一條有正常的標題和內容, 另外一條的標題是 2, 內容是 3

關鍵點是第二列和第三列的值會被顯示到瀏覽器的界面中,接下來我們可以這么做:
```
SELECT xxx FROM xxx WHERE xx=3 union select 1,2,database()
```
于是就獲得了數據庫的名稱: epdb

mysql 中 information_schema.tables 這個表保存著所有的表名,現在知道了數據庫的名稱,只需要把數據庫名稱傳遞過去就行了”
```
SELECT xxx FROM xxx WHERE xx=3 union select 1,2,table_name from information_schema.tables where table_schema='epdb'
```

用戶表多半是ep_users
接下來又構造出一個 sql ,把 ep_users 的列名全取出來:
```
SELECT xxx FROM xxx WHERE xx=3 union select 1,2,column_name from information_schema.columns where table_name='ep_users'
```

看來這個 ep_users 有這么幾個 column : id ,name, pwd。
再接再厲,把 ep_users 表的數據給選出來:
```
SELECT xxx FROM xxx WHERE xx=3 union select 1,name,pwd from ep_users
```

### 破解密碼
雖然現在看得到密碼了,但是密碼不是明文的是,這些密碼是通過HASH算法計算出來。

“這個 Hash 值會保存到數據庫當中,等到你下次登錄,輸入用戶名和密碼的時候,就會再次對密碼進行同樣的 hash 計算,然后和數據庫的值比較,看看是不是相同。”
我們知道, hash 是不可逆的運算,所以即使被偷取了,也無法得到明文密碼。
有幾種辦法可以去破解密碼,
- 一種就是猜測,比如我準備了很多人們最常用的密碼,然后把這些密碼也做 hash 操作,和數據庫密碼對比,如果匹配,我就知道明文密碼了。
- 還有一種就是查表,我事先把明文密碼和計算好的 hash 值形成一個對照表,然后用數據庫中密碼的 hash 值去對照表中查找,如果找到了,明文密碼也就有了。當然為了提高效率,人們還制作了所謂彩虹表。”

那怎么辦?
還可以在密碼中加鹽
給每個密碼都加了一了隨機數,然后再做 Hash 操作。 這樣一來,通過查找的方式就難于破解了

其實這都不是最關鍵的。
最關鍵的是有可能管理員權限也通過SQL注入的方式得到了。
那個 SQL 可能是這樣子的:
```
select xx from ep_users where user='<username>'and pwd='<password>'
```
然后代碼會判斷這個 sql 返回的結果集數目是否為 0, 如果不為 0 就認為登錄成功。
那我通過注入把它改寫成:
```
select xxx from ep_users where user='admin'and password='password'OR'1'='1'
```
然后黑客就立刻就登錄了,由于用戶名是 admin,現在黑客已經有了管理員權限了
### 后記
實際上 SQL 注入漏洞的危害非常巨大,因為黑客可以利用漏洞去執行數據庫中很多函數(mysql 的 LOAD_FILE, SELECT INTOFILE),存儲過程(例如臭名卓著的 xp_cmdshell), 可以向服務器植入代碼。 如果有數據庫賬號適當權限,還可以創建表,刪除表,非常可怕。
防御 SQL 注入的最佳方式,就是不要拼接字符串, 而要使用**預編譯**語句,綁定變量,不管你輸入了什么內容,預編譯語句只會把它當成數據的一部分。
```
String sql = "select id from users where name=?";
PreparedStatement pstmt = conn.prepareSatement(sql);
pstmt.setStrig(1,request.getParameter("name");
pstmt.executeQuery();
```