PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/nogotofail/mitm/blame/app_blame.py

https://gitlab.com/gaurav1981/nogotofail
Python | 341 lines | 338 code | 0 blank | 3 comment | 4 complexity | 4c158fed7baade68f28c884ba7a14f9a MD5 | raw file
Possible License(s): Apache-2.0
  1. r'''
  2. Copyright 2014 Google Inc. All rights reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. '''
  13. from collections import namedtuple
  14. import logging
  15. import socket
  16. import select
  17. import sys
  18. import ssl
  19. import threading
  20. import time
  21. import urllib
  22. from nogotofail.mitm.connection import handlers
  23. Application = namedtuple("Application", ["package", "version"])
  24. class Client(object):
  25. socket = None
  26. info = None
  27. last_used = None
  28. def __init__(self, socket, info, now):
  29. self.socket = socket
  30. self.info = info
  31. self.last_used = now
  32. def recv_lines(socket):
  33. lines = []
  34. file = socket.makefile()
  35. while True:
  36. line = file.readline().strip()
  37. if line == "":
  38. break
  39. lines.append(line)
  40. return lines
  41. class Server:
  42. """Server for managing connections to the connection blaming app on devices.
  43. """
  44. port = None
  45. clients = None
  46. CLIENT_TIMEOUT = 21600
  47. def __init__(self, port, cert, default_prob, default_attacks, default_data):
  48. self.txid = 0
  49. self.kill = False
  50. self.port = port
  51. self.cert = cert
  52. self.default_prob = default_prob
  53. self.default_attacks = default_attacks
  54. self.default_data = default_data
  55. self.clients = {}
  56. self.listening_thread = threading.Thread(target=self.run)
  57. self.listening_thread.daemon = True
  58. self.logger = logging.getLogger("nogotofail.mitm")
  59. self.server_socket = None
  60. def start(self):
  61. self.listening_thread.start()
  62. def run(self):
  63. try:
  64. self.listen()
  65. except Exception as e:
  66. self.logger.exception("Uncaught exception in Listening thread!")
  67. self.logger.critical("EXITING")
  68. sys.exit()
  69. def listen(self):
  70. self.server_socket = socket.socket()
  71. self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  72. self.server_socket.bind(("", self.port))
  73. self.server_socket.listen(5)
  74. self.server_socket.settimeout(2)
  75. if self.cert:
  76. self.server_socket = (
  77. ssl.wrap_socket(
  78. self.server_socket, certfile=self.cert,
  79. server_side=True))
  80. while not self.kill:
  81. try:
  82. # Check our old sockets and cleanup if needed
  83. for client in self.clients.keys():
  84. self.client_available(client)
  85. (client_socket, client_address) = self.server_socket.accept()
  86. client_socket.settimeout(2)
  87. client_addr, client_port = client_address
  88. client_info = None
  89. # handshake
  90. try:
  91. client_info = self._handshake(client_socket)
  92. except (ValueError, KeyError, IndexError):
  93. try:
  94. client_socket.sendall("400 Error parsing message\n\n")
  95. except socket.error:
  96. pass
  97. client_socket.close()
  98. self.logger.warning(
  99. "AppBlame bad handshake from %s" % client_addr)
  100. continue
  101. except socket.timeout:
  102. client_socket.close()
  103. self.logger.info(
  104. "AppBlame handshake timeout from %s" % client_addr)
  105. continue
  106. old_client = self.clients.get(client_addr, None)
  107. self.clients[client_addr] = Client(
  108. client_socket, client_info, time.time())
  109. if old_client:
  110. old_client.socket.close()
  111. self.logger.info("AppBlame new client from %s" % client_addr)
  112. except socket.timeout:
  113. pass
  114. except socket.error:
  115. self.logger.exception("AppBlame socket error")
  116. self.server_socket.close()
  117. def _handshake(self, client_socket):
  118. lines = recv_lines(client_socket)
  119. name, version = lines[0].split("/", 1)
  120. if name != "nogotofail_ctl":
  121. raise ValueError("Unexpected app type")
  122. # Parse out the headers
  123. raw_headers = [line.split(":", 1) for line in lines[1:]]
  124. headers = {entry.strip(): header.strip()
  125. for entry, header in raw_headers}
  126. client_info = self._parse_headers(headers)
  127. # Send the OK
  128. client_socket.sendall("0 OK\n")
  129. # Send the configs
  130. prob = client_info.get("Attack-Probability", self.default_prob)
  131. client_socket.sendall("Attack-Probability: %f\n" % prob)
  132. attacks = client_info.get("Attacks", self.default_attacks)
  133. attacks_str = ",".join([attack.name for attack in attacks])
  134. client_socket.sendall("Attacks: %s\n" % attacks_str)
  135. supported_str = ",".join([
  136. attack
  137. for attack in
  138. handlers.connection.handlers.map])
  139. client_socket.sendall("Supported-Attacks: %s\n" % supported_str)
  140. data = client_info.get("Data-Attacks", self.default_data)
  141. data_str = ",".join([attack.name for attack in data])
  142. client_socket.sendall("Data-Attacks: %s\n" % data_str)
  143. supported_data = ",".join([
  144. attack
  145. for attack in handlers.data.handlers.map])
  146. client_socket.sendall("Supported-Data-Attacks: %s\n" % supported_data)
  147. client_socket.sendall("\n")
  148. return client_info
  149. def _parse_headers(self, headers):
  150. client_info = {}
  151. # Platform-Info is required
  152. client_info["Platform-Info"] = headers["Platform-Info"]
  153. if "Installation-ID" in headers:
  154. client_info["Installation-ID"] = headers["Installation-ID"]
  155. if "Attack-Probability" in headers:
  156. value = float(headers["Attack-Probability"])
  157. if value < 0 or value > 1.0:
  158. raise ValueError("Attack-Probability outside range")
  159. client_info["Attack-Probability"] = value
  160. if "Attacks" in headers:
  161. attacks = headers["Attacks"].split(",")
  162. attacks = map(str.strip, attacks)
  163. client_info["Attacks"] = [
  164. handlers.connection.handlers.map[attack] for attack in attacks
  165. if attack in handlers.connection.handlers.map]
  166. if len(client_info["Attacks"]) == 0:
  167. client_info["Attack-Probability"] = 0
  168. if "Data-Attacks" in headers:
  169. attacks = headers["Data-Attacks"].split(",")
  170. attacks = map(str.strip, attacks)
  171. client_info["Data-Attacks"] = [handlers.data.handlers.map[attack]
  172. for attack in attacks
  173. if attack in
  174. handlers.data.handlers.map]
  175. # Store the raw headers as well in case a handler needs something the
  176. # client sent in an additional header
  177. client_info["headers"] = headers
  178. return client_info
  179. def client_available(self, client_addr):
  180. """Returns if the app blame client is running on client_addr.
  181. This is best effort only, it may return True for lost clients.
  182. """
  183. client = self.clients.get(client_addr, None)
  184. if not client:
  185. return False
  186. now = time.time()
  187. if now - client.last_used > Server.CLIENT_TIMEOUT:
  188. self.logger.info("AppBlame pruning client %s", client_addr)
  189. del self.clients[client_addr]
  190. client.socket.close()
  191. return False
  192. return True
  193. def get_applications(
  194. self, client_addr, client_port, server_addr, server_port):
  195. """Get the list of applications that owns the (client_addr, client_port, server_addr, server_port) connection on the device.
  196. Returns a tuple containing the platform info string and a list of
  197. Application or None if the
  198. client is available.
  199. """
  200. if not self.client_available(client_addr):
  201. return None
  202. client = self.clients[client_addr]
  203. client_socket = client.socket
  204. client.last_used = time.time()
  205. txid = self.txid
  206. self.txid += 1
  207. family = socket.AF_INET6 if ":" in server_addr else socket.AF_INET
  208. message = (
  209. unicode(
  210. "%d tcp_client_id %s %s %s\n" %
  211. (txid, client_port,
  212. socket.inet_pton(family, server_addr).encode("hex"),
  213. server_port)))
  214. try:
  215. client_socket.sendall(message)
  216. response = client_socket.recv(8192)
  217. if response == "":
  218. raise ValueError("Socket closed")
  219. response = unicode(response).strip()
  220. except (socket.error, ValueError) as e:
  221. self.logger.info(
  222. "AppBlame error for %s, %s. Removing." % (client_addr, e))
  223. del self.clients[client_addr]
  224. client_socket.close()
  225. return None
  226. try:
  227. inid, apps = response.split(" ", 1)
  228. except ValueError:
  229. return None
  230. if int(inid) != txid:
  231. self.logger.error("Blame response for wrong txid, expected %s got %s" % (txid, inid))
  232. return None
  233. platform_info = self.clients[client_addr].info.get(
  234. "Platform-Info", "Unknown")
  235. apps = apps.split(",")
  236. try:
  237. return platform_info, [Application(
  238. *map(urllib.unquote, app.strip().split(" ", 1)))
  239. for app in apps]
  240. except (ValueError, TypeError):
  241. return None
  242. def vuln_notify(
  243. self, client_addr, server_addr, server_port, id, type,
  244. applications):
  245. """Send a notification to client_addr of a vulnerability in applications.
  246. Arguments:
  247. client_addr: Client to notify
  248. server_addr: remote destination of the vulnerable connection
  249. server_port: remote port of the vulnerable connection
  250. id: An opaque blob to identify the connection later on
  251. type: Type of vuln. See nogotofail.mitm.util.vuln.*
  252. applications: List of Applications to blame
  253. Returns if the notification was sent successfully
  254. """
  255. if not self.client_available(client_addr):
  256. return False
  257. client = self.clients[client_addr]
  258. client_socket = client.socket
  259. client.last_used = time.time()
  260. txid = self.txid
  261. self.txid += 1
  262. message = (
  263. unicode("%d vuln_notify %s %s %s %d %s\n") %
  264. (txid, id, type, server_addr, server_port,
  265. ", ".join(
  266. [
  267. "%s %s" % (urllib.quote(app.package), app.version)
  268. for app in applications])))
  269. try:
  270. client_socket.sendall(message)
  271. response = client_socket.recv(8192)
  272. if response == "":
  273. raise ValueError("Socket closed")
  274. response = unicode(response).strip()
  275. except (socket.error, ValueError) as e:
  276. self.logger.info(
  277. "AppBlame notify error for %s, %s. Removing." %
  278. (client_addr, e))
  279. del self.clients[client_addr]
  280. client_socket.close()
  281. return False
  282. id, message = response.split(" ", 2)
  283. if int(id) != txid:
  284. self.logger.error("Blame response for wrong txid, expected %s got %s" % (txid, id))
  285. return False
  286. return message == "OK"
  287. def shutdown(self):
  288. self.kill = True
  289. self.server_socket.close()
  290. self.listening_thread.join(5)
  291. for client in self.clients.values():
  292. try:
  293. client.socket.close()
  294. except:
  295. pass