Rusty Pearl: Remote Code Execution in Postgres Instances 

Varonis uncovers an RCE vulnerability in PostgreSQL via PL/Perl and PL/Rust. Learn how AWS RDS responded and how to secure your Postgres environment.
Tal Peleg
7 min read
Last updated May 21, 2025

As part of our ongoing data security research, Varonis Threat Labs uncovered a remote code execution vulnerability (RCE) in the PostgreSQL database software using multiple vulnerabilities found in PostgreSQL extensions.

An RCE vulnerability allows an attacker to execute arbitrary commands on the database server's underlying operating system. A successful attack could result in data exfiltration and destruction, as well as the attacker gaining an initial access vector into your network.

Background

Our researchers discovered a vulnerability in PL/Perl, a trusted language extension that is built into many default Postgres distributions. Along with a vulnerability in the Postgres extension PL/Rust, this primitive could be leveraged to an RCE, potentially causing data exfiltration, destruction, and further movement into the network.

While the vulnerability we identified was in Postgres, the Amazon Relational Database Service (RDS) environment we tested was not vulnerable to a successful attack. When we executed the vulnerability on Amazon RDS, prior to Postgres releasing patches, AWS quickly detected and stopped the activity as part of their automated protections.

Given the additional protections (like SELinux) we saw present, the attempted commands had no practical application, as no cross-database or cross-customer information was accessible before AWS shut down our database.

The Postgres team released an advisory and solution for the issue on November 14, 2024, and AWS provided the following statement on May 6, 2025:

AWS has confirmed that Amazon Relational Database Service (RDS) and Aurora services were not affected by the issue. Out of an abundance of caution, we encourage customers to upgrade to the latest version applicable to their environment. These updates are available through the [AWS Management Console, AWS CLI, or RDS API].

We would like to thank Varonis for collaborating with us on this research through the coordinated disclosure process.

- Amazon Web Services (AWS)

 

As a general recommendation, we recommend Postgres customers upgrade their PostgreSQL databases. Unmanaged standalone Postgres customers using PL/Rust should upgrade the extension to the latest version and monitor their database error logs for suspicious activity.

Continue reading to learn more about the RCE vulnerability in PostgreSQL and how our researchers performed vulnerability testing on Amazon RDS.

Hunting for vulnerabilities in Amazon RDS

Amazon RDS is a fully managed database service that supports several common relational databases, including Postgres.

It is important to remember that under the shared responsibility model, AWS manages the security of the database server and cluster, while Amazon RDS customers are responsible for configuring and securing the network access, database access control, and the data itself.

The most privileged access Amazon RDS users can have is an AWS-managed rds_superadmin role. Contrary to its name, this access is not a Postgres superadmin. The rds_superadmin cannot interact with the OS, open custom network connections, write untrusted functions, and cannot read or write files to the disk.

During our test, we initially looked at escalating privileges in an Amazon Aurora PostgreSQL 17 database. According to AWS, “Aurora includes a high-performance storage subsystem. Its MySQL- and PostgreSQL-compatible database engines are customized to take advantage of that fast distributed storage.”

This creates an area of additional interest (or curiosity) for us to search for vulnerabilities in the AWS integration of the open-source PostgreSQL with the managed service.

We chose to follow a common method of utilizing a vulnerability in a Postgres extension to interact with the system or run a command as the database superuser. The PL/Perl extension stood out to us because it is maintained by PostgreSQL and included out-of-the-box in default Postgres builds, including Amazon RDS and Amazon Aurora.

Initial primitive: Modifying environment variables with PL/Perl

PL/Perl is a trusted language extension for PostgreSQL that allows writing PostgreSQL functions and procedures in the Perl programming language, a procedural language that is commonly used for converting or processing large amounts of data.

PostgreSQL documentation states, Normally, PL/Perl is installed as a ‘trusted’ programming language named plperl. In this setup, certain Perl operations are disabled to preserve security. In general, the operations that are restricted are those that interact with the environment.

This includes file handle operations, require, and use (for external modules). There is no way to access internals of the database server process or to gain OS-level access with the permissions of the server process, as a C function can do. Thus, any unprivileged database user can be permitted to use this language.”

However, Perl has a built-in interface for reading and setting environment variables, the %ENV hash map, that is integrated into the core of the language and is not protected by Perl security modules such as “Strict” and“Opcode”, which PL/Perl relies on for security.

