6.2 生成会话令牌过程中的漏洞
很多时候,应用程序的安全取决于令牌生成过程中的不可预知性。由于生成令牌过程中存在漏洞,攻击者能够确定发布给其他用户的令牌,达到攻击会话管理机制的目的。令牌的种类有“记住我”功能使用的永久令牌、用于一次性访问受保护的资源的令牌、发送到用户注册的电子邮件地址的恢复密码令牌、隐藏表单字段中用于防止跨站点请求伪造攻击的令牌、未使用验证的购物应用程序的消费者用于检索现有订单的当前状态的令牌等几种。
由于当前的许多应用程序都采用成熟的会话令牌生成机制,我们可以在这些功能区域中发现有关令牌生成过程中的漏洞。
6.2.1 结构化令牌的组成存在漏洞
含有含义数据的令牌通常表现出某种结构,也就是说,它们由几种成分组成,通常以分隔符隔开,攻击者可分别提取并分析这些成分,以了解它们的功能和生成方法。
结构化令牌的组成成分包括以下几项。
(1)账户用户名。
(2)应用程序用来区分账户的数字标识符。
(3)用户姓名中的名/姓。
(4)用户的电子邮件地址。
(5)用户在应用程序中所属的组或扮演的角色。
(6)日期/时间戳。
(7)一个递增或可预测的数字。
(8)客户的IP地址。
从应用程序中获取一个令牌,对其进行系统化的修改,以确定整个令牌是否有效,或者令牌的某些成分是否被忽略。尝试以一次一个字节(或者一次一个位)的方式更改令牌的值,然后重新向应用程序提交修改后的令牌,看应用程序是否仍然接受这个令牌。如果发现令牌中的某些部分实际上并无作用,可以将它们排除在深入分析之外,以减轻工作负担。
在不同时间以不同的用户登录,记录服务器发布的令牌。如果应用程序允许自我注册,可以选择自己的用户名,用一系列存在细微差别的相似用户名登录,如admin、admina、adminb、adminlogin等。如果其他与某一名用户有关的数据(如电子邮件地址)在登录阶段提交或保存在用户资料中,对其进行与前面类似的系统化修改,并记录登录后收到的令牌。
对令牌进行分析,查找任何与用户名和其他用户可控制的数据有关的内容。
分析令牌,查找任何明显的编码或模糊处理方案。如果用户名包含一组相同的字符,在令牌中寻找可能使用XOR模糊处理的对应字符序列;在令牌中寻找仅包含十六进制字符的字符序列,它表示应用程序可能对ASCII字符串进行了十六进制编码处理,或者披露其他信息。寻找以等号(=)结尾的字符序列或仅包含其他有效Base64字符的序列,如a-z、A-Z、0-9、+和/。
如果对会话令牌样本进行逆向工程可获得任何有意义的结果,看看是否拥有足够的信息可猜测出应用程序最近向其他用户发布的令牌。找到一个依赖会话的应用程序页面(即如果不使用有效会话访问,就会返回错误消息或指向其他位置的重定向页面),通过Burp Suite Free Edition之类的工具可使猜测出的令牌向该页面提出大量请求。监控页面被正确加载的所有情况的结果,这表示会话令牌有效。
6.2.2 令牌含义的可预测性
一些会话令牌并不包含与某个特殊用户有关的任何有意义的数据,但由于它们包含某种顺序或模式,允许攻击者通过几个令牌样本即可推断出应用程序最近发布的其他有效令牌,因此具有可预测性。即使推断过程需要做出大量尝试,并且成功率极低(如每1000次尝试得到一个有效令牌),自动攻击工具也仍然能够利用这种缺陷在很短的时间内确定大量有效令牌。
与定制应用程序相比,会话管理的商业应用,如Web服务器或Web应用程序平台中的令牌可预测漏洞更容易被发现。当向一个定制会话管理机制实施远程攻击时,攻击者所能获得的已发布令牌样本的数量可能受到服务器容量、其他用户的活动、带宽、网络延时等因素的限制。然而,在实验室环境中,渗透测试员可以迅速建立数百万个令牌样本,所有样本都紧密相连,并使用了时间戳,而且可以降低其他用户造成的干扰。
在最简单也是最容易受到攻击的情况下,应用程序使用一个简单的连续数字作为会话令牌。这时,攻击者只需获得两个或三个令牌样本就可以实施攻击,并立即截获当前有效的所有令牌。
在其他情况下,应用程序令牌中可能包含更加复杂的序列,需要付出一定的努力才能发现。序列的变化形式可能多种多样,但是,根据我们的行业经验,可预测的会话令牌通常来自3个方面:隐含序列、时间依赖、生成的数字随机性不强。
(1)隐含序列。
有时,对会话令牌的原始形式进行分析可能无法预测它们,但是对其进行适当解码或解译就可以揭示其中包含的序列。
(2)时间依赖。
一些Web服务器和应用程序使用时间作为令牌值的输入,通过某种算法生成会话令牌。如果没有在算法中合并足够的熵,攻击者就可能推测出其他用户的令牌。虽然任何特定的令牌序列本身是完全随机的,但是如果组合生成每个令牌的时间信息,也许可以发现某种可以辨别的模式。一个忙碌的应用程序每秒会生成大量的会话,因此实施一次自定义攻击就可以成功确定其他用户的大量令牌。
(3)生成的数字随机性不强。
计算机中的数据极少完全随机,因此如果由于某种原因需要随机数据,一般通过软件使用各种技巧生成伪随机数字。所使用的一些算法生成看似随机并且在可能的数值范围内平均分布的序列,但有些人只需要少数几个样本,仍然能够准确推导出整个序列。
有时,令牌根据一个伪随机数字发生器的输出而生成,因此开发者决定将发生器的几个连续输出连接起来建立每个令牌。开发者认为使用这种方法可建立一个更长因而“更强大”的令牌。但是这种策略通常是一种误解。如果攻击者能够获得发生器生成的几个连续输出,他们就可以通过它们推断出发生器内部状态的一些信息,因此更容易向前或向后推导发生器的输出顺序。
图6-5表示正在使用Burp Suite Free Edition软件循环访问一个会话令牌,以查找会话仍处于活动状态可被劫持的令牌值。这时,服务器响应的长度是发现有效会话的可靠指标。
图6-5 循环访问一个会话令牌
1 单击Burp Suite Free Edition软件的“intruder”按钮。
2 在弹出的选项中单击“Options”按钮。
3 单击显示窗口右上方的“Start attack”按钮,软件开始循环访问一个会话令牌。
6.2.3 对令牌的算法存在漏洞
当用户登录时,系统创建一个访问令牌,里面包含登录进程返回的Session ID和由本地安全策略分配给用户和用户的安全组的特权列表以该用户身份运行的所有进程都拥有该令牌的一个拷贝。系统使用令牌控制用户可以访问哪些安全对象,并控制用户执行相关系统操作的能力。对令牌的算法漏洞可能存在于以下4个方面。
1.没有采用强算法生成Session ID
会话ID必须具有随机性和不可预测性。一般来说,会话ID的长度至少为128位。下面就以常见的应用服务器Tomcat来说明如何配置会话ID的长度和生成算法。
首先找到{TOMCAT_HOME}\conf\context.xml,然后加入下面一段代码:
<Manager sessionIdLength="20" secureRandomAlgorithm="SHA1PRNG" secureRandomClass="java.security.SecureRandom" />
定义会话ID的长度,如果这里不声明的话,默认是16字节。可能有读者会纳闷,怎么平时我看到的会话ID都是很长的呀?我们就以这里的20个字节来讲,在浏览器发送请求时会发现这样的会话ID:
JSESSIONID=90503B6BE403D4AB6164A311E167CF1F6F3F2BD0
仔细观察会发现ID的长度为40,因为这里显示的是十六进制,每两个字符代表一个字节。
定义随机数算法,默认的是SHA1PRNG,你也可以换成自己的算法。
定义随机数类,默认的是java.security.SecureRandom,我们也可以继承这个类来实现自己的算法。但有一点要注意,在实现自己的随机数算法时,一定要保证生成的Session ID不能有重复,这里我们参考一下Tomcat实现的机制。
/** * Generate and return a new session identifier. */ protected StringgenerateSessionId() { String result = null; do { if (result ! = null) { duplicates++; } result = sessionIdGenerator.generateSessionId(); } while (sessions.containsKey(result)); return result; }
由此可见,Tomcat不会产生两个相同的会话ID。
2.没有使用“软硬兼施,会话过期”机制
会话过期是应用程序的一项重要的安全控制,它定义了用户在多长时间段内不用重新登录而仍然维持一个登录状态。一般来说,有两种会话过期:软会话过期(Soft Session Timeout)和硬会话过期(Hard Session Timeout)。
(1)软会话过期。
软会话过期指的是用户在一定的时间内与应用系统没有交互,则会话过期。例如,一个用户登录了一个应用系统,他临时离开了计算机40分钟,而应用系统设置的会话过期时间为30分钟,这时用户回到计算机前再做任何操作,系统都会重定向为登录页面让用户重新输入用户名和密码。
那么,软会话过期有什么用呢?我们知道在CSRF攻击中一个最基本的假设就是合法用户处在一个登录状态中,如果我们设置了一个合理的且较低的会话过期时间,就提高了实施CSRF攻击的难度,从而保护了系统。
通常有3种办法来设定软会话过期,其级别由高到低依次为:Tomcat级别> Web应用级别>Servlet运行时context级别,这时低级别的设定会覆盖高级别的设定。
①Tomcat级别的设定。
若需要设定30分钟的会话过期,可以在{TOMCAT_HOME}\conf\web.xml中进行如下设定:
<session-confi g> <session-timeout>30</session-timeout><! -- set in minutes --> </session-confi g>
②Web应用级别的设定。
若需要设定15分钟的会话过期,可以在{TOMCAT_HOME}\webapps\ {APP_NAME}\WEB-INF\web.xml中进行如下设定:
<session-confi g> <session-timeout>15</session-timeout><! -- set in minutes --> </session-confi g>
③在程序代码中进行设定。
若需要在程序中设定5分钟的会话过期,可以用下面一行代码来实现:
httpSession.setMaxInactiveInterval(5*60); // set in seconds
如果我们按照上面的步骤进行了会话过期设置,那么最后真正起作用的是在程序中进行设定的5分钟。
(2)硬会话过期。
硬会话过期指的是用户登录到系统中经过一定的时间后,不管用户做什么,该会话都会过期。例如,网络游戏防沉迷系统,如果未成年人的累计在线时间已满5小时,则累计在线时间清零。这与我们所说的硬会话过期很相似,只不过这里不是在线时间清零,而是强制用户退出并重新登录。
硬会话过期主要用来防止永久地对一个账号劫持。例如,一个攻击者通过XSS得到了受害者的Session,并用它冒充受害者进行登录,如果我们设定了硬会话过期,则经过了一段时间之后,系统会强制用户重新进行认证。
没有专门的API或者配置来设定硬会话过期,但我们可以通过在Web filter中输入自己的代码来实现这个功能。基本思路如下:对每个用户登录成功后记录下此时的时间,并且把这个时间与他们的Session ID绑定起来,如果用同一个Session ID发送的请求的时间减去这个Session ID刚登录成功的时间大于我们设定的会话过期时间,则使这个会话无效,并重定向到登录页面。
3.没有使用Cookies保护机制
Cookies有两个很重要的属性:secure和HttpOnly,设置好这两个属性对于保护你的Cookies至关重要。
(1)Secure属性。
声明了Secure属性,则说明当前这个Cookies只会在HTTPS的链接中进行传递,这样就使攻击者无法很容易地通过分析网络流量来获得会话ID,从而有效地防止中间人攻击(Man-in-the-Middle)。
一个好消息是,Tomcat 7支持了Servlet 3.0,所以我们可以在web.xml设定上面的两个属性。
<session-confi g> <cookies-confi g> <secure>true</secure> </cookies-confi g> <cookies-confi g> <http-only>true</http-only> </cookies-confi g> </session-confi g>
需要注意的是,Tomcat 6以前的版本不支持,Tomcat 6支持的是Servlet 2.5。
(2)HttpOnly属性。
大家知道Cookies都是通过document对象获取的,如果能让Cookies在浏览器中不可见就可以了,那么HttpOnly就是在设置Cookies时接受这样一个参数,一旦被设置,在浏览器的document对象中就看不到Cookies了。而在浏览网页时不受任何影响,因为Cookies会被放在浏览器头中发送出去(包括Ajax的时候),应用程序也一般不会在JS里操作这些敏感的Cookies。对于一些敏感的Cookies我们采用HttpOnly,对于一些需要在应用程序中用JS操作的Cookies我们就不予设置,这样就保障了Cookies信息的安全,也保证了应用。
给浏览器设置Cookies的头如下:
Set-Cookies: =[; =] [; expires=][; domain=] [; path=][; secure][; HttpOnly]
如果Cookies具有HttpOnly特性且不能通过客户端脚本访问,则为true,否则为false;默认值为false。
4.没有提供logout功能
上面介绍的是系统自动按照设定的时间使会话过期,一个好的应用程序应该提供一个功能,即用户可以手动地使当前会话过期,这就是我们在几乎所有网站上都看到的logout按钮。那么一般的logout需要完成哪些功能呢?下面我们看看ESAPI中是如何实现logout功能的。
Class: org.owasp.esapi.reference.DefaultUser public void logout() { ESAPI.httpUtilities().killCookies( ESAPI.currentRequest(), ESAPI.currentResponse(), HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME ); HttpSession session = ESAPI.currentRequest().getSession (false); if (session ! = null) { removeSession(session); session.invalidate(); } ESAPI.httpUtilities().killCookies(ESAPI.currentRequest(), ESAPI.currentResponse(), "JSESSIONID"); loggedIn = false; logger.info(Logger.SECURITY_SUCCESS, "Logout successful"); ESAPI.authenticator().setCurrentUser(User.ANONYMOUS); }
killCookies的实现代码如下:
public void killCookies(HttpServletRequest request, HttpServletResponse response, String name) { String path = "//"; String domain=""; Cookies cookies = getFirstCookies(request, name); if ( cookies ! = null ) { path = cookies.getPath(); domain = cookies.getDomain(); } Cookies deleter = new Cookies( name, "deleted"); deleter.setMaxAge( 0 ); if ( domain ! = null ) deleter.setDomain( domain ); if ( path ! = null ) deleter.setPath( path ); response.addCookies( deleter ); }
以上代码的主要功能有以下4个方面。
(1)清除remember me这个Cookies,这是针对网站有remember这个功能来说的。
(2)使得当前的会话无效,这样即使当前的会话ID泄露出去了,攻击者也无法用这个会话ID进行登录。
(3)清除JSESSIONID这个Cookies。
(4)使deleter(与传递进来的Cookies同名)立即无效。
如图6-6所示,使用Burp Suite Free Edition软件中的“位翻转程序”有效载荷类型,可以测试出应用程序是否存在算法漏洞。
图6-6 测试应用程序是否存在算法漏洞
1 单击Burp Suite Free Edition软件的“intruder”按钮。
2 单击弹出的“Positions”按钮。
3 在“Attack type”下拉列表中选择“Sniper”选项。
4 查看显示代码内容,其中的加密会话令牌被标记为有效载荷位置。