【CVE-2019-3778】Spring-Security-OAuth2 Open Redirection 复现分析
字数 1382 2025-08-26 22:12:03
Spring Security OAuth2 Open Redirection漏洞(CVE-2019-3778)分析与复现指南
0x00 漏洞概述
漏洞编号: CVE-2019-3778
漏洞类型: Open Redirection (开放重定向)
影响组件: Spring Security OAuth2
漏洞描述: 攻击者在使用授权码模式时,可以构造恶意请求发送至授权端点,通过篡改redirect_uri参数值,导致授权服务端将用户回调至攻击者可控的URI,从而泄露授权码。
0x01 影响版本
- 2.3.0 - 2.3.4
- 2.2.0 - 2.2.3
- 2.1.0 - 2.1.3
- 2.0.0 - 2.0.16
0x02 环境要求
要复现此漏洞,目标应用必须满足以下条件:
- 必须是授权服务器角色(使用
@EnableAuthorizationServer注解) - 使用
DefaultRedirectResolver类来解析redirect_uri
0x03 环境搭建
1. 数据库配置
需要在服务端数据库表oauth_client_details中插入客户端信息:
INSERT INTO oauth_client_details
(client_id, client_secret, redirect_uri, scope, authorized_grant_types)
VALUES
('ananaskr', '123456', 'http://www.baidu.com', 'all', 'authorization_code');
2. 用户信息配置
在WebConfig类中添加用户信息(示例):
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password("password1").roles("USER")
.and()
.withUser("user2").password("password2").roles("USER")
.and()
.withUser("user3").password("password3").roles("USER");
}
0x04 漏洞分析
关键代码分析
漏洞位于DefaultRedirectResolver类的obtainMatchingRedirect()方法中:
public String obtainMatchingRedirect(String requestedRedirect, ClientDetails client) {
if (requestedRedirect == null) {
return null;
}
Set<String> redirectUris = client.getRegisteredRedirectUri();
if (redirectUris == null || redirectUris.isEmpty()) {
return requestedRedirect;
}
for (String redirectUri : redirectUris) {
if (redirectMatches(requestedRedirect, redirectUri)) {
return requestedRedirect; // 直接返回用户输入的redirect_uri
}
}
return null;
}
redirectMatches()方法检查逻辑:
private boolean redirectMatches(String requestedRedirect, String redirectUri) {
// 检查scheme、host、path和port四部分是否匹配
UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();
// scheme检查
if (!registeredRedirectUri.getScheme().equals(requestedRedirectUri.getScheme())) {
return false;
}
// host检查
if (!registeredRedirectUri.getHost().equals(requestedRedirectUri.getHost())) {
return false;
}
// port检查
if (registeredRedirectUri.getPort() != requestedRedirectUri.getPort()) {
return false;
}
// path检查
String registeredPath = registeredRedirectUri.getPath();
String requestedPath = requestedRedirectUri.getPath();
if (requestedPath.length() < registeredPath.length()) {
return false;
}
if (!requestedPath.substring(0, registeredPath.length()).equals(registeredPath)) {
return false;
}
return true;
}
漏洞利用原理
- 原始检查逻辑只验证了scheme、host、port和path,未对userinfo部分进行验证
- 攻击者可以利用URL解析差异构造恶意URL:
http://www.google.com%ff@www.baidu.com - 服务端解析时,
%ff会被转换为非法字符,导致解析错误 - 某些浏览器会将此类URL解析为
http://www.google.com,从而实现开放重定向
0x05 漏洞复现步骤
-
访问授权端点构造恶意请求:
http://localhost:9090/oauth/authorize? response_type=code& client_id=ananaskr& redirect_uri=http://www.google.com%ff@www.baidu.com& scope=all& client_secret=123456 -
系统会跳转至登录页面,输入有效凭据登录
-
点击"Authorize"按钮授权
-
观察跳转行为:实际会跳转至
www.google.com而非注册的www.baidu.com
0x06 补丁分析
在2.3.5版本中,修复措施包括:
- 在
DefaultRedirectResolver类中增加了对userinfo的检查 - 对请求参数进行了更严格的验证
- 修改
obtainMatchingRedirect()方法,不再直接返回用户输入的redirect_uri,而是在注册的redirect_uri基础上进行修改后返回
关键修复代码:
public String obtainMatchingRedirect(String requestedRedirect, ClientDetails client) {
// ... 省略部分代码 ...
for (String redirectUri : redirectUris) {
if (redirectMatches(requestedRedirect, redirectUri)) {
// 不再直接返回用户输入,而是基于注册URI构建
return UriComponentsBuilder.fromUriString(redirectUri)
.query(requestedRedirectUri.getQuery())
.fragment(requestedRedirectUri.getFragment())
.build()
.toUriString();
}
}
return null;
}
0x07 防护建议
- 升级Spring Security OAuth2到安全版本(2.3.5+)
- 自定义
RedirectResolver实现,增加更严格的重定向URL验证 - 实施白名单机制,严格控制允许的重定向域名
- 对所有用户提供的URL参数进行严格验证和过滤