We wondered whether we could play with environment variables, as this often leads to interesting results. By writing a trusted plperl function, we used the following syntax to set environment variables:

A pl/perl function that sets an environment variable
00_PLPERL_FUNCTION_SET_ENV
A pl/perl function that sets an environment variable

And it worked!

The Postgres functions are executed in the context of a PostgreSQL session. When a client connects to a PostgreSQL database, the main PostgreSQL process spawns a worker process for handling the connected database session. The vulnerability in PL/Perl allowed us to set environment variables on session worker processes.

Now we needed to find a way to elevate this primitive, setting environment variables of a PostgreSQL worker process, into an RCE. There are multiple techniques to execute arbitrary code by manipulating environment variables, which we decided not to use.

  1. Environment variables can be used to set configuration parameters for Postgres and its extensions, which may result in running arbitrary code when extensions are loaded, but these are normally loaded by the main Postgres process before a worker process is forked, and so we cannot affect environment variables in that context. Moreover, sensitive SQL commands and most Postgres extensions run in sandboxed processes and run background tasks using Postgres’ built-in functionality for spawning worker processes out of the main Postgres process, so they are unaffected by our environment changes.
  2. Common environment variables such as PATH and LD_PRELOAD are known methods to execute arbitrary code. The PATH environment variable tells the OS where to search for binaries when these are executed with relative names instead of full paths, while LD_PRELOAD tells Linux processes to load a stored object from the disk before running a new process. Both these methods can be used to execute arbitrary binaries, but require writing a file on the server, and we wanted something simpler.

We searched for process invocations we could use in GitHub repositories of Postgres, and its extension available in Amazon RDS.

More from Varonis Threat Labs.
See more
Image_AttackersPlaybook_202408 (1)

PL/Rust — Utilizing PL/Perl primitive to execute code

The recently released PL/Rust is a trusted language extension allowing writing Postgres functions in the Rust programming language. It is supported on Amazon RDS, but not Amazon Aurora.

To install PL/Rust, which is a compiled language, on a Postgres instance, the language development kit must be installed on the server. It contains a compiler, rustc, and cargo — Rust’s package manager. When creating a plrust function, the extension, also written in Rust, runs the compilation process from the Postgres user’s current session, inheriting the same environment. This means that when we create a PostgreSQL function in the plrust language, `cargo build` is executed using our modified environment variables.

Cargo inherits environment variables we can control from the PostgreSQL worker process
01_SAFETY_ENV_INHERITANCE
Cargo inherits environment variables we can control from the PostgreSQL worker process

At this point, using the code execution methods mentioned previously would require uploading files to the server. With our target being RDS, this was not an option.

Instead, we looked at the environment variables that configure cargo during the compilation process. The PL/Rust extension clears several problematic environment variables before running cargo. However, many environment variables are not cleared by plrust nor are they mentioned in plrust’s documentation. One such variable allowed us to make cargo run any binary we choose.

This is PL/Rust clears some sensitive environment variables before executing cargo
02_CARGO_CODE
This is PL/Rust clears some sensitive environment variables before executing cargo

This alone did not allow for unrestricted code execution because it did not provide control over command line arguments. To bypass this, we needed a binary that executes commands from environment variables before parsing command line arguments. We specifically set it to use another binary that comes with the default build for PL/Rust, rust-gdb, used for debugging Rust applications. rust-gdb, when reading the RUST_GDB environment variable, completely ignores any command line arguments, and uses the variable’s value to determine what binary to execute, with the ability to fully control command line arguments.

In cargo (top), if a wrapper environment variable is set, run it instead of reading the rustc environment variable; In rust-gdb (bottom), if RUST_GDB is set, override any command line arguments.

03_WRPPER_CODE

In cargo (top), if a wrapper environment variable is set, run it instead of reading the rustc environment variable; In rust-gdb (bottom), if RUST_GDB is set, override any command line arguments.

This allowed us to execute code on our PostgreSQL lab.

Using a PL/Perl function that sets the command line arguments, then builds a PL/Rust function, invoking cargo, which spawns rust-gdb in place of the default rust compiler. rust-gdb in turn invokes the `id` commend provided in the RUST_GDB environment variable, producing the output in stdout, as presented by Postgres once the compilation process fails.

04_LAB_RCE

Using a PL/Perl function that sets the command line arguments, then builds a PL/Rust function, invoking cargo, which spawns rust-gdb in place of the default rust compiler. rust-gdb in turn invokes the `id` commend provided in the RUST_GDB environment variable, producing the output in stdout, as presented by Postgres once the compilation process fails.

