OAuth CSRF实验

实验目的

  1. 通过一个简单的OAuth CSRF攻击实验,来熟悉OAuth的协议流程;
  2. 通过攻击实验来理解OAuth协议的安全性;

实验环境

实验内容

通过OAuth CSRF攻击实验,达到如下效果:攻击者通过诱导用户点击恶意链接,使得用户的账号通过OAuth协议与攻击的IdP账号绑定在一起,从而可以使得攻击者用自己的IdP账号通过OAuth登录到用户的账号。 具体步骤如下:

  1. 攻击者登录到RP,并且选择绑定自己的IdP账号;
  2. RP将攻击者重定向到IdP,攻击者登录IdP,IdP向他显示是否授权RP访问的页面;
  3. 攻击者在点击”同意授权”之后,截获IdP服务器返回的含有Authorization code参数的HTTP响应;
  4. 攻击者精心构造一个Web页面,它会触发向IdP发起令牌申请的请求,而这个请求中的Authorization Code参数正是上一步截获到的code;
  5. 攻击者将这个Web页面放到互联网上,等待或者诱骗受害者来访问;
  6. 受害者之前登录了RP网站,只是没有把自己的账号和其他社交账号绑定起来。在受害者访问了攻击者准备的这个Web页面,令牌申请流程在受害者的浏览器里被顺利触发,RP从IdP那里获取到access_token,但是这个token以及通过它进一步获取到的用户信息却都是攻击者的;
  7. RP将攻击者的IdP账号同受害者的RP账号关联绑定起来;
  8. 从此以后,攻击者就可以用自己的IdP账号通过OAuth登录到受害者在RP中的账号,冒充受害者的身份执行各种操作。

具体的攻击流程图如下图所示:

备注:第9步中,攻击者应该获取到当前登录状态下用户的cookie,并且将cookie写入请求头中,RP会检查cookie来判断用户是否是登录状态,从而判断是否同意进行绑定请求,但目前从实现上来讲,获取cookie,并检查cookie较为困难,因此在实际实现中,cookie只是已简单的参数形式,传给RP,RP也不检查cookie;

实验步骤

环境配置没有按照实验要求,使用的是自己之前配置完成的JDK+Mysql+Apache环境,相关参数上述已经说明,但是由于环境和程序源代码出现冲突,做了一些改变,配置步骤不再赘述;

数据库创建

RP数据库

  1. 建立名为RP的数据库;

  2. 并且建立user数据表,其字段为:

    • id:编号
    • userename:RP用户名
    • password:RP密码
    • idp:身份认证提供商

IdP数据库

  1. 建立名为shiro的数据库;

  2. 建立oauth2_client数据表,其字段为:

    • id:编号
    • client_name:第三方应用名称
    • client_id:第三方应用id
    • client_secret:第三方应用密钥

  3. 建立oauth2_user表,其字段为:

    • id:编号
    • username:IdP用户名
    • password:IdP密码
    • salt:盐

检查RP数据库配置

程序中数据库配置正确;

1
2
3
4
5
public static final String driver="com.mysql.jdbc.Driver";
public static final String url="jdbc:mysql://localhost:3306/rp?characterEncoding=utf8&useSSL=true";
public static final String username="root";
public static final String password="123456";
public static Connection con=null;

检查IdP数据库配置

程序中数据库配置正确;

1
2
3
4
#dataSource configure
connection.url=jdbc:mysql://localhost:3306/shiro
connection.username=root
connection.password=toor

服务器启动

启动IdP服务器

双击jetty:run,启动服务器;

运行显示如下图,表示IdP服务器启动成功;

启动RP服务器

将点击 Edit Configurations,修改RP配置如下;

将application context置为空;

启动RP服务器,运行显示如下图,表示RP服务器启动成功;

实验流程

注册RP,获得ClientID和Secret

  1. 打开IdP网址http://localhost:8080/zetark-oauth2-server/client进行第三方应用(RP)注册;

  2. 点击应用新增,输入第三方应用RP的名称testApp

    由于未将id设置为自动递增,报错如下:

    更改数据库配置,将id设置为自动递增;

    再次点击新增应用,注册完毕获得客户端ID(client_id)88f55e00-5f78-44e1-abfb-fa6c5368366d和客户端安全KEY(clinet_secret)788639e3-8355-4fa8-aa38-ae04670ac30b,RP需要记录这两个参数,用于后续的OAuth认证,即需要在源代码中一些位置更改上述的两个参数,这里只举例一处;

