访问控制

“权限”一词在安全领域出现的频率很高。“权限”实际上是一种“能力”。对于权限的合理分配,一直是安全设计中的核心问题。但“权限”一词的中文含义过于广泛,因此本章中将使用“访问控制”代替。在互联网安全领域,尤其是Web安全领域中,“权限控制”的问题都可以归结为“访问控制”的问题,这种描述也更精确一些。

What Can I Do?

在上一章中,我们曾指出“认证(Authentica-tion)”与“授权(Authorization)”的不同。“认证”解决了“Who am I?”的问题,而“授权”则解决了“What can I do?”的问题。

权限控制,或者说访问控制,广泛应用于各个系统中。抽象地说,都是某个主体(subject)对某个客体(object)需要实施某种操作(operation),而系统对这种操作的限制就是权限控制。

在网络中,为了保护网络资源的安全,一般是通过路由设备或者防火墙建立基于IP的访问控制。这种访问控制的“主体”是网络请求的发起方(比如一台PC),“客体”是网络请求的接收方(比如一台服务器),主体对客体的“操作”是对客体的某个端口发起网络请求。这个操作能否执行成功,是受到防火墙ACL策略限制的。

防火墙的ACL策略面板

在操作系统中,对文件的访问也有访问控制。此时“主体”是系统的用户,“客体”是被访问的文件,能否访问成功,将由操作系统给文件设置的ACL(访问控制列表)决定。比如在Linux系统中,一个文件可以执行的操作分为“读”、“写”、“执行”三种,分别由r、w、x表示。这三种操作同时对应着三种主体:文件拥有者、文件拥有者所在的用户组、其他用户。主体、客体、操作这三者之间的对应关系,构成了访问控制列表。

Linux的文件权限

在一个安全系统中,确定主体的身份是“认证”解决的问题;而客体是一种资源,是主体发起的请求的对象。在主体对客体进行操作的过程中,系统控制主体不能“无限制”地对客体进行操作,这个过程就是“访问控制”。

主体“能够做什么”,就是权限。权限可以细分成不同的能力(capability)。在Linux的文件系统中,将权限分成了“读”、“写”、“执行”三种能力。用户可能对某个文件拥有“读”的权限,但却没有“写”的权限。

在Web应用中,根据访问客体的不同,常见的访问控制可以分为“基于URL的访问控制”、“基于方法(method)的访问控制”和“基于数据的访问控制”。

一般来说,“基于URL的访问控制”是最常见的。要实现一个简单的“基于URL的访问控制”,在基于Java的Web应用中,可以通过增加一个fil-ter实现,如下:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
// 获取访问功能 String url=request.getRequestPath(); // 进行权限验证 User user=request.getSession().get("user"); boolean permit=PrivilegeManager.permit( user, url ); if( permit ) { chain.doFilter( request, response ); } else { // 可以转到提示界面 }

当访问控制存在缺陷时,会如何呢?我们看看下面这些真实的案例,这些案例来自漏洞披露平台WooYun。

凤凰网分站后台某页面存在未授权访问漏洞,导致攻击者可以胡乱修改节目表:

凤凰网分站的后台

mop后台管理系统未授权访问:

mop后台

网易某分站后台存在未授权访问:

网易某分站的后台

酷6网某活动用户审核页面未授权访问:

酷6网后台

在正常情况下,管理后台的页面应该只有管理员才能够访问。但这些系统未对用户访问权限进行控制,导致任意用户只要构造出了正确的URL,就能够访问到这些页面。

在正常情况下,这些管理页面是不会被链接到前台页面上的,搜索引擎的爬虫也不应该搜索到这些页面。但是把需要保护的页面“藏”起来,并不是解决问题的办法。攻击者惯用的伎俩是使用一部包含了很多后台路径的字典,把这些“藏”起来的页面扫出来。比如上面的4个案例中,有3个其管理URL中都包含了“admin”这样的敏感词。而“admin”这个词,必然会被收录在任何一部攻击的字典中。

在这些案例的背后,其实只需要加上简单的“基于页面的访问控制”,就能解决问题了。下面我们将探讨如何设计一个访问控制系统。

垂直权限管理

访问控制实际上是建立用户与权限之间的对应关系,现在应用广泛的一种方法,就是“基于角色的访问控制(Role-Based Access Control)”,简称RBAC。

RBAC事先会在系统中定义出不同的角色,不同的角色拥有不同的权限,一个角色实际上就是一个权限的集合。而系统的所有用户都会被分配到不同的角色中,一个用户可能拥有多个角色,角色之间有高低之分(权限高低)。在系统验证权限时,只需要验证用户所属的角色,然后就可以根据该角色所拥有的权限进行授权了。

