database.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os.path
  4. import time
  5. try:
  6. from urllib.request import urlopen
  7. from urllib.error import URLError
  8. except ImportError as e:
  9. from urllib2 import urlopen, URLError
  10. from .utils import UpdateInBurpException
  11. INSTALLATION_DIR = os.path.realpath(os.path.dirname(__file__))
  12. DATABASE_FILE = os.path.join(INSTALLATION_DIR, "webtech.json")
  13. WAPPALYZER_DATABASE_FILE = os.path.join(INSTALLATION_DIR, "apps.json")
  14. WAPPALYZER_DATABASE_URL = "https://raw.githubusercontent.com/AliasIO/Wappalyzer/master/src/apps.json"
  15. WEBTECH_DATABASE_URL = "https://raw.githubusercontent.com/ShielderSec/webtech/master/webtech/webtech.json"
  16. DAYS = 60 * 60 * 24
  17. def download_database_file(url, target_file):
  18. """
  19. Download the database file from the WAPPPALIZER repository
  20. """
  21. print("Updating database...")
  22. response = urlopen(url)
  23. with open(target_file, 'wb') as out_file:
  24. out_file.write(response.read())
  25. print("Database updated successfully!")
  26. def save_database_file(content, target_file):
  27. with open(target_file, 'wb') as out_file:
  28. out_file.write(content)
  29. print("Database updated successfully!")
  30. def download(webfile, dbfile, name, force=False, burp=False):
  31. """
  32. Check if outdated and download file
  33. """
  34. now = int(time.time())
  35. if not os.path.isfile(dbfile):
  36. print("{} Database file not present.".format(name))
  37. if burp:
  38. raise UpdateInBurpException()
  39. download_database_file(webfile, dbfile)
  40. # set timestamp in filename
  41. else:
  42. last_update = int(os.path.getmtime(dbfile))
  43. if last_update < now - 30 * DAYS or force:
  44. if burp:
  45. raise UpdateInBurpException()
  46. if force:
  47. print("Force update of {} Database file".format(name))
  48. else:
  49. print("{} Database file is older than 30 days.".format(name))
  50. os.remove(dbfile)
  51. download_database_file(webfile, dbfile)
  52. def update_database(args=None, force=False, burp=False):
  53. """
  54. Update the database if it's not present or too old
  55. """
  56. try:
  57. download(WAPPALYZER_DATABASE_URL, WAPPALYZER_DATABASE_FILE, "Wappalyzer", force=force, burp=burp)
  58. download(WEBTECH_DATABASE_URL, DATABASE_FILE, "WebTech", force=force, burp=burp)
  59. return True
  60. except URLError as e:
  61. print("Unable to update database, check your internet connection and Github.com availability.")
  62. return False
  63. def merge_databases(db1, db2):
  64. """
  65. This helper function merge elements from two databases without overrding its elements
  66. This function is not generic and *follow the Wappalyzer db scheme*
  67. """
  68. # Wappalyzer DB format must have an apps object
  69. db1 = db1['apps']
  70. db2 = db2['apps']
  71. merged_db = db1
  72. for prop in db2:
  73. if merged_db.get(prop) is None:
  74. # if the element appears only in db2, add it to db1
  75. # TODO: Validate type of db2[prop]
  76. merged_db[prop] = db2[prop]
  77. else:
  78. # both db contains the same property, merge its children
  79. element = merged_db[prop]
  80. for key, value in db2[prop].items():
  81. if merged_db[prop].get(key) is None:
  82. # db1's prop doesn't have this key, add it freely
  83. if type(value) in [str, list, dict]:
  84. element[key] = value
  85. else:
  86. raise ValueError('Wrong type in database: only "dict", "list" or "str" are permitted - element of type {}'.format(type(value).__name__))
  87. else:
  88. # both db's prop have the same key, pretty disappointing :(
  89. element[key] = merge_elements(merged_db[prop][key], value)
  90. merged_db[prop] = element
  91. return {'apps': merged_db}
  92. def merge_elements(el1, el2):
  93. """
  94. Helper function to merge 2 element of different types
  95. Note: el2 has priority over el1 and can override it
  96. The possible cases are:
  97. dict & dict -> merge keys and values
  98. list & list -> merge arrays and remove duplicates
  99. list & str -> add str to array and remove duplicates
  100. str & str -> make a list and remove duplicates
  101. all other cases will raise a ValueError exception
  102. """
  103. if isinstance(el1, dict):
  104. if isinstance(el2, dict):
  105. # merge keys and value
  106. el1.update(el2)
  107. return el1
  108. else:
  109. raise ValueError('Incompatible types when merging databases: element1 of type {}, element2 of type {}'.format(type(el1).__name__, type(el2).__name__))
  110. elif isinstance(el1, list):
  111. if isinstance(el2, list):
  112. # merge arrays and remove duplicates
  113. el1.extend(el2)
  114. return list(set(el1))
  115. elif isinstance(el2, str):
  116. # add string to array and remove duplicates
  117. el1.append(el2)
  118. return list(set(el1))
  119. else:
  120. raise ValueError('Incompatible types when merging databases: element1 of type {}, element2 of type {}'.format(type(el1).__name__, type(el2).__name__))
  121. elif isinstance(el1, str):
  122. if isinstance(el2, str):
  123. # make a list and remove duplicates
  124. return list(set([el1, el2]))
  125. else:
  126. return merge_elements(el2, el1)
  127. raise ValueError('Wrong type in database: only "dict", "list" or "str" are permitted - element of type {}'.format(type(el1).__name__))