在讲解Jenkins的跨站请求伪造(CSRF)保护机制之前,让我们首先对CSRF这一安全威胁及其重要性进行简明扼要的概述。
CSRF(即跨站请求伪造)是指利用受害者尚未失效的身份认证信息、(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害人的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(转账,改密码等)。CSRF属于业务逻辑漏洞,在服务器看来,所有请求都是合法正常的(请求是经过身份认证的)。
因为CSRF攻击,会重复利用用户的 Cookie,而说到 Cookie,就得先从 HTTP 协议开始讲起。
无记忆性:
HTTP是一种无状态协议,即服务器不会保留与客户交易时的状态。
用户A在很短的时间间隔内向Web服务器发送了两次同样的请求,服务器并不会因为已经响应了该请求一次就不对第二次请求进行响应,因为服务器并不知道已经响应过一次该请求。
假设用户在网站A的某一个页面上已经完成了登录操作,当在该网站的另一个页面上执行的操作需要验证用户登录的时候仍然需要用户再次登录,因为HTTP并不知道你已经登录了,它不会维持你的登录状态。
因为要多次登录太过麻烦,为了让服务器能够记住用户引入了 Cookie 机制。
当用户访问站点的时候,站点会为该用户分配一个 Cookie 值,站点使用该 Cookie 值来标记用户,当用户浏览器接受到包含 Cookie 值得数据包后,会将 Cookie 得值取出,存放到浏览器中,随后浏览器会在发往该站点得数据包中自动得填充该 Cookie 值。Cookie 的值的填充是浏览器的行为。
当浏览器自动完成Cookie的填充,目标网站会误认为该数据包就是管理员发送的,会以管理员的权限进行相关的操作。
之所以会出现 CSRF 的攻击,本质原因是黑客可以伪造用户的请求,用户的请求信息实际上是存在 Cookie 中的,所以黑客可以在不知道上述那些验证信息直接跳过安全验证。所以防御 CSRF 的关键点在于当用户的请求在发出的时候,黑客不能去伪造这个信息,并且这个信息不能存在于 cookie 之中。
Referer 是存在于 HTTP 报文头部的一个字段,它是由浏览器提供的,可以记录当前请求的来源地址(标明请求的来源)。黑客在伪造请求时只能在自己的网站上构造请求,这样的话服务器验证 Referer 就可以知道这个请求并不是自己网站内部的请求,而是黑客伪造的,直接拒绝。以百度里面的一个文件为例:
使用这种方式的好处是简单、方便,一般的开发人员就不用去操心 CSRF 的安全性漏洞了,只需要在服务端加一个拦截器,去验证请求的 Referer 值就可以了。这种验证 Referer 的方式固然简单高效,但是也并不是万无一失的,虽然说 Referer 的值是由浏览器提供的,但是在某些浏览器上黑客是可以在发送请求时去篡改 Referer 或其他 Header 的值的,这样的话就相当于跳过了验证,从而又可以进行 CSRF 攻击了。
Anti CSRF Token。Token,就是令牌,最大的特点就是随机性,不可预测。
Anti CSRF Token 原理上是通过 Session Token 来实现的。当客户端请求页面时,服务器会生成一个随机数 Token,并且将 Token 放置到 Session 当中,然后将 Token 发给客户端(一般通过构造 Hidden 表单)。下次客户端提交请求时,Token 会随着表单一起提交到服务器端,服务器端会在收到请求后用拦截器去对 Token 的值进行验证,判断是否和 Session 中的 Token 值相等,若相等,则可以证明请求有效,不是伪造的,如果没有Token或者Token不正确都会被认为是攻击而直接丢弃。
GET请求
http://url?csrftoken=tokenvalue
POST请求
<input type="hidden" name="csrftoken" value="tokenvalue" />
1.2.3 用户验证
对于敏感操作,可以要求用户进行额外的身份验证(例如输入密码或验证码)。
1.2.4 避免使用 GET 请求进行状态改变
确保状态改变操作(例如数据修改、删除)使用 POST、PUT 或 DELETE 方法,而不是 GET 方法。GET 请求应只用于数据获取操作。
Jenkins的CSRF保护使用Token(在Jenkins里叫Crumb),它由Jenkins创建,并发送给用户。任何导致修改的表单提交或者类似的操作,比如:触发任务或修改构建配置,都需要提供Crumb。Crumb包含标识创建它的用户的信息,因此使用其他用户令牌的提交将被拒绝。所有这些都发生在后台,除了极少数情况外,没有明显的影响,例如,在用户的会话到期后,他们再次登录。
Dashboard » Manage Jenkins » Security 中,管理员可以配置 CSRF Protection。另外,从Jenkins 2.214和Jenkins LTS 2.222.1开始,Jenkins默认启用跨站请求伪造保护功能,并且在Jenkins图形化页面不允许关闭。
默认启用的The *Default Crumb Issuer, 这个拦截器会计算客户端传递的Crumb值的hash是否是可用hash, 这个hash的来源是通过用户名、sessionID、请求IP以及访问的jenkins的唯一标识生成的。
唯一支持的选项“启用代理兼容”会从令牌中删除有关用户IP地址的信息。当Jenkins在反向代理后面运行并且从Jenkins看到的用户IP地址会定期更改时,这可能很有用。
注意 1:默认的Crumb Issuer生成Crumb的哈希,并在其中编码以下的信息(如果启用代理兼容,生成的Crumb哈希时不再使用请求Ip):
- 为其生成Crumb的用户名
- 生成Crumb的网络会话 ID
- 为其生成Crumb的用户的 IP 地址
- 此 Jenkins 实例独有的salt
当Crumb被发送回 Jenkins 时,所有这些信息都需要匹配,以便该提交被视为有效。
注意 2:CrumbIssuer是一个为了抵御CSRF攻击而生成名为Crumb值的算法。Crumb通常是对能够唯一标识发送请求的代理(用户端)的信息的hash值,并且其会被加密以防第三方伪造。Crumb值由服务器端在用户登录时生成并发送给用户,之后用户每次登录时都需要对其进行验证。为了保证Crumb不会被猜测或者伪造,生成方式是获取用户信息进行hash并加密。
发给Jenkins的POST请求通常需要提供Crumb。这也适用于使用用户名和密码进行身份验证的脚本客户端。由于 Crumb 包含网络会话 ID,客户端需要执行以下操作:
示例(禁用流水线项目test-crumb):
通过API禁用流水线项目前先查看流水线状态。
通过API禁用流水线:
curl -X POST -u zmc:123456 "http://10.20.31.153:8080/jenkins/job/test-crumb/disable"
报403错误(鉴权失败),并且日志给出了明确的报错原因,请求里面缺少合法的Crumb。在第二章节介绍Jenkins跨站请求伪造保护功能时已经讲过任何导致修改的表单提交或者类似的操作,都需要提供Crumb。
那么我们就为接口提供Crumb值,通过API获取当前用户的Crumb。
curl -s -u zmc:123456 http://10.20.31.153:8080/jenkins/crumbIssuer/api/json
接下来请求头里面带着当前用户的Crumb值,再次禁用流水线。
curl -X POST -u zmc:123456 -H "Jenkins-Crumb:98faf768c7d636709ae6e328e7344366614d68db62dcaacf8737c58278cfa71c" "http://10.20.31.153:8080/jenkins/job/ssssss/disable"
还是报缺少合法的Crumb错误,这是因为除了提供用户名、密码、Crumb之外还需要填写会话cookie(curl客户端工具不是浏览器不会在请求里面自动带着cookie值),再次通过API获取当前用户的Crumb。
curl -s -verbose -u zmc:123456 http://10.20.31.153:8080/jenkins/crumbIssuer/api/json
接下来请求头里面带着当前用户的Crumb值、会话cookie,再次禁用流水线。
curl -X POST -u zmc:123456 --cookie "JSESSIONID=8A381340DD8DFC299F9A88BBE10880E0" -H "Jenkins-Crumb:a849f65adaf2ecf8ecf95bf957c1b1288fe86bf091af85a1e7d17421525bb8fc" "http://10.20.31.153:8080/jenkins/job/test-crumb/disable"
查看流水线项目状态,项目已禁用。
另外,我们知道Jenkins API认证方式有用户名/密码+用户名/Token两种形式,对于用户名/密码认证的Jenkins任何导致修改的表单提交或者类似的请求,都需要带着 Crumb,而对于用户名/API Token的话,即使Jenkins打开了CSRF保护,也不需要提供Crumb。
示例(启用流水线项目test-crumb):
通过API Token启用上面禁用的流水线项目。
curl -X POST -u zmc:116e373b20b15ca5788b2a37044f4cb0b5 "http://10.20.31.153:8080/jenkins/job/test-crumb/enable"
查看流水线项目状态,项目已启用。
注意 1:本文主要讲解Jenkins如何使用CrumbIssuer防御CSRF攻击,关于API认证如何颁发API Token本文不做讲解。
注意 2:Crumb的有效期通常与用户会话绑定,只要会话有效,Crumb就有效。jenkins本身并没有直接配置会话超时时间的设置,它依赖于底层的Servlet容器的会话配置,假设底层容器用的Tomcat的话,默认会话有效期是30分钟。
向Jenkins发送HTTP请求的过时插件可能无法在启用CSRF保护的情况下工作。在这种情况下,可能需要暂时禁用CSRF保护。要禁用CSRF保护,请设置系统属性hudson.security.CSRF.GlobalCrumbIssuerConfiguration。启动时将DISABLE_CSRF_PROTECTION设置为true。
Jenkins的CSRF保护使用Crumb,它由Jenkins创建,并发送给用户。任何导致修改的表单提交或者类似的操作,比如:触发任务或修改构建配置,都需要提供Crumb。Crumb包含标识创建它的用户的信息,因此使用其他用户令牌的提交将被拒绝。所有这些都发生在后台,除了极少数情况外,没有明显的影响,例如,在用户的会话到期后,他们再次登录。
发给Jenkins的POST请求通常需要提供Crumb。这也适用于使用用户名和密码进行身份验证的脚本客户端。由于 Crumb 包含网络会话 ID,客户端需要执行以下操作:
另外,我们知道Jenkins API认证方式有用户名/密码+用户名/Token两种形式,对于用户名/密码认证的Jenkins任何导致修改的表单提交或者类似的请求,都需要带着 Crumb,而对于用户名/API Token的话,即使Jenkins打开了CSRF保护,也不需要提供Crumb。