Package horizons :: Package network :: Package packets
[hide private]
[frames] | no frames]

Source Code for Package horizons.network.packets

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA       02110-1301      USA 
 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  } # type: Dict[str, Dict[str, Set[str]]] 
39 40 41 -class SafeUnpickler:
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
77 - def set_mode(cls, client=True):
78 global PICKLE_RECIEVE_FROM 79 if client: 80 PICKLE_RECIEVE_FROM = 'server' 81 else: 82 PICKLE_RECIEVE_FROM = 'client'
83 84 @classmethod
85 - def find_class(cls, module, name):
86 global PICKLE_SAFE, PICKLE_RECIEVE_FROM 87 if module not in PICKLE_SAFE[PICKLE_RECIEVE_FROM]: 88 raise pickle.UnpicklingError( 89 'Attempting to unpickle unsafe module "{0}" (class="{1}")'. 90 format(module, name)) 91 mod = importlib.import_module(module) 92 if name not in PICKLE_SAFE[PICKLE_RECIEVE_FROM][module]: 93 raise pickle.UnpicklingError( 94 'Attempting to unpickle unsafe class "{0}" (module="{1}")'. 95 format(name, module)) 96 klass = getattr(mod, name) 97 return klass
98 99 @classmethod
100 - def loads(cls, str):
101 class CustomUnpickler(pickle.Unpickler): 102 find_global = cls.find_class
103 104 file = BytesIO(str) 105 return CustomUnpickler(file).load()
106
107 108 #------------------------------------------------------------------------------- 109 110 -class packet:
111 maxpacketsize = 0 112
113 - def __init__(self):
114 """ctor"""
115 116 @staticmethod
117 - def validate(pkt, protocol):
118 return True
119
120 - def serialize(self):
121 return pickle.dumps(self, PICKLE_PROTOCOL)
122
123 124 #------------------------------------------------------------------------------- 125 -class cmd_ok(packet):
126 """simple ok message"""
127 128 129 SafeUnpickler.add('common', cmd_ok)
130 131 132 #------------------------------------------------------------------------------- 133 -class cmd_error(packet):
134 - def __init__(self, errorstr, _type=0):
135 self.errorstr = errorstr 136 self.type = _type
137 138 @staticmethod
139 - def validate(pkt, protocol):
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)
147 148 149 #------------------------------------------------------------------------------- 150 -class cmd_fatalerror(packet):
151 - def __init__(self, errorstr):
152 self.errorstr = errorstr
153 154 @staticmethod
155 - def validate(pkt, protocol):
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):
165 mypacket = SafeUnpickler.loads(data) 166 if validate: 167 if not inspect.isfunction(mypacket.validate): 168 raise NetworkException("Attempt to override packet.validate()") 169 if mypacket.__class__.maxpacketsize > 0 and len(data) > mypacket.__class__.maxpacketsize: 170 raise PacketTooLarge("packet={}, length={:d})".format(mypacket.__class__.__name__, len(data))) 171 mypacket.__class__.validate(mypacket, protocol) 172 return mypacket
173 174 #------------------------------------------------------------------------------- 175 176 177 import horizons.network.packets.server # isort:skip 178 import horizons.network.packets.client # isort:skip 179