Làm sao mở file lỗi image not rege năm 2024
# Path traversal to RCE with Flask debug ## Mở đầu Path Traversal là một lỗ hổng cho phép kẻ tấn công đọc, sửa, xóa một file tùy ý trên hệ thống. ![](https://i.imgur.com/gcwRGGO.png) Path traversal mang nhiều impact khác nhau khi đặt trong các ngữ cảnh khác nhau, ví dụ như nếu attacker kiểm soát được input rơi vào hàm `include` của PHP, lúc này nó sẽ được gọi là Local File Inclusion, giúp attacker include một file PHP bất kì. Nếu như bằng một cách nào đó, attacker include được một file PHP shell vào thì LFI sẽ có thể trở thành một lỗ hổng RCE. Từ path traversal có thể lead ra được RCE là do cách hoạt động của PHP, hàm `include` đơn giản sẽ chỉ lấy nội dung từ file đích rồi đưa vào làm nội dung cho file hiện tại, nếu tồn tại cặp dầu `` thì nội dung bên trong sẽ được xem như code PHP ![](https://i.imgur.com/JheeFz2.png) Tuy nhiên đối với Python thì việc import các module sẽ không hoạt động như thế, ta sẽ không thể tự do inject code trong runtime giống như PHP. Vậy liệu từ Path traversal có con đường nào để biến nó trở thành RCE hay không? Thật ra thì có rất nhiều, tùy thuộc vào ngữ cảnh của lỗi Path traversal đó, lần này ta sẽ cùng tìm hiểu một con đường thú vị để turn từ Path traversal thành RCE khi Debug mode của Flask được bật! ## Debug mode: ON == Risk: Exist Debug là một giải pháp vô cùng hữu dụng đối với các lập trình viên khi cần tìm ra lỗi trong các dòng code, việc xem cách dòng code đó chạy qua từng bước, inspect giá trị của các biến sẽ dễ tìm bug hơn nhiều so với việc chỉ nhìn code chạy, nhất là đối với các code base lớn với nhiều dependencies. Một số web framework biết được việc debug là thiết yếu đối với các developers nên thường sẽ tích hợp sẵn chế độ này, developers có thể tự do tắt mở chế độ này khi cần thiết. Chế độ debug của các web framework thường sẽ in ra stack trace, in ra các web routes, **biến môi trường**, một đoạn code snippets, ... Với từng ấy thông tin, chắc chắn chế độ này sẽ không nên được bật khi lên môi trường production, tuy nhiên không ít lập trình viên đã quên tắt chế độ này dẫn đến việc tiết lộ cho attacker nhiều thông tin nhạy cảm của server. ![](https://i.imgur.com/HyRh5WG.png) Một số web framework có chế độ debug như: Symfony, Laravel, Django, Flask, ... Một số bug đã được tìm ra từ việc quên tắt debug khi lên production: - Symfony profiler: https://infosecwriteups.com/how-i-was-able-to-find-multiple-vulnerabilities-of-a-symfony-web-framework-web-application-2b82cd5de144 - Microsoft django debug mode: https://medium.com/@syedabuthahir/django-debug-mode-to-rce-in-microsoft-acquisition-189d27d08971 ## Debug mode: ON trong flask Bản thân Flask cũng có debug mode của riêng nó, khi được bật thì một endpoint mới là `/console` sẽ xuất hiện, khi truy cập endpoint này, developers sẽ có thể chạy các python script với context của ứng dụng hiện tại ![](https://i.imgur.com/bjMF9f8.png) Tuy nhiên trước đó thì ta sẽ phải nhập đúng mã pin được tạo ra bởi Flask ![](https://i.imgur.com/q1sUiiw.png) Mã pin này hiển thị trên terminal khi ta chạy lệnh flask run với biến môi trường `FLASK_DEBUG=ON` ![](https://i.imgur.com/Ck1twZu.png) Vậy thì nếu là một attacker, để vào được console này thì ta sẽ phải biết mã PIN kia, nhưng làm sao để biết được mã PIN đó? Thực chất mã PIN kia không thật sự ngẫu nhiên, nó được tạo nên bởi một số yếu tố của server ## PIN code Thuật toán dùng để tạo ra mã pin nằm trong file `/usr/lib/python3/dist-packages/werkzeug/debug/__init__.py` ( WSGI mặc định của flask trong môi trường dev là werkzeug ). Ở các version python khác nhau thì thuật toán dùng để tạo ra mã pin cũng sẽ khác nhau một chút, version python hiện tại trên server là `3.10.6`. Việc ta cần làm bây giờ đó là reverse thuật toán tạo PIN code ## Reversing PIN code generating algorithm Hàm chủ yếu nhận nhiệm vụ tạo ra mã PIN và cookie là `get_pin_and_cookie_name`, có 2 yếu tố mà hàm này sử dụng: ```python probably_public_bits = [ username, modname, getattr(app, "__name__", type(app).__name__), getattr(mod, "__file__", None), ] ``` và ```python private_bits = [str(uuid.getnode()), get_machine_id()] ``` Ta sẽ bắt đầu với phần `probably_public_bits`. Trace lên bên trên ta sẽ thấy đoạn `username = getpass.getuser()`, hàm `getpass.getuser()` sẽ trả về logged user, nói đơn giản là user đã khởi chạy flask. Trường hợp bên này thì user tên là `shin24` tiếp theo là `modname`, phần này thì được gán ở câu lệnh: ```python modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__) ``` Sau câu lệnh này thì `modname` sẽ luôn có giá trị là `flask.app`. `getattr(app, "__name__", type(app).__name__)` sẽ luôn mang giá trị `Flask` `getattr(mod, "__file__", None)` sẽ mang giá trị là đường dẫn tuyệt đối đến file `app.py` của flask, trường hợp này là `/usr/lib/python3/dist-packages/flask/app.py` Vậy tóm lại ta sẽ có: ```python probably_public_bits = [ username, # shin24 modname, # flask.app getattr(app, "__name__", type(app).__name__), # Flask getattr(mod, "__file__", None), # /usr/lib/python3/dist-packages/flask/app.py ] ``` Giờ qua phần `private_bits`, ta sẽ có `uuid.getnode()` là địa chỉ MAC hiện tại của máy ở định dạng Decimal, thông tin này có thể có được từ `/sys/class/net/ |