攻击者:注册IdP账号

  1. 打开IdP网址http://localhost:8080/zetark-oauth2-server/user进行账号注册,该账号将用于绑定RP账号;

  2. 点击用户新增,注册用户attack

  3. 注册成功后,显示用户;

用户注册RP账号

  1. 打开RP网站http://localhost:8081/进行账号注册;

  2. 到注册页面,设置用户名为Leeyuxun,点击注册;

  3. 注册成功后,返回登录页面,进行登录;

  4. 调试窗口显示登录成功;

  5. 网页显示登陆成功;

攻击者完成攻击前的状态

  1. 在IdP账号没有绑定RP账号testApp的情况下,用IdP账号attacker来登录RP网站,点击使用IdP授权,发现IdP显示,应用testApp正在请求接入;

  2. 输入IdP账号,用户名attack,点击登录并授权,验证通过后会显示该用户未绑定。实际上attack用户只是IdP用户,并没有绑定过RP用户,因此无法用IdP的账号来登录RP网站;

攻击者的操作流程

  1. 下面进行CSRF攻击,首先让用户Leeyuxun登录到RP网站;

  2. 攻击者在用户Leeyuxun保持登录状态时,打开绑定用户的链接http://localhost:8080/zetark-oauth2-server/authorize?client_id=88f55e00-5f78-44e1-abfb-fa6c5368366d&response_type=code&redirect_uri=http://localhost:8081/accesstoken.jsp&scope=bindlogin

  3. 输入攻击者attack在IdP上的账号,点击登录并授权;

  4. 验证通过后,获得返回的授权码code,位于url地址中,为38575af7f999713cffce720e74409ad6

  5. 将code放入事先写好的攻击项目中,并build运行attcak项目;

用户登录RP并访问攻击者的网站

  1. 等待用户访问攻击者的网站,点击恶意链接http://localhost:8082/attack,攻击者伪造一个post请求,发送给RP网站http://localhost:8081/binding_request,其中post请求含有:授权码code,受害者RP的用户名rp_user_name;

  2. 当用户点击恶意链接时,当前RP用户就会与攻击者的IdP账号绑定,完成CSRF攻击;

攻击者入侵用户的RP账户

  1. 攻击者用攻击者的IdP账号成功登录用户的RP账号;

实验结论

由于OAuth2的认证流程是分为好几步来完成的,当第三方应用在收到一个GET请求时,除了能知道当前用户的cookie,以及URL中的Authorization Code之外,难以分辨出这个请求到底是用户本人的意愿,还是攻击者利用用户的身份伪造出来的请求。因此,攻击者可使用移花接木的手段,提前准备一个含有自己的Authorization Code请求,并让受害者的浏览器来接着完成后续的令牌申请流程;

攻击者攻击前提条件

  1. 在攻击过程中,受害者在RP上的用户会话(User Session)必须是有效的,即受害者在受到攻击前已经登录了RP;
  2. 整个攻击必须在短时间内完成,因为OAuth2提供者颁发的Authorization Code有效期很短,OAuth2官方推荐的时间是不大于10分钟,而一旦Authorization Code过期那么后续的攻击也就不能进行下去了;
  3. 一个Authorization Code只能被使用一次,如果OAuth2提供者收到重复的Authorization Code,它会拒绝当前的令牌申请请求。不止如此,根据OAuth2官方推荐,它还可以把和这个已经使用过的Authorization Code相关联的access_token全部撤销掉,进一步降低安全风险;

预防方法

要防止这样的攻击其实很容易,作为第三方应用的开发者,只需在OAuth2认证过程中加入state参数,并验证它的参数值即可。具体细节如下:

在将用户重定向到OAuth2的Authorization Endpoint去的时候,为用户生成一个随机的字符串,并作为state参数加入到URL中。在收到OAuth2服务提供者返回的Authorization Code请求的时候,验证接收到的state参数值。如果是正确合法的请求,那么此时接受到的参数值应该和上一步提到的为该用户生成的state参数值完全一致,否则就是异常请求。state参数值需要具备下面几个特性:

  1. 不可预测性:足够的随机,使得攻击者难以猜到正确的参数值
  2. 关联性:state参数值和当前用户会话(user session)是相互关联的
  3. 唯一性:每个用户,甚至每次请求生成的state参数值都是唯一的
  4. 时效性:state参数一旦被使用则立即失效