ajaxmap.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-"
  3. """
  4. This file is part of the UFONet project, https://ufonet.03c8.net
  5. Copyright (c) 2013/2026 | psy <epsylon@riseup.net>
  6. You should have received a copy of the GNU General Public License along
  7. with UFONet; if not, write to the Free Software Foundation, Inc., 51
  8. Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  9. """
  10. import socket, threading, re, os, time, base64, traceback
  11. import webbrowser, subprocess, json, sys, shlex
  12. import urllib.request, urllib.error
  13. from urllib.parse import urlparse as urlparse
  14. from .main import UFONet
  15. try:
  16. import pygeoip
  17. except ImportError:
  18. from core._ensure import ensure
  19. if ensure('pygeoip') is None:
  20. print("\n[Error] [AI] Cannot import lib: pygeoip. \n\n To install it try:\n\n $ 'sudo apt-get install python3-geoip libgeoip-dev libgeoip1'\n")
  21. sys.exit(2)
  22. import pygeoip
  23. class AjaxMap(object):
  24. def __init__(self):
  25. self.geo_db_mirror1 = 'https://turina.space/bordercheck/maps.tar.gz' # Turina Server
  26. self._geoip=None
  27. self._geoasn=None
  28. self._geoipstatus='nomap'
  29. self._err=''
  30. self.zombies = []
  31. ufonet = UFONet()
  32. ufonet.create_options()
  33. try:
  34. _z = ufonet.extract_zombies() or []
  35. self.zombies.extend(_z)
  36. except Exception:
  37. pass
  38. for _fn in (ufonet.extract_aliens, ufonet.extract_droids, ufonet.extract_ucavs,
  39. ufonet.extract_rpcs, ufonet.extract_ntps, ufonet.extract_dnss,
  40. ufonet.extract_snmps):
  41. try:
  42. _v = _fn() or []
  43. self.zombies.extend(_v)
  44. except Exception:
  45. pass
  46. for _fpath in ('botnet/memcached.txt', 'botnet/chargen.txt', 'botnet/cldap.txt',
  47. 'botnet/ssdp.txt', 'botnet/qotd.txt', 'botnet/tftp.txt',
  48. 'botnet/wsdisco.txt', 'botnet/coap.txt', 'botnet/mssql.txt',
  49. 'botnet/arms.txt', 'botnet/plex.txt', 'botnet/netbios.txt',
  50. 'botnet/ripv1.txt', 'botnet/middlebox.txt'):
  51. try:
  52. from core._botnet import load_botnet_file
  53. _entries, _empty, _all_ph = load_botnet_file(_fpath)
  54. if _entries and not _empty and not _all_ph:
  55. self.zombies.extend(_entries)
  56. except Exception:
  57. pass
  58. def get_err(self):
  59. return self._err
  60. # check for geoip data status
  61. # basic lock file mechanism to avoid multiple downloads
  62. def get_status(self):
  63. if os.path.exists('maps.downloading'):
  64. if not os.path.exists('maps.downloadmsg'):
  65. f=open("maps.downloadmsg","wb")
  66. f.write("".encode('utf-8'))
  67. f.close()
  68. print("[Info] [AI] [Control] GeoIP data download started! -> [OK!]\n")
  69. self._geoipstatus='downloading'
  70. elif os.path.isdir('maps'):
  71. if self._geoip == None :
  72. self._geoip = pygeoip.GeoIP('maps/GeoLiteCity.dat')
  73. if self._geoasn == None :
  74. self._geoasn = pygeoip.GeoIP('maps/GeoIPASNum.dat')
  75. if os.path.exists("maps.downloadmsg") :
  76. os.remove("maps.downloadmsg")
  77. self._geoipstatus='ok'
  78. return self._geoipstatus
  79. def retrieve(self,url,name):
  80. try:
  81. handle = urllib.request.urlopen(url)
  82. CHUNK = 16384
  83. with open(name,'wb') as fp:
  84. while True:
  85. chunk = handle.read(CHUNK)
  86. if not chunk:
  87. break
  88. fp.write(chunk)
  89. except:
  90. traceback.print_exc()
  91. def download_maps(self):
  92. # generate geolocation values on a map
  93. if self.get_status() != 'nomap':
  94. return self._geoipstatus == 'ok'
  95. if os.path.exists("maps.downloadmsg"):
  96. os.remove("maps.downloadmsg")
  97. f=open("maps.downloading",'w')
  98. f.write("download started<script>$'('#ufomsg').load('/js/ajax.js?fetchmap=')")
  99. f.close()
  100. self._geoipstatus="downloading"
  101. try: # mirror 1
  102. print("\n[Info] [AI] Fetching maps from 'Turina Server':", self.geo_db_mirror1 + "\n")
  103. response = self.retrieve(self.geo_db_mirror1, 'maps.tar.gz')
  104. except:
  105. print(("[Error] [AI] Something wrong fetching maps from remote servers! -> [Aborting!]"), "\n")
  106. traceback.print_exc()
  107. return False #sys.exit(2)
  108. subprocess.call(shlex.split('tar zxfv maps.tar.gz'))
  109. print("\n[Info] [AI] [Control] GeoIP maps and databases -> [OK!]\n")
  110. # set pygeoip data sources
  111. self._geoip = pygeoip.GeoIP('maps/GeoLiteCity.dat')
  112. self._geoasn = pygeoip.GeoIP('maps/GeoIPASNum.dat')
  113. self._geoipstatus='ok'
  114. os.remove('maps.tar.gz')
  115. os.remove('maps.downloading')
  116. return True
  117. # fetches geoip data for specified zombie
  118. def geo_ip(self, zombie):
  119. # check for status, downloading is done by ajax() method
  120. if self.get_status() != 'ok':
  121. if self._geoipstatus =='downloading':
  122. print("\n[Info] [AI] [Control] GeoIP maps and databases -> [Downloading!]\n")
  123. self._err= "ufomsg('[Info] [AI] Downloading maps... -> [Waiting!]')"
  124. elif not os.path.exists('maps/GeoIPASNum.dat') or not os.path.exists('maps/GeoLiteCity.dat'):
  125. print("\n[Info] [AI] GeoIP maps and databases -> [Starting!]\n")
  126. self._err= "ufomsg('[Info] [AI] Map download starting')\n$('#ufomsg').load('/js/ajax.js?fetchgeoip=')"
  127. else:
  128. print("\n[Error] [AI] GeoIP maps and databases: FAILED! -> [Discarding!]\n")
  129. self._err= "ufomsg('<font color='red'>[Info] [AI]</font> Maps: unknown error -> [Discarding!]')"
  130. return None
  131. if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', zombie) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', zombie) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', zombie) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', zombie) or re.match('localhost', zombie):
  132. self._err= "ufomsg('<font color='red'>[Info] [AI] [Control]</font> Maps: invalid ip data -> [Discarding!]')"
  133. return None
  134. # create geoip data skeleton
  135. geo_zombie={}
  136. geo_zombie['qq']=zombie
  137. url = urlparse(zombie)
  138. geo_zombie['city'] = '-'
  139. geo_zombie['country'] = '-'
  140. geo_zombie['country_code'] = '-'
  141. geo_zombie['longitude'] = '-'
  142. geo_zombie['latitude'] = '-'
  143. geo_zombie['ip'] = '-'
  144. geo_zombie['host_name'] = '-'
  145. geo_zombie['asn'] = '-'
  146. geo_zombie['latitude'] = '-'
  147. # retrieve and allocate geoip data
  148. try:
  149. ip = socket.gethostbyname(url.netloc)
  150. except:
  151. try:
  152. import dns.resolver
  153. r = dns.resolver.Resolver()
  154. from core._dns_pool import random_resolvers; r.nameservers = random_resolvers(2)
  155. a = r.resolve(url.netloc, "A") # A record
  156. for rd in a:
  157. ip = str(rd)
  158. except:
  159. self._err= "ufomsg('<font color='yellow'>[Error] [AI]</font> GeoIP: hostbyname failed for "+str(url.netloc)+"...')"
  160. return None
  161. if ip:
  162. if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$",ip):
  163. geo_zombie['ip'] = ip
  164. try:
  165. record = self._geoip.record_by_addr(ip)
  166. except:
  167. self._err= "ufomsg('<font color='yellow'>[Error] [AI] </font> GeoIP: lookup failed for "+ip+", page reload required...')"
  168. return None
  169. try:
  170. asn = self._geoasn.org_by_addr(ip)
  171. if asn is not None:
  172. geo_zombie['asn'] = asn
  173. except:
  174. geo_zombie['asn'] = 'No ASN provided'
  175. try:
  176. geo_zombie['host_name'] = socket.gethostbyaddr(ip)[0]
  177. except:
  178. geo_zombie['host_name'] = 'No hostname'
  179. try:
  180. longitude = str(float(record['longitude']))
  181. geo_zombie['longitude'] = longitude
  182. latitude = str(float(record['latitude']))
  183. geo_zombie['latitude'] = latitude
  184. except:
  185. pass
  186. try:
  187. geo_zombie['country'] = record["country_name"]
  188. geo_zombie['country_code'] = record["country_code"].lower()
  189. if record['city'] is not None:
  190. geo_zombie['city'] = record["city"]
  191. except:
  192. pass
  193. else:
  194. geo_zombie = None
  195. return geo_zombie
  196. # generates javascript for adding a new zombie with geoip data
  197. def get_js(self,z):
  198. ret = ""
  199. gz = self.geo_ip(z)
  200. if gz is not None and gz['latitude']!= '-':
  201. ret = "Zombies.add('"+str(z)+"',Array(new L.LatLng("+str(gz['latitude'])+","+str(gz['longitude'])+"),'"+str(gz['city'])+"','"+str(gz['country'])+"','"+str(gz['country_code'])+"','"+str(gz['asn'])+"','"+str(gz['ip'])+"','"+str(gz['host_name'])+"'))\n"
  202. else:
  203. ret += "dead_zombies.push('"+z+"')\n"
  204. ret += "last_zombie = '"+z+"'\n"
  205. return ret
  206. # fetches next zombie from list (using all types of zombies)
  207. def get_next_zombie(self,name):
  208. if name in self.zombies:
  209. for z in self.zombies:
  210. if name == None:
  211. return z
  212. if z == name:
  213. name = None
  214. return None
  215. else:
  216. return self.zombies[0]
  217. # ajax controller
  218. def ajax(self,pGet={}):
  219. if 'fetchgeoip' in list(pGet.keys()):
  220. if self.get_status() == "nomap":
  221. self.download_maps()
  222. return "[Info] [AI] [Control] Geoip data download! -> [OK!]<br/>"
  223. if 'stats' in list(pGet.keys()):
  224. stat='<script>$(".ufo_stat_div").show()</script>'
  225. if os.path.exists('/tmp/ufonet.html'):
  226. for x in open(r'/tmp/ufonet.html').readlines():
  227. stat = stat + x
  228. else:
  229. stat="<i>[Info] [AI] [Control] Generating statistics... -> [Waiting!]</i>"
  230. return stat+"</div>"
  231. if self.get_status() != "ok":
  232. dljs=""
  233. if self.get_status() == "nomap":
  234. dljs+="$('#ufomsg').load('/js/ajax.js?fetchgeoip=')\n"
  235. if 'doll' in list(pGet.keys()):
  236. dljs+="$('#ufomsg').load('/js/ajax.js?fetchdoll="+pGet['doll']+"')\n"
  237. dljs+="doll=new Doll('"+pGet["doll"]+"')\n"
  238. return "[Info] [AI] GeoIP data download in progress...<br><i>See console for errors</i>+<script>"+dljs+"</script>"
  239. if 'zombie' in list(pGet.keys()):
  240. zn=base64.b64decode(pGet['zombie']).decode('utf-8')
  241. nzn=self.get_next_zombie(zn)
  242. if nzn is not None:
  243. zombie=self.get_js(nzn)
  244. return """ <script>
  245. """+str(zombie)+"""
  246. ufomsg('[Info] [AI] [Control] Adding zombie: """+str(nzn)+"""...')
  247. </script>"""
  248. else:
  249. return "<script>zdone=true\nufomsg('[Info] [AI] [Control] All zombies deployed! -> [OK!]')\n </script>\n"
  250. if 'fetchdoll' in list(pGet.keys()):
  251. tn=pGet['fetchdoll']
  252. target = self.geo_ip(tn)
  253. if target is None:
  254. return "doll waiting for geoip data !"
  255. try:
  256. _lat = float(target['latitude'])
  257. _lon = float(target['longitude'])
  258. except (ValueError, TypeError):
  259. _lat = 0.0
  260. _lon = 0.0
  261. return """ doll up !<script>
  262. doll.setData(Array(new L.LatLng("""+str(_lat)+","+str(_lon)+"),'"+target['city']+"','"+target['country']+"','"+target['country_code']+"','"+target['asn']+"','"+target['ip']+"','"+target['host_name']+"'))\nufomsg('[Info] Adding target: """+tn+"""...')\ndoll.show() </script>"""
  263. if 'doll' in list(pGet.keys()):
  264. tn=pGet['doll']
  265. return """<script>
  266. doll=new Doll('"""+tn+"""')\n</script>"""
  267. return "\n"