關於Web安全的問題,是一個老生常談的問題,作為離使用者最近的一層,我們大前端應該把手伸的更遠一點。
我們最常見的Web安全攻擊有以下幾種:
XSS
跨站指令碼攻擊CSRF
跨站請求偽造URL
跳轉漏洞ClickJacking
點選劫持/UI-覆蓋攻擊SQL Injection
SQL隱碼攻擊OS Command Injection
OS命令注入
一、XSS
XSS (Cross Site Script),中文是跨站指令碼攻擊;其原本縮寫是 CSS,但為了和層疊樣式表(Cascading Style Sheet)有所區分,因而在安全領域叫做 XSS。
惡意攻擊者往Web頁面裡插入惡意Script程式碼,當使用者瀏覽該頁之時,嵌入其中Web裡面的Script程式碼會被執行,從而達到惡意攻擊使用者的目的。
XSS攻擊可以分為3類:
- 反射型 - 非持久型
Reflected XSS
- 儲存型 - 持久型
Stored XSS
- 基於DOM或本地的XSS
DOM-based or local XSS
1. 反射型
反射型 XSS 只是簡單地把使用者輸入的資料 “反射” 給瀏覽器,這種攻擊方式往往需要攻擊者誘使使用者點選一個惡意連結,或者提交一個表單,或者進入一個惡意網站時,注入指令碼進入被攻擊者的網站。
假裝我是一個惡意連結(Click Me~)
const http = require('http');
// 啟http服務
const server = http.createServer(function (req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
res.write('<script>while(true)alert("反射型 XSS 攻擊")</script>');
res.end();
});
server.listen('8000');
複製程式碼
這樣就產生了反射型 XSS 攻擊。攻擊者可以注入任意的惡意指令碼進行攻擊,可能注入惡作劇指令碼,或者注入能獲取使用者隱私資料(如cookie)的指令碼,這取決於攻擊者的目的。
2. 儲存型
儲存型 XSS 會把使用者輸入的資料 "儲存" 在伺服器端,當瀏覽器請求資料時,指令碼從伺服器上傳回並執行。這種 XSS 攻擊具有很強的穩定性。
比較常見的一個場景是攻擊者在社群或論壇上寫下一篇包含惡意 JavaScript 程式碼的文章或評論,文章或評論發表後,所有訪問該文章或評論的使用者,都會在他們的瀏覽器中執行這段惡意的 JavaScript 程式碼。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>儲存型 XSS</title>
</head>
<body>
<div>Try Me:<input type="text" id="input"></div>
<button id="btn">Submit</button>
<script>
const input = document.getElementById('input');
const btn = document.getElementById('btn');
let val;
input.addEventListener('change', e => {
val = e.target.value;
}, false);
btn.addEventListener('click', e => {
fetch('http://localhost:8000/save', {
method: 'POST',
body: val
});
}, false);
</script>
</body>
</html>
複製程式碼
const http = require('http');
let userInput = '';
function handleReequest (req, res) {
const method = req.method;
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
if (method === 'POST' && req.url === '/save') {
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
if (body) {
userInput = body;
}
res.end();
});
} else {
res.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8' });
res.write(userInput);
res.end();
}
}
// 啟http服務
const server = http.createServer((req, res)=> {
handleReequest(req, res)
});
server.listen('8000');
複製程式碼
3. 基於DOM
基於DOM或本地的XSS其實是一種特殊型別的反射型XSS,它是基於DOM文件物件模型的一種漏洞。可以通過DOM來動態修改頁面內容,從客戶端獲取DOM中的資料並在本地執行。基於這個特性,就可以利用JS指令碼來實現XSS漏洞的利用。
<body>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
const input = document.getElementById('input');
const btn = document.getElementById('btn');
const div = document.getElementById('div');
let val;
input.addEventListener('change', (e) => {
val = e.target.value;
}, false);
btn.addEventListener('click', () => {
div.innerHTML = `<a href=${val}>Try Me~</a>`
}, false);
</script>
</body>
複製程式碼
總結: XSS攻擊的本質就是,利用一切手段在目標使用者的瀏覽器中執行攻擊指令碼。
防範: 對於一切使用者的輸入、輸出、客戶端的輸出內容視為不可信,在資料新增到DOM或者執行了DOM API的時候,我們需要對內容進行HtmlEncode或JavaScriptEncode,以預防XSS攻擊。
現在主流的瀏覽器內建了防範 XSS 的措施,例如 內容安全策略(CSP)。但對於開發者來說,也應該尋找可靠的解決方案來防止 XSS 攻擊。
二、CSRF
CSRF(Cross-site request forgery)跨站請求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。
通常情況下,CSRF 攻擊是攻擊者藉助受害者的 Cookie 騙取伺服器的信任,可以在受害者毫不知情的情況下以受害者名義偽造請求傳送給受攻擊伺服器,從而在並未授權的情況下執行在許可權保護之下的操作。
關於瀏覽器的Cookie策略請參考HTTP Cookie。
1. 通過 Cookie 進行 CSRF 攻擊
假設有一個BBS站點http://www.a.com
:
-
當使用者登入之後,會設定如下 cookie:
res.setHeader('Set-Cookie', ['user=william; expires=Fri, 23 Mar 2019 00:00:00 GMT;'])
-
當登入後的使用者發起
http://www.a.com/delete?id=666666
請求時,會刪除 id 為 666666 的帖子。 -
CSRF攻擊者準備的網站B:
<img src="http://www.a.com/delete?id=666666">
-
當登入使用者訪問攻擊者的網站B時,會向
www.a.com
發起一個刪除使用者帖子的請求。此時若使用者在切換到www.a.com
的帖子頁面重新整理,會發現ID 為 666666 的帖子已經被刪除。
要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:
- 登入受信任網站A,並在本地生成Cookie。
- 在不登出A的情況下,訪問危險網站B。
2. CSRF攻擊的防範
1.驗證 HTTP Referer 欄位
2.新增 token 驗證
3.驗證碼
複製程式碼
儘管CSRF聽起來像跨站指令碼(XSS),但它與XSS非常不同,XSS利用站點內的信任使用者,而CSRF則通過偽裝來自受信任使用者的請求來利用受信任的網站。
與XSS攻擊相比,CSRF攻擊往往不大流行(因此對其進行防範的資源也相當稀少)和難以防範,所以被認為比XSS更具危險性,往往同XSS一同作案!
三、URL跳轉漏洞
藉助未驗證的URL跳轉,將應用程式引導到不安全的第三方區域,從而導致的安全問題。
當使用者點選後,經過伺服器或者瀏覽器解析後,將會跳到惡意的網站中。
http://a.baidu.com/index?act=go&url=http://evil.cn/
http://b.baidu.com/safecheck.html?id=1&url=http://evil.cn/
http://c.baidu.com/f/user/passport?jumpUrl=http://evil.cn/
複製程式碼
1. 實現方式
- META標籤內跳轉
- Javascript跳轉
- Header頭跳轉
通過以GET或者POST的方式接收將要跳轉的URL,然後通過上面的幾種方式的其中一種來跳轉到目標URL。一方面,由於使用者的輸入會進入Meta,javascript,http頭所以都可能發生相應上下文的漏洞,如xss等等,但是同時,即使只是對於URL跳轉本身功能方面就存在一個缺陷,因為會將使用者瀏覽器從可信的站點導向到不可信的站點,同時如果跳轉的時候帶有敏感資料一樣可能將敏感資料洩漏給不可信的第三方。
2. 防禦方案
① referer的限制
如果確定傳遞URL引數進入的來源,我們可以通過該方式實現安全限制,保證該URL的有效性,避免惡意使用者自己生成跳轉連結
② 加入有效性驗證Token
我們保證所有生成的連結都是來自於我們可信域的,通過在生成的連結里加入使用者不可控的Token對生成的連結進行校驗,可以避免使用者生成自己的惡意連結從而被利用,但是如果功能本身要求比較開放,可能導致有一定的限制。
四、ClickJacking
ClickJacking點選劫持,也叫UI覆蓋攻擊,攻擊者會利用一個或多個透明或不透明的層來誘騙使用者支援點選按鈕的操作,而實際的點選確實使用者看不到的一個按鈕,從而達到在使用者不知情的情況下實施攻擊。
1. iframe覆蓋
這種攻擊方式的關鍵在於可以實現頁中頁的<iframe>
標籤,並且可以使用css樣式表將他不可見。
防止點選劫持有兩種主要方法:
-
X-FRAME-OPTIONS
X-FRAME-OPTIONS是微軟提出的一個http響應首部,指示瀏覽器不允許從其他域進行取景,專門用來防禦利用iframe巢狀的點選劫持攻擊。並且在IE8、Firefox3.6、Chrome4以上的版本均能很好的支援。
DENY
: 拒絕任何域載入SAMEORIGIN
: 允許同源域下載入ALLOW-FROM
: 可以定義允許frame載入的頁面地址 -
頂層判斷
在UI中採用防禦性程式碼,以確保當前幀是最頂層的視窗,如:
top != self || top.location != self.location || top.location != location
2. 圖片覆蓋
圖片覆蓋攻擊(Cross Site Image Overlaying),攻擊者使用一張或多張圖片,利用圖片的style或者能夠控制的CSS,將圖片覆蓋在網頁上,形成點選劫持。當然圖片本身所帶的資訊可能就帶有欺騙的含義,這樣不需要使用者點選,也能達到欺騙的目的。
<a href="http://www.a.com/delete?id=666666">
<img src="~~~" style="~~~" />
</a>
複製程式碼
解決方案: 在防禦圖片覆蓋攻擊時,需要檢查使用者提交的HTML程式碼中,img標籤的style屬性是否可能導致浮出。
五、SQL Injection
SQL 注入漏洞(SQL Injection)是 Web 開發中最常見的一種安全漏洞。可以用它來從資料庫獲取敏感資訊,或者利用資料庫的特性執行新增使用者,匯出檔案等一系列惡意操作,甚至有可能獲取資料庫乃至系統使用者最高許可權。
1. 原理
SQL隱碼攻擊指的是通過構建特殊的輸入作為引數傳入Web應用程式,而這些輸入大都是SQL語法裡的一些組合,通過執行SQL語句進而執行攻擊者所要的操作,其主要原因是程式沒有細緻地過濾使用者輸入的資料,致使非法資料侵入系統。
根據相關技術原理,SQL隱碼攻擊可以分為平臺層注入和程式碼層注入。前者由不安全的資料庫配置或資料庫平臺的漏洞所致;後者主要是由於程式設計師對輸入未進行細緻地過濾,從而執行了非法的資料查詢。基於此,SQL隱碼攻擊的產生原因通常表現在以下幾方面:
① 不當的型別處理;
② 不安全的資料庫配置;
③ 不合理的查詢集處理;
④ 不當的錯誤處理;
⑤ 轉義字元處理不合適;
⑥ 多個提交處理不當。
2. 攻擊
當應用程式使用輸入內容來構造動態sql語句以訪問資料庫時,會發生sql注入攻擊。如果程式碼使用儲存過程,而這些儲存過程作為包含未篩選的使用者輸入的字串來傳遞,也會發生sql注入。sql注入可能導致攻擊者使用應用程式登陸在資料庫中執行命令。相關的SQL隱碼攻擊可以通過測試工具pangolin進行。
3. 防護
① 永遠不要信任使用者的輸入。對使用者的輸入進行校驗,可以通過正規表示式,或限制長度;對單引號和雙"-"進行轉換等。
② 永遠不要使用動態拼裝sql,可以使用引數化的sql或者直接使用存詢存取。
③ 永遠不要使用管理員許可權的資料庫連線,為每個應用使用單獨的許可權有限的資料庫連線。
④ 不要把機密資訊直接存放,加密或者hash掉密碼和敏感的資訊。
⑤ 應用的異常資訊應該給出儘可能少的提示,最好使用自定義的錯誤資訊對原始錯誤資訊進行包裝。
⑥ sql注入的檢測方法一般採取輔助軟體或網站平臺來檢測,軟體一般採用sql注入檢測工具jsky,網站平臺就有億思網站安全平臺檢測工具MDCSOFT SCAN等。採用MDCSOFT-IPS可以有效的防禦SQL隱碼攻擊,XSS攻擊等。
六、OS命令注入
OS 注入攻擊是指程式提供了直接執行 Shell 命令的函式的場景,當攻擊者不合理使用,且開發者對使用者引數未考慮安全因素的話,就會執行惡意的命令呼叫,被攻擊者利用。
OS 命令注入其實原理和 SQL 注入是類似的,只是場景不一樣而已。
在 Node.js 中可以使用 exec()
執行命令通過傳入一段字串命令,並把一個錯誤或命令處理結果回傳至回撥函式中。
1. exec
let userInput = "user input";
child_process.exec('ls -l ' + userInput, (err, data) => {
console.log(data);
});
複製程式碼
攻擊者可以使用一個分號";"來結束命令,並開始一個新的呼叫,他們可以使用反引號或$()來執行子命令。還有很多潛在的濫用。
在child_process.exec引擎下,將呼叫執行"/bin/sh"。而不是目標程式。已傳送的命令只是被傳遞給一個新的"/bin/ sh'程式來執行shell。 child_process.exec的名字有一定誤導性 - 這是一個bash的直譯器,而不是啟動一個程式。這意味著,如果直接執行使用者輸入的引數,所有的shell字元可能會產生毀滅性的後果。
2. execFile/spawn
在 Node.js 中除了 exec() 之外,還有 execFile() 和 spawn() 兩個方法也可以用來執行系統命令。它們和 exec() 的區別是後者是直接將一個命令字串傳給 /bin/sh 執行,而前者是提供了一個陣列作為引數容器,最後引數會被直接傳到 C 的命令執行方法 execve() 中,不容易執行額外的引數。
<!--child_process.execFile -->
let path = "user input";
child_process.execFile('/bin/ls', ['-l', path], (err, result) => {
console.log(result)
});
複製程式碼
<!--child_process.spawn-->
let path = "user input";
let ls = child_process.spawn('/bin/ls', ['-l', path])
ls.stdout.on('data', data => {
console.log(data.toString());
});
複製程式碼
注意:
使用spawn或execFile並不總是安全的。例如,執行/bin/find,並傳入使用者輸入引數仍有可能導致系統被攻陷。 find命令有一些選項,允許讀/寫任意檔案。
- 避免使用child_process.exec,當需要包含使用者輸入的引數時更是如此,請牢記。
- 儘量避免讓使用者傳入引數,使用選擇項比讓使用者直接輸入字串要好得多。
- 必須允許使用者輸入引數的情況下,請廣泛參考該命令的引數,確定哪些選項是安全的,並建立一個白名單。
更多文章在我的個人部落格www.williamife.com等你喲~
Thanks.