simulator.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # -*- coding: utf-8 -*-
  2. # TraCINg-Server - Gathering and visualizing cyber incidents on the world
  3. #
  4. # Copyright 2013 Matthias Gazzari, Annemarie Mattmann, André Wolski
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import requests
  18. import json
  19. import random
  20. import time
  21. import sys
  22. import argparse
  23. import time
  24. import datetime
  25. # based on http://stackoverflow.com/questions/10218486/set-nested-dict-value-and-create-intermediate-keys
  26. from collections import defaultdict
  27. recursivedict = lambda: defaultdict(recursivedict)
  28. # constants and defaults
  29. sensorName = "Simulator"
  30. sensorType = "Honeypot"
  31. url = "https://localhost:9999"
  32. cert = ("ssl/simulator/simulator_cert.pem", "ssl/simulator/simulator_key.pem")
  33. incidentTypes = {
  34. 0: "Unknown",
  35. 10: "Transport Layer",
  36. 11: "Portscan",
  37. 20: "Shellcode Injection",
  38. 30: "SQL",
  39. 31: "MySQL",
  40. 32: "MS SQL",
  41. 40: "SMB",
  42. 50: "VoIP",
  43. 60: "Invalid" # invalid test case
  44. }
  45. predefined = {
  46. "sensor": {
  47. "name": "Predefined Sensor Name",
  48. "type": "Predefined Sensor Type",
  49. },
  50. "src": {
  51. "ip": "130.83.58.211",
  52. "port": 80,
  53. },
  54. "dst": {
  55. "ip": "192.30.252.130",
  56. "port": 22,
  57. },
  58. "type": 0,
  59. "log": "Predefined Log",
  60. "md5sum": "7867de13bf22a7f3e3559044053e33e7",
  61. "date": 1,
  62. }
  63. # Return a field only containing the mandatory field
  64. def getMandatoryOnlyEntry():
  65. payload = recursivedict()
  66. payload["src"]["ip"] = getRandomIP()
  67. return payload
  68. # Return a randomly generated entry using every fields possible
  69. def getFullEntry(chooseRandomly):
  70. return {
  71. "sensor": {
  72. "name": sensorName,
  73. "type": sensorType,
  74. },
  75. "src": {
  76. "ip": getRandomIP(),
  77. "port": getRandomPort(),
  78. },
  79. "dst": {
  80. "ip": getRandomIP(),
  81. "port": getRandomPort(),
  82. },
  83. "type": getRandomIncident(),
  84. "log": getRandomLog(),
  85. "md5sum": getRandomMd5sum(),
  86. "date": getTime(chooseRandomly),
  87. }
  88. # Return a randomly generated entry (mandatory field always set)
  89. def getRandomizedEntry(chooseRandomly):
  90. payload = recursivedict()
  91. setRandomly(payload, "sensor", "name", sensorName)
  92. setRandomly(payload, "sensor", "type", sensorType)
  93. payload["src"]["ip"] = getRandomIP()
  94. setRandomly(payload, "src", "port", getRandomPort())
  95. setRandomly(payload, "dst", "ip", getRandomIP())
  96. setRandomly(payload, "dst", "port", getRandomPort())
  97. setRandomly(payload, "type", None, getRandomIncident())
  98. setRandomly(payload, "log", None, getRandomLog())
  99. setRandomly(payload, "md5sum", None, getRandomMd5sum())
  100. setRandomly(payload, "date", None, getTime(chooseRandomly))
  101. return payload
  102. # Return a randomly set entry to be submitted
  103. def getRandomlyEntry(mode, chooseRandomly):
  104. if mode == None:
  105. mode = random.randint(0, 2)
  106. # only set mandatory fields (source IP address)
  107. if mode == 0:
  108. payload = getMandatoryOnlyEntry()
  109. # set every possible field
  110. elif mode == 1:
  111. payload = getFullEntry(chooseRandomly)
  112. # set randomly fields (but always the mandatory field)
  113. else:
  114. payload = getRandomizedEntry(chooseRandomly)
  115. return payload
  116. # Return either the current or a random time (32bit unix time)
  117. def getTime(chooseRandomly):
  118. if chooseRandomly:
  119. return random.randint(0, 2**31 - 1)
  120. else:
  121. return int(time.time())
  122. # Set a value with a probability of 0.5
  123. def setRandomly(target, key1, key2, content):
  124. if random.randint(0, 1) == 0:
  125. if key2 == None:
  126. target[key1] = content
  127. else:
  128. target[key1][key2] = content
  129. # Return a random port ranging from 0 to 65535
  130. def getRandomPort():
  131. return random.randint(0, 2**16 - 1)
  132. # Return a random IP address
  133. def getRandomIP():
  134. ip = [random.randint(0, 255) for _ in range(4)]
  135. return '.'.join([str(e) for e in ip])
  136. # Return a random incident chosen from incidentTypes dictionary
  137. def getRandomIncident():
  138. return random.choice(list(incidentTypes.keys()))
  139. # Return a random md5sum
  140. def getRandomMd5sum():
  141. return str(hex(random.randint(0, 2**128 - 1)).lstrip("0x"))
  142. # Return a random log message
  143. def getRandomLog():
  144. return "Testlog with random value: " + str(random.randint(0, 2**10)) + " \n and a new line and <b>html</b>"
  145. # Post the payload in json format to the server
  146. def post(payload_json, url, cert, useCert, verify):
  147. try:
  148. if useCert:
  149. r = requests.post(url, cert=cert, verify=verify, data=payload_json)
  150. else:
  151. r = requests.post(url, verify=verify, data=payload_json)
  152. return payload_json + "\n --> " + str(r) + "\n>---\n" + r.text + "\n>---\n"
  153. except requests.exceptions.SSLError as e:
  154. print(e)
  155. except IOError as e:
  156. print("Either the cert, the key or both of them are not found.")
  157. except:
  158. print("Unexpected error:", sys.exc_info()[0])
  159. raise
  160. return ""
  161. # define a positive integer type
  162. def positiveInt(string):
  163. value = int(string)
  164. if value < 0:
  165. msg = string + " is not a positive integer"
  166. raise argparse.ArgumentTypeError(msg)
  167. return value
  168. def main():
  169. # define arguments and their behaviour
  170. parser = argparse.ArgumentParser(description = "Simulate incident reports (single, multiple or in logs) and send them via HTTPS POST to a designated server.")
  171. parser.add_argument("-s", "--seed", help = "set the SEED to initialize the pseudorandom number generator", type = positiveInt)
  172. parser.add_argument("-m", "--mode", help = "determine which fields are sent (0: only mandatory fields, 1: every possible field, 2: at least the mandatory fields)", choices = [0, 1, 2], type = int)
  173. parser.add_argument("-n", "--number", help = "set the NUMBER of incident reports (or logs) to be sent (by default 1)", type = positiveInt, default = 1)
  174. parser.add_argument("-i", "--interval", help = "set the MIN and MAX time in ms between single incident reports (by default 1000 and 1000)", nargs = 2, metavar = ("MIN", "MAX"), type = positiveInt, default = [1000, 1000])
  175. parser.add_argument("-v", "--verbose", help = "show more details while sending incident reports", action = "store_true")
  176. parser.add_argument("-u", "--url", help = "set the server URL to sent the incident report(s) (by default " + url + ")", default = url)
  177. certFormated = str(cert).replace("(", "").replace(",", " and")
  178. parser.add_argument("-c", "--cert", help = "set the CERT and private KEY path to be used (by default " + certFormated, nargs = 2, metavar = ("CERT", "KEY"), default = cert)
  179. parser.add_argument("-nc", "--no-cert", help = "disable certificate usage", action = "store_true")
  180. parser.add_argument("-vc", "--verify-cert", help = "disable server certificate verification", action = "store_true")
  181. parser.add_argument("-lf", "--log-format", help = "send multiple incident reports in one log (by default 3 to 10 reports per log)", action = "store_true")
  182. parser.add_argument("-ls", "--log-size", help = "set the MIN and MAX number of incident reports per log (by default MIN = 3 and MAX = 10)", nargs = 2, metavar = ("MIN", "MAX"), type = positiveInt, default = [3, 10])
  183. parser.add_argument("-r", "--random-time", help = "set the timestamp at random instead of using the current time", action = "store_true")
  184. group = parser.add_mutually_exclusive_group()
  185. group.add_argument("-p", "--predefined", help = "send a predefined incident report (cf. the source of this program for details about the predefined report)", action = "store_true")
  186. group.add_argument("-cu", "--custom", help = "apply a custom incident REPORT in JSON format, for example: '{\"src\":{\"ip\":\"192.30.252.130\"}}' (put the incident report into single quotes to prevent the shell from removing the double quotes)", metavar = "REPORT", type = json.loads)
  187. args = parser.parse_args()
  188. # init seed
  189. if args.seed:
  190. seed = args.seed
  191. else:
  192. seed = random.randint(0, sys.maxsize)
  193. random.seed(seed)
  194. # send incidents
  195. if args.number > 0:
  196. for i in range(0, args.number):
  197. # send multiple entries in one log separated by \n
  198. if args.custom is not None:
  199. entry = args.custom
  200. elif args.predefined:
  201. entry = predefined
  202. elif args.log_format:
  203. entry = ""
  204. size = random.randint(args.log_size[0], args.log_size[1])
  205. for j in range(0, size):
  206. entry += getRandomlyEntry(args.mode, args.random_time) + "\n"
  207. else:
  208. # send a single entry
  209. entry = getRandomlyEntry(args.mode, args.random_time)
  210. # post the entry
  211. result = post(json.dumps(entry), args.url, args.cert, not args.no_cert, args.verify_cert)
  212. # print server reply
  213. if args.verbose:
  214. print("-----------------------------------------------")
  215. print("Attack No.:", i + 1, "of", args.number)
  216. try:
  217. print("Date (UTC):", datetime.datetime.utcfromtimestamp(entry["date"]))
  218. except:
  219. print("Date is not supplied")
  220. try:
  221. print("Incident type:", incidentTypes[entry["type"]])
  222. except:
  223. print("Incident type is not supplied")
  224. print("Server response:\n")
  225. print(result)
  226. else:
  227. print("Attack No.:", i + 1, "of", args.number)
  228. # avoid sleep in the last loop
  229. if i < args.number - 1:
  230. time.sleep(random.randint(args.interval[0], args.interval[1])/1000)
  231. if args.verbose:
  232. print("-----------------------------------------------")
  233. # determine new args applying the seed if missing to the previous args
  234. newArgs = " ".join(sys.argv[1:])
  235. if not args.seed:
  236. newArgs += " --seed " + str(seed)
  237. # add single quotes to custom json entry argument
  238. # (prevent the shell removing the required double quotes)
  239. if args.custom is not None:
  240. trimmedEntry = str(entry).replace(" ", "").replace("'", '"')
  241. newArgs = newArgs.replace(trimmedEntry, "'" + trimmedEntry + "'")
  242. # print used arguments with an additional example command
  243. print("To reproduce this simulation use the following argument(s): ")
  244. print("\t" + newArgs)
  245. print("For example using the following command: ")
  246. print("\t python3 " + sys.argv[0], newArgs)
  247. if __name__ == '__main__':
  248. main()