Behind the Scenes: Securing In-House Execution of Unsafe Third-Party Executables
Many companies rely on third-party native executables for functionality like image and video processing. However, many of these tools are written in C or C++ and were not designed with security in mind. When a malicious user uploads a specially crafted file, it can lead to arbitrary command execution via a buffer overflow or command injection, arbitrary file read or write, and other bad outcomes.
Mukul recommends a three step defense-in-depth process for mitigating these risks.
1. Profile the executable
Do your homework:
What’s the security history of the code base? Any systemic issue classes? Do they fuzz it regularly?
Audit it for dangerous functionality - is there any unnecessary functionality you can disable?
Threat model the app’s attack surface - understand how data flows through it as well as its storage and networking requirements.
Profile the executable and determine the system calls it requires:
Install the latest version in a VM and run its test suite or run it with the specific commands you’ll be using in producting, using known good data.
Observe the system calls it makes using a tool like strace and then build a seccomp-bpf profile that blocks any syscall beyond the set required to minimize the kernel attack surface exposed to the tool.
Here there be dragons
As one of the audience questions pointed out, and based on my personal experience, this is easier said than done: creating the policies is hard (making sure you exercise all of the functionality you’ll require is nontrivial), and in some cases the tool may require a number of dangerous syscalls, like
2. Harden the application layer
We also want to make the application itself as resistant to attack as possible.
Magic byte analysis: parse the initial few bytes of input file and match it against a known set of file signatures. This can be massively complex in practice, as file formats may be complex or polymorphic, with other file types nested inside.
Input validation - the canonical “security thing you should do,” but still applies here.
Is there some expected structure or input restrictions you can apply immediately and reject inputs that don’t fulfill them? For example, a field may be a number, so restrict the input to
3. Secure the processing pipeline (harden your infrastructure, use secure network design)
Run the tool as an unprivileged user in an ephemeral container (to limit persistence).
Restrict the app’s capabilities as much as possible by doing syscall filtering, drop privileges before running it, and using Linux namespaces and filesystem jails (like chroot). Google’s light-weight process isolation tool nsjail can also be useful.
Implement a secure network design: so that even if a compromise happens, the attacker has limited ability to pivot and access other systems.
Example secure network design
Example process from end to end