Spring Security中的权限管理,就是RBAC模型的一个实现。Spring Security基于SpringMVC框架,它的前身是Acegi,是一套较为全面的Web安全解决方案。在Spring Security中提供了认证、授权等功能。在这里我们只关注Spring Secu-rity的授权功能。

Spring Security提供了一系列的“FilterChain”,每个安全检查的功能都会插入在这个链条中。在与Web系统集成时,开发者只需要将所有用户请求的URL都引入到Filter Chain即可。

Spring Security提供两种权限管理方式,一种是“基于URL的访问控制”,一种是“基于method的访问控制”。这两种访问控制都是RBAC模型的实现,换言之,在Spring Security中都是验证该用户所属的角色,以决定是否授权。

对于“基于URL的访问控制”,Spring Secu-rity使用配置文件对访问URL的用户权限进行设定,如下:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
<sec:http> <sec:intercept-url pattern="/ president_portal.do**" access="ROLE_PRESIDENT" /> <sec:intercept-url pattern="/ manager_portal.do**" access="ROLE_MANAGER" /> <sec:intercept-url pattern="/**" access="ROLE_USER" /> <sec:form-login /> <sec:logout /> </sec:http>

不同的URL对于能访问其的角色有着不同的要求。

Spring Security还支持“基于表达式的访问控制”,这使得访问控制的方法更加灵活。

1 2 3 4 5 6 7
<http use-expressions="true"> <intercept-url pattern="/admin*" access="hasRole('admin') and public List<Contact> getAll();

虽然Spring Security的权限管理功能非常强大,但它缺乏一个管理界面可供用户灵活配置,因此每次调整权限时,都需要重新修改配置文件或代码。而其配置文件较为复杂,学习成本较高,维护成本也很高。

除了Spring Security外,在PHP的流行框架“Zend Framework”中,使用的Zend ACL实现了一些基础的权限管理。

不同于Spring Security使用配置文件管理权限,Zend ACL提供的是API级的权限框架。其实现方式如下:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
$acl = new Zend_Acl(); $acl->addRole(new Zend_Acl_Role('guest')) ->addRole(new Zend_Acl_Role('member')) ->addRole(new Zend_Acl_Role('admin')); $parents = array('guest', 'member', 'admin'); $acl->addRole(new Zend_Acl_Role('someUser'), $parents); $acl->add(new Zend_Acl_Resource('someResource')); $acl->deny('guest', 'someResource'); $acl->allow('member', 'someResource'); echo $acl->isAllowed('someUser', 'someResource') ? 'allowed' :'denied'; $acl = new Zend_Acl();

权限管理其实是业务需求上的一个问题,需要根据业务的不同需求来实现不同的权限管理。因此很多时候,系统都需要自己定制权限管理。定制一个简单的权限管理系统,不妨选择RBAC模型作为依据。

这种基于角色的权限管理(RBAC模型),我们可以称之为“垂直权限管理”。

不同角色的权限有高低之分。高权限角色访问低权限角色的资源往往是被允许的,而低权限角色访问高权限角色的资源往往则被禁止。如果一个本属于低权限角色的用户通过一些方法能够获得高权限角色的能力,则发生了“越权访问”。

在配置权限时,应当使用“最小权限原则”,并使用“默认拒绝”的策略,只对有需要的主体单独配置“允许”的策略。这在很多时候能够避免发生“越权访问”。

水平权限管理

在上节中提到权限管理其实是一个业务需求,而业务是灵活多变的,那么“垂直权限管理”是否够用呢?答案是否定的。我们看几个真实的案例。

优酷网用户越权访问问题(漏洞编号wooyun-2010-0129)

用户登录后,可以通过以下方式查看他人的来往信件(只要更改下面地址的数字id即可),查看和修改他人的专辑信息。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
http://u.youku.com/my_mail/ type_read_ref_inbox_id_52379500_desc_1? __rt=1&__ro=myInboxList http://u.youku.com/my_mail/ type_read_ref_outbox_id_52380790_desc_1? __rt=1&__ro=myOutboxList http://u.youku.com/my_video/ type_editfolder_step_1_id_4774704? __rt=1&__ro=myPlaylistList

漏洞分析:URL经过rewrite后将参数映射成URL路径,但这并不妨碍通过修改用户id来实现攻击。在这里,id代表资源的唯一编号,因此通过篡改id,就能改变要访问的资源。而优酷网显然没有检查这些资源是否属于当前用户。

来伊份购物网站越权访问问题(漏洞编号wooyun-2010-01576)

