SCIM Hunting – Beyond SSO

# SCIM Hunting – Beyond SSO

08 May 2025 – Posted by Francesco Lacerenza

## Introduction

Single Sign-On (SSO) related bugs have gotten an incredible amount of hype and a lot of amazing public disclosures in recent years. Just to cite a few examples:

– Common OAuth Vulnerabilities
– Sign in as anyone: Bypassing SAML SSO authentication with parser differentials
– Account hijacking using “dirty dancing” in sign-in OAuth-flows

And so on – there is a lot of gold out there.

Not surprisingly, systems using a custom implementation are the most affected since integrating SSO with a platform’s _User_ object model is not trivial.

However, while SSO often takes center stage, another standard is often under-tested – **SCIM (System for Cross-domain Identity Management)**. In this blogpost we will dive into its core aspects & the insecure design issues we often find while testing our clients’ implementations.

_Classic AI Generated Placeholder Image_

## Table of Contents

1. SCIM 101
2. Hunting for Bugs
3. Extra Focus Areas
4. Conclusions

## SCIM 101

SCIM is a standard designed to automate the provisioning and deprovisioning of user accounts across systems, ensuring access consistency between the connected parts.

The standard is defined in the following RFCs: RFC7642, RFC7644, RFC7643.

While it is **not specifically designed to be an IdP-to-SP protocol**, rather a generic user pool syncing protocol for cloud environments, real-world scenarios mostly embed it in the IdP-SP relationship.

#### Core Components

To make a long story short, the standard defines a set of RESTful APIs exposed by the Service Providers (SP) which should be callable by other actors (mostly Identity Providers) to update the users pool.

It provides REST APIs with the following set of operations to edit the managed objects (see scim.cloud):

– **Create**: POST `https://example-SP.com/{v}/{resource}`
– **Read**: GET `https://example-SP.com/{v}/{resource}/{id}`
– **Replace**: PUT `https://example-SP.com/{v}/{resource}/{id}`
– **Delete**: DELETE `https://example-SP.com/{v}/{resource}/{id}`
– **Update**: PATCH `https://example-SP.com/{v}/{resource}/{id}`
– **Search**: GET `https://example-SP.com/{v}/{resource}?`
– **Bulk**: POST `https://example-SP.com/{v}/Bulk`

So, we can summarize SCIM as a set APIs usable to perform CRUD operations on a set of JSON encoded objects representing user identities.

**Core Functionalities**

If you want to look into a SCIM implementation for bugs, here is a list of core functionalities that would need to be reviewed during an audit:

1. **Server Config & Authn/z Middlewares**- SCIM does not define its authn/authz method, hence it will always be custom
2. **SCIM Object to Internal Objects Mapping Function**- How the backend is converting / linking the SCIM objects to the internal _Users_ and _Groups_ objects. Most of the times they are more complex and have tons of constraints `& ||` safety checks.

A few examples: `internal` attributes that should not be user-controlled, platform-specific attributes not allowed in SCIM, etc.
3. **Operations Exec Logic**- Changes within identity-related objects typically trigger application flows. A few examples include: _update_ should trigger a confirmation flow / flag the user as unconfirmed, `username` _update_ should trigger ownership / pending invitations / re-auth checks and so on.

#### Mind The Impact

As direct IdP-to-SP communication, most of the resulting issues will require a certain level of access either in the IdP or SP. Hence, the complexity of an attack may lower most of your findings.
Instead, the impact might be skyrocketing in _Multi-tenant Platforms_ where SCIM Users may lack tenant-isolation logic common.

## Hunting for Bugs

The following are some juicy examples of bugs you should look for while auditing SCIM implementations.

### Auth Bypasses

A few months ago we published our advisory for an _Unauthenticated SCIM Operations In Casdoor IdP Instances_. It is an open-source identity solution supporting various auth standards such as OAuth, SAML, OIDC, etc. Of course SCIM was included, but as a service, meaning the Casdoor (IdP) would also allow external actors to manipulate its users pool.

Casdoor utilized the elimity-com/scim library, which, by default, does not include authentication in its configuration as per the standard. Consequently, a SCIM server defined and exposed using this library remains unauthenticated.

“`
server := scim.Server{ Config: config, ResourceTypes: resourceTypes, }
“`

Exploiting an instance required emails matching the configured domains. A SCIM POST operation was usable to create a new user matching the internal email domain and data.

“`
➜ 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”: “[email protected]”}],”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:///scim/Users’
“`

Then, authenticate to the IdP dashboard with the new admin user `admin2:12345678`.

**Note**: The maintainers released a new version (v1.812.0), which includes a fix.

While that was a very simple yet critical issue, bypasses could be found in authenticated implementations. In other cases the service could be available only internally and unprotected.

### SCIM Token Management

_[*] IdP-Side Issues_

Since SCIM secrets allow dangerous actions on the _Service Providers_, they should be protected from extractions happening after the setup. Testing or editing an IdP SCIM integration on a configured application should require a new SCIM token in input, if the connector URL differs from the one previously set.

A famous IdP was found to be issuing the SCIM integration test requests to `/v1/api/scim/Users?startIndex=1&count=1` with the old secret while accepting a new `baseURL`.

