1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import importlib
23 import inspect
24 import pickle
25 import sys
26 from io import BytesIO
27 from typing import Dict, Set
28
29 from horizons.network import NetworkException, PacketTooLarge
30
31 __version__ = '0.1'
32
33 PICKLE_PROTOCOL = 2
34 PICKLE_RECIEVE_FROM = 'server'
35 PICKLE_SAFE = {
36 'client': {},
37 'server': {},
38 }
42 """
43 NOTE: this is a security related method and may lead to
44 execution of arbritary code if used in a wrong way
45
46 pickle encodes modules and classes using their name. during "unpickling"
47 pickle imports the modules and creates instances of these classes again.
48 knowing this an attacker could easily create a paket "tricking" pickle
49 to load and execute an instance of dangerous classes/methods/commands.
50 this is not an exploit but by design!
51 e.g. python -c 'import pickle; pickle.loads("cos\nsystem\n(S\"ls ~\"\ntR.")'
52
53 In order to make pickle safer we build a whitelist of modules and classes
54 which pickle will check during "unpickling". Please note that we aren't
55 100% sure if there is still a way to execute arbitrary code.
56
57 References:
58 - http://docs.python.org/library/pickle.html
59 - http://nadiana.com/python-pickle-insecure
60 """
61 @classmethod
62 - def add(cls, origin, klass):
63 """Adding SafeUnpickler to the pickle whitelist"""
64 global PICKLE_SAFE
65 module = klass.__module__
66 name = klass.__name__
67 if (module == cls.__module__ and name == cls.__name__):
68 raise RuntimeError("Adding SafeUnpickler to the pickle whitelist is not allowed")
69 types = ['client', 'server'] if origin == 'common' else [origin]
70 for origin in types:
71 if module not in PICKLE_SAFE[origin]:
72 PICKLE_SAFE[origin][module] = set()
73 if name not in PICKLE_SAFE[origin][module]:
74 PICKLE_SAFE[origin][module].add(name)
75
76 @classmethod
83
84 @classmethod
98
99 @classmethod
101 class CustomUnpickler(pickle.Unpickler):
102 find_global = cls.find_class
103
104 file = BytesIO(str)
105 return CustomUnpickler(file).load()
106
122
126 """simple ok message"""
127
128
129 SafeUnpickler.add('common', cmd_ok)
135 self.errorstr = errorstr
136 self.type = _type
137
138 @staticmethod
140 if not isinstance(pkt.errorstr, str):
141 raise NetworkException("Invalid datatype: errorstr")
142 if not isinstance(pkt.type, int):
143 raise NetworkException("Invalid datatype: type")
144
145
146 SafeUnpickler.add('common', cmd_error)
152 self.errorstr = errorstr
153
154 @staticmethod
156 if not isinstance(pkt.errorstr, str):
157 raise NetworkException("Invalid datatype: errorstr")
158
159
160 SafeUnpickler.add('common', cmd_fatalerror)
161
162
163
164 -def unserialize(data, validate=False, protocol=0):
173
174
175
176
177 import horizons.network.packets.server
178 import horizons.network.packets.client
179