来伊份购物网站没有对用户进行权限控制,通过变化URL中的id参数即可查看对应id的个人姓名、地址等隐私信息。

获取他人敏感信息的请求过程

漏洞分析:同样的,id是用户的唯一标识,修改id即可修改访问的目标。网站后台应用并未判断资源是否属于当前用户。

从这两个例子中我们可以看到,用户访问了原本不属于他的数据。用户A与用户B可能都属于同一个角色RoleX,但是用户A与用户B都各自拥有一些私有数据,在正常情况下,应该只有用户自己才能访问自己的私有数据。

但是在RBAC这种“基于角色的访问控制”模型下,系统只会验证用户A是否属于角色RoleX,而不会判断用户A是否能访问只属于用户B的数据DataB,因此,发生了越权访问。这种问题,我们就称之为“水平权限管理问题”。

水平权限管理问题示意图

相对于垂直权限管理来说,水平权限问题出在同一个角色上。系统只验证了能访问数据的角色,既没有对角色内的用户做细分,也没有对数据的子集做细分,因此缺乏一个用户到数据之间的对应关系。由于水平权限管理是系统缺乏一个数据级的访问控制所造成的,因此水平权限管理又可以称之为“基于数据的访问控制”。

在今天的互联网中,垂直权限问题已经得到了普遍的重视,并已经有了很多成熟的解决方案。但水平权限问题却尚未得到重视。

首先,对于一个大型的复杂系统来说,难以通过扫描等自动化测试方法将这些问题全部找出来。

其次,对于数据的访问控制,与业务结合得十分紧密。有的业务有数据级访问控制的需求,有的业务则没有。要理清楚不同业务的不同需求,也不是件容易的事情。

最后,如果在系统已经上线后再来处理数据级访问控制问题,则可能会涉及跨表、跨库查询,对系统的改动较大,同时也可能会影响到性能。

这种种原因导致了现在数据级权限管理并没有很通用的解决方案,一般是具体问题具体解决。一个简单的数据级访问控制,可以考虑使用“用户组(Group)”的概念。比如一个用户组的数据只属于该组内的成员,只有同一用户组的成员才能实现对这些数据的操作。

此外,还可以考虑实现一个规则引擎,将访问控制的规则写在配置文件中,通过规则引擎对数据的访问进行控制。

水平权限管理问题,至今仍然是一个难题——它难以发现,难以在统一框架下解决,在未来也许会有新的技术用以解决此类问题。

OAuth简介

OAuth是一个在不提供用户名和密码的情况下,授权第三方应用访问Web资源的安全协议。OAuth 1.0于2007年12月公布,并迅速成为了行业标准(可见不同网站之间互通的需求有多么的迫切)。2010年4月,OAuth 1.0正式成为了RFC5849。

OAuth 与 OpenID都致力于让互联网变得更加的开放。OpenID解决的是认证问题,OAuth则更注重授权。认证与授权的关系其实是一脉相承的,后来人们发现,其实更多的时候真正需要的是对资源的授权。

OAuth委员会实际上是从OpenID委员会中分离出来的(2006年12月),OAuth的设计原本想弥补OpenID中的一些缺陷或者说不够方便的地方,但后来发现需要设计一个全新的协议。

OAuth产生的背景

常见的应用OAuth的场景,一般是某个网站想要获取一个用户在第三方网站中的某些资源或服务。

比如在人人网上,想要导入用户MSN里的好友,在没有OAuth时,可能需要用户向人人网提供MSN用户名和密码。

人人网要求用户输入MSN密码

这种做法使得人人网会持有用户的MSN账户和密码,虽然人人网承诺持有密码后的安全,但这其实扩大了攻击面,用户也难以无条件地信任人人网。

而OAuth则解决了这个信任的问题,它使得用户在不需要向人人网提供MSN用户名和密码的情况下,可以授权MSN将用户的好友名单提供给人人网。

在OAuth 1.0中,涉及3个角色,分别是:

1 2 3 4 5
Consumer:消费方(Client) Service Provider:服务提供方(Server) User:用户(Resource Owner)

在新版本的OAuth中,又被称为Client、Server、Resource Owner。在上面的例子中,Client是人人网,Server是MSN,Resource Owner是用户。

我们再来看一个实际场景。假设Jane在faji.com上有两张照片,她想将这两张照片分享到beppa.com,通过OAuth,这个过程是如何实现的呢?

Jane在beppa.com上,选择要从faji.com上分享照片。

在beppa.com后台,则会创建一个临时凭证(Temporary Credentials),稍后Jane将持此临时凭证前往faji.com。