_+1 Extra – Covering traces_: Avoid logging errors by mocking a response JSON with the expected data for a successful SCIM integration test.
An example mock response’s JSON for a `Users` query:

“`
{ “Resources”: [ { “externalId”: “”, “id”: “[email protected]”, “meta”: { “created”: “2024-05-29T22:15:41.649622965Z”, “location”: “/Users/[email protected]”, “version”: ” 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”: “[email protected]”, “display”: “[email protected]” }] }
“`

The platform created an access map named `TEST_NAME`, granting the `superadmin` role to members.

_[*] Example 2 – Mass Assignment In SCIM-To-User Mapping_

Other internal attributes manipulation may be possible depending on the object mapping strategy. A juicy example could look like the one below.

“`
SSO_user.update!( external_id: scim_data[“externalId”], # (o)__(o)’ userData: Oj.load(scim_req_body), )
“`

Even if `Oj` defaults are overwritten (sorry, no deserialization) it could still be possible to put any data in the SCIM request and have it accessible through `userData`. The logic is assuming it will only contain SCIM attributes.

### Verification Bypasses

This category contains all the bugs arising from _required_ internal user-management processes not being applied to updates caused by SCIM events ( _e.g., `email` / `phone` / `userName` verification_).

An interesting related finding is Gitlab Bypass Email Verification (CVE-2019-5473). We have found similar cases involving the bypass of a code verification processes during our assessments as well.

_[*] Example – Same-Same But With Code Bypass_

A SCIM email change did not trigger the typical confirmation flow requested with other email change operations.

Attackers could request a verification code to their email, change the email to a victim one with SCIM, then redeem the code and thus verify the _new_ email address.

“`
PATCH /scim/v2// HTTP/2 Host: Authorization: Bearer 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”: “” } } ] }
“`

### Account Takeover

In multi-tenant platforms, the SSO-SCIM identity should be linked to an underlying user object. While it is not part of the RFCs, the management of user attributes such as `userName` and `email` is required to eventually trigger the platform’s processes for validation and ownership checks.

A public example case where things did not go well while updating the underlying user is CVE-2022-1680 – Gitlab Account take over via SCIM email change. Below is a pretty similar instance discovered in one of our clients.

_[*] Example – Same-Same But Different_

A client permitted SCIM operations to change the email of the user and perform account takeover.
The function `set_username` was called every time there was a creation or update of SCIM users.

“`
#[…] 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
“`

The `underlying_user` should be `nil`, hence blocking the change, if the organization is not entitled to manage the user according to `isAuthzed`. In our specific case, the authorization function did not protect users in a specific state from being taken over. SCIM could be used to forcefully change the victim user’s email and take over the account once it was added to the tenant. If combined with the classic _“Forced Tenant Join”_ issue, a nice chain could have been made.

Moreover, since the platform did not protect against multi-SSO context-switching, once authenticated with the new email, the attacker could have access to all other tenants the user was part of.

## Extra Focus Areas

### Interesting SCIM Ops Syntax

As per rfc7644, the _Path_ attribute is defined as:

> The “path” attribute value is a String containing an attribute path describing the target of the operation. The “path” attribute is OPTIONAL for “add” and “replace” and is REQUIRED for “remove” operations.

As the `path` attribute is _OPTIONAL_, the `nil` possibility should be carefully managed when it is part of the execution logic.

“`
def exec_scim_ops(scim_identity, operation) path = operation[“path”] value = operation[“value”] case path when “members” # […] when “externalId” # […] else # semi-Catch-All Logic! end end
“`

Putting a catch-all default could allow another syntax of `PatchOp` messages to still hit one of the restricted cases while skipping the checks. Here is an example SCIM request body that would skip the `externalId` checks and edit it within the context above.

“`
{ “schemas”: [“urn:ietf:params:scim:api:messages:2.0:PatchOp”], “Operations”: [ { “op”: “replace”, “value”: { “externalId”: “” } } ] }
“`

The `value` of an `op` is allowed to contain a dict of “.

### Bulk Ops Order Evaluation

Since bulk operations may be supported (currently very few cases), there could be specific issues arising in those implementations:

– _Race Conditions_- the ordering logic could not include reasoning about the extra processes triggered in each step
– _Missing Circular References Protection_- The RFC7644 is explicitly talking about Circular Reference Processing (see example below).

### JSON Interoperability

Since SCIM adopts JSON for data representation, JSON interoperability attacks could lead to most of the issues described in the hunting list. A well-known starting point is the article: An Exploration of JSON Interoperability Vulnerabilities .

Once the parsing lib used in the SCIM implementation is discovered, check if other internal logic is relying on the stored JSON serialization while using a different parser for comparisons or unmarshaling.

Despite being a relatively simple format, JSON parser differentials could lead to interesting cases – such as the one below:

## Conclusions

As an extension of SSO, SCIM has the potential to enable critical exploitations under specific circumstances. If you’re testing SSO, SCIM should be in scope too!

Finally, most of the interesting vulnerabilities in SCIM implementations require a deep understanding of the application’s authorization and authentication mechanisms. The real value lies in identifying the differences between _SCIM objects_ and the mapped internal _User objects_, as these discrepancies often lead to impactful findings.

Leave a Reply

Your email address will not be published. Required fields are marked *