Blog_VTL-RustyPearl_Diagram1_202505_V2 (1)

The impact of executing arbitrary code on Amazon RDS

Back in the cloud, we attempted to exploit this vulnerability in our Amazon RDS server but found that rust-gdb was missing some binaries that it expected to find and would not execute our shell command. So, we decided to use /bin/bash instead.

Although Bash would not accept the command line arguments passed to it by PL/Rust, the environment variable, BASH_ENV, tells Bash to load and run a shell initialization script before parsing the command line. This is just like how rust-gdb parses and executes the RUST_GDB variable before reading command line arguments.

The value of BASH_ENV is subjected to parameter expansion, command substitution, and arithmetic expansion before being interpreted as a file name. Setting the environment variable to ‘$(command 2>&1)’will cause command to run and print its output to stderr, which is returned from the compilation of a plrust function when it fails.

Using this method, we were able to successfully execute the command! Due to the security restrictions in place, we did not have permission to access any sensitive Amazon RDS data locations or perform network access using this vulnerability.

Nonetheless, customers should upgrade their databases and monitor error logs for suspicious activity.

PoC execution of commands, without function code, showing proof of RCE on Amazon RDS
05_RDS_RCE
PoC execution of commands, without function code, showing proof of RCE on Amazon RDS

Recommendations for Postgres customers

We recommend Postgres customers update their databases to the latest minor version at a minimum, whether they use a standalone deployment or a managed cloud deployment such as with Amazon RDS. Users of standalone databases with PL/Rust should update this extension to the latest version and remove debugging tools such as rust-gdb from production environments.

Although in this case we used plrust to execute code, other extensions, including custom extensions, may also be affected.

Database administrators should block the installation of extensions they do not use by excluding them from the rds.allowed_extensions database configuration parameter.

For example, here are instructions for blocking the installation of unused extensions in Amazon RDS:

  1. Sign in to the AWS Management Console and open the Amazon RDS console at https://console.aws.amazon.com/rds/.
  2. Select an existing PostgreSQL database you want to
  3. Under the “Configuration” tab, locate the parameter group attached to the
  4. If it is a default parameter group, you will need to create a new parameter group and attach it to the instance, since the default value for the rds.allowed_extensions parameter allows all extensions.
  5. Otherwise, you can use this guide to edit the parameter
  6. Make sure the allowed_extensions parameter includes only those extensions you approve to run on your database.

Closing

It is important to remember that while AWS provides security tools for its customers to use with RDS, it remains the responsibility of cloud customers to apply security best practices when deploying and configuring their resources, access control, network access, and data compliance and protection.

Amazon RDS provides customers with additional resources on security best practices.

Need help securing your AWS environment? Discover Varonis for AWS and schedule a demo to see it in action.

What should I do now?

Below are three ways you can continue your journey to reduce data risk at your company:

1

Schedule a demo with us to see Varonis in action. We'll personalize the session to your org's data security needs and answer any questions.

2

See a sample of our Data Risk Assessment and learn the risks that could be lingering in your environment. Varonis' DRA is completely free and offers a clear path to automated remediation.

3

Follow us on LinkedIn, YouTube, and X (Twitter) for bite-sized insights on all things data security, including DSPM, threat detection, AI security, and more.

Try Varonis free.

Get a detailed data risk report based on your company’s data.
Deploys in minutes.

Keep reading

Varonis tackles hundreds of use cases, making it the ultimate platform to stop data breaches and ensure compliance.

data-security-report-reveals-99%-of-orgs-have-sensitive-information-exposed-to-ai
Data Security Report Reveals 99% of Orgs Have Sensitive Information Exposed to AI
Varonis' 2025 State of Data Security Report shares findings from 1,000 real-world IT environments to uncover the dark side of the AI boom and what proactive steps orgs can take to secure critical information.
exploring-infrastructure-as-code:-a-technical-deep-dive 
Exploring Infrastructure as Code: A Technical Deep Dive 
See how Infrastructure as Code (IaC) enhances security, streamlines operations, and optimizes infrastructure management.
enhancing-proactive-security-across-saas-applications 
Enhancing Proactive Security Across SaaS Applications 
Discover powerful strategies to secure SaaS apps, Microsoft 365, and AI tools like Copilot. Uncover how to safeguard your data and elevate cloud security.