然后页面跳转到faji.com的OAuth页面,并要求Jane登录。注意,这里是在faji.com上登录!

登录成功后,faji.com会询问Jane是否授权beppa.com访问Jane在faji.com里的私有照片。

如果Jane授权成功(点击“Approve”按钮),faji.com会将Jane带来的临时凭证(Temporary Credentials)标记为“Jane已经授权”,同时跳转回beppa.com,并带上临时凭证(Temporary Cre-dentials)。凭此,beppa.com知道它可以去获取Jane的私有照片了。

对于beppa.com来说,它首先通过RequestToken去faji.com换取Access Token,然后就可以用Access Token访问资源了。Request Token只能用于获取用户的授权,Access Token才能用于访问用户的资源。

最终,Jane成功地将她的照片从faji.com分享到beppa.com上。

我们也可以参考如下新浪微博开放平台的OAuth的授权过程,它与上面描述的过程是一样的。新浪微博的OAuth使用过程

OAuth的发展道路并非一帆风顺,OAuth 1.0也曾经出现过一些漏洞,因此OAuth也出过几个修订版本,最终才在2010年4月定稿OAuth 1.0为 RFC 5849,在这个版本中,修复了所有已知的安全问题,并对实现OAuth协议需要考虑的安全因素给出了建议。OAuth标准中的安全建议

事实上,自己完全实现一个OAuth协议对于中小网站来说并没有太多的必要,且OAuth涉及诸多加密算法、伪随机数算法等容易被程序员误用的地方,因此使用第三方实现的OAuth库也是一个较好的选择。目前有以下这些比较知名的OAuth库可供开发者选择:

  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101
ActionScript/Flash oauth-as3 http://code.google.com/p/oauth-as3/ A flex oauth client http://www.arcgis.com/home/item.html? id=ff6ffa302ad04a7194999f2ad08250d7 C/C++ QTweetLib http://github.com/minimoog/ QTweetLib libOAuth http://liboauth.sourceforge.net/ clojure clj-oauth http://github.com/mattrepl/clj- oauth .net oauth-dot-net http://code.google.com/p/oauth- dot-net/ DotNetOpenAuth http://www.dotnetopenauth.net/ Erlang erlang-oauth http://github.com/tim/erlang- oauth Java Scrible http://github.com/fernandezpablo85/ scribe-java oauth-signpost http://code.google.com/p/ oauth-signpost/ JavaScript oauth in js http://oauth.googlecode.com/svn/ code/javascript/ Objective-C/Cocoa & iPhone programming OAuthCore http://bitbucket.org/atebits/ oauthcore MPOAuthConnection http://code.google.com/p/ mpoauthconnection/ Objective-C OAuth http://oauth.googlecode.com/svn/code/obj-c/ Perl Net::OAuth http://oauth.googlecode.com/svn/ code/perl/ PHP tmhOAuth http://github.com/themattharris/ tmhOAuth oauth-php http://code.google.com/p/oauth-php/ Python python-oauth2 http://github.com/brosner/ python-oauth2 Qt qOauth http://github.com/ayoy/qoauth Ruby Oauth ruby gem http://oauth.rubyforge.org/ Scala DataBinder Dispatch http://dispatch.databinder.net/About

OAuth 1.0已经成为了RFC标准,但OAuth2.0仍然在紧锣密鼓的制定中,到2011年年底已经有了一个较为稳定的版本。

OAuth 2.0吸收了OAuth 1.0的经验,做出了很多调整。它大大地简化了流程,改善了用户体验。两者并不兼容,但从流程上看区别不大。

常见的需要用到OAuth的地方有桌面应用、手机设备、Web应用,但OAuth 1.0只提供了统一的接口。这个接口对于Web应用来说尚可使用,但手机设备和桌面应用用起来则会有些别扭。同时OAuth 1.0的应用架构在扩展性方面也存在一些问题,当用户请求数庞大时,可能会遇到一些性能瓶颈。为了改变这些问题,OAuth 2.0应运而生。

小结

在本章中,介绍了安全系统中的核心:访问控制。访问控制解决了“What Can I Do?”的问题。

还分别介绍了“垂直权限管理”,它是一种“基于角色的访问控制”;以及“水平权限管理”,它是一种“基于数据的访问控制”。这两种访问控制方式,在进行安全设计时会经常用到。

访问控制与业务需求息息相关,并非一个单纯的安全问题。因此在解决此类问题或者设计权限控制方案时,要重视业务的意见。

最后,无论选择哪种访问控制方式,在设计方案时都应该满足“最小权限原则”,这是权限管理的黄金法则。

浙ICP备11005866号-12