RCE (Remote Code Execution): Exploitation Techniques and Security Best Practices

RCE (Remote Code Execution): Exploitation Techniques and Security Best Practices

RCE (Remote Code Execution) vulnerabilities are among the most dangerous security flaws affecting modern applications and infrastructure. Their ability to enable the remote execution of arbitrary code makes them a recurring entry point in some of the most high-profile cyberattacks.

In this article, we explain how RCE works, the main vulnerabilities that can lead to it, and the techniques used during a penetration test to identify and exploit them. We also explore the possibilities available to an attacker once they have gained RCE, before outlining the key measures for preventing, containing and detecting this type of compromise.

Comprehensive Guide to RCE (Remote Code Execution)

What is RCE (Remote Code Execution)?

An RCE refers to a situation in which an attacker is able to remotely cause a target system to execute unintended instructions.

These instructions can take various forms. In some cases, the attacker directly controls a command executed by the operating system. In others, they inject code into an interpreter, hijack a template engine, trigger a chain of gadgets during deserialisation, or exploit a vulnerability in a software component.

The term ‘remote’ is key. Unlike Local Code Execution, which generally assumes that the attacker already has local access to the system, an RCE can be triggered via a remotely accessible interface: a web application, API, network service, upload function, middleware, administration component or service exposed on the Internet or on an internal network.

An RCE can sometimes be exploited without authentication. In other situations, it requires a user account, access to a specific feature, or exploitation in combination with another vulnerability.

Its severity therefore does not depend solely on the existence of the execution primitive. The context in which this execution takes place plays a decisive role.

RCE and Command Injection: Key Differences?

The terms RCE and command injection are often used interchangeably, even though they do not describe exactly the same thing.

  • A command injection is a vulnerability that allows an attacker to manipulate a command intended to be executed by the operating system. If the application passes this input to a shell and the attacker manages to inject new commands, this injection may lead to RCE.
  • RCE, on the other hand, primarily describes the result achieved: the attacker is able to remotely execute arbitrary instructions within the context of the target system.

An exploit chain can, for example, be summarised as follows: user-controlled data is incorporated into a system command, an injection becomes possible, and the attacker then executes ‘whoami’. The initial vulnerability is a command injection; the capability gained constitutes RCE.

In another application, no system command is constructed directly. The attacker exploits insecure deserialisation, finds a chain of gadgets and ultimately reaches a function capable of creating a process. The mechanism is completely different, but the result remains RCE.

This distinction is important during a security audit: RCE indicates the impact, whilst the underlying vulnerability generally explains the cause and determines the appropriate remediation.

Why Are RCE’s particularly critical?

A typical application vulnerability is often confined to the application’s context. RCE, on the other hand, can create a direct link between the application’s surface and the underlying runtime environment.

A web application running under the www-data user, a Java service operating with a service account, a serverless function with a cloud identity, or a Kubernetes pod using a service account all have specific permissions and access rights.

When an attacker gains RCE, they generally inherit the capabilities of the compromised process.

This can give them access to configuration files, application secrets, database credentials, internal APIs, environment variables, authentication tokens, services accessible only from the internal network, or technical identities enabling interaction with cloud infrastructure.

However, RCE does not automatically equate to a complete compromise.

A highly isolated service, running without privileges, devoid of secrets and subject to strict network controls, will significantly limit the impact. Conversely, an RCE obtained within a privileged process possessing sensitive credentials and extensive network access may mark the beginning of a much more significant compromise.

How Does a Vulnerability Lead to an RCE?

The technical mechanisms used to achieve an RCE can vary considerably, but most scenarios share several common elements.

First, data controlled by an attacker enters the application. This data then passes through various processing stages until it reaches a component capable of interpreting it or triggering sensitive operations.

The boundary between data and instructions then disappears.

Inputs controlled by an attacker

The data used in a chain leading to an RCE does not necessarily come from an obvious form field.

It may be introduced via a URL parameter, a JSON request body, an HTTP header, a cookie, a file, an archive, a message placed in a queue, or even data from a third-party integration.

An application may also store a value in its database before using it several hours or days later in a dangerous context. A vulnerability may therefore be of a secondary nature: the data is not dangerous at the time it is stored, but becomes so when it subsequently reaches a component capable of interpreting it.

It is therefore insufficient to analyse only the entry point. During a penetration test or code review, it is necessary to trace the path taken by the data all the way to the sensitive functions.

Sinks and execution contexts

A sink is an operation in which controlled data becomes particularly dangerous.

In the context of RCE, this may include, for example, the execution of a system command, the evaluation of code, the dynamic compilation of a template, the deserialisation of an object, the dynamic loading of a class, or the processing of a file by a vulnerable parser.

Let’s take a network diagnostic function that accepts an IP address.

The application might construct:

os.system("ping -c 4 " + user_input)

The value provided by the user is initially just a string. However, as soon as it is concatenated with a command sent to a shell, some characters may take on a specific meaning.

An input such as:

127.0.0.1 && whoami

can then cause the shell to interpret two separate commands.

The problem is therefore not merely the presence of user input. It lies in the fact that untrusted data reaches an interpreter capable of giving it an executable meaning.

Execution context and privileges

Once execution has been achieved, the vulnerable process generally determines the attacker’s initial privileges.

An RCE in a web server running under a heavily restricted user will not immediately have the same impact as an RCE in a system service running with elevated privileges.

The auditor must therefore determine several factors: which user is running the process, which files are accessible to it, which network resources it can access, which environment variables are available, and which identities or credentials are associated with the workload.

In modern architectures, these latter factors are often more important than the ability to gain root privileges on the operating system.

For example, a non-root container may have an excessively privileged Kubernetes service account. A serverless function may have an IAM role granting it access to multiple buckets, secrets or databases. A web application may contain a token within its environment that allows it to administer a third-party service.

Thus, an RCE with seemingly low local privileges may nevertheless have a major impact.

From execution to compromise

The first command executed during a penetration test is generally intended to demonstrate the existence of the vulnerability rather than to actually compromise the system.

A simple command such as ‘whoami’ or ‘id’ allows the execution to be confirmed and the initial context to be identified.

The next step is to assess the impact whilst remaining within the boundaries defined for the audit: operating system, process privileges, access to configurations, presence of secrets, internal connectivity and any accessible technical identities.

The difference between an execution primitive and a significant compromise then depends on the defences surrounding the application: the principle of least privilege, network segmentation, protection of secrets, runtime isolation, restrictions on cloud identities and monitoring.

How to Identify and Test an RCE During a Penetration Test?

The search for RCE does not involve indiscriminately sending payloads to all of an application’s parameters.

An effective approach begins with an understanding of the features and technologies that are likely to provide an execution context.

Map sensitive features

Some features deserve particular attention: diagnostic tools, document conversion, report generation, image processing, template compilation or rendering, import/export mechanisms, uploads, plugin systems, administration functions, and integrations that execute external commands.

The auditor also seeks to identify the technologies used. Knowing the programming language, framework, template engine, operating system or parsing libraries helps to guide the testing.

During a white-box audit, this phase can be expedited by searching the source code for potentially dangerous functions: shell calls, `eval`, native deserialisation, dynamic template creation or process launch.

Validate the execution with minimal impact

The initial aim is to demonstrate the vulnerability using the least intrusive proof possible.

In a command injection attack, the auditor can use a command that produces an easily identifiable result.

In an SSTI, an operation such as {{7*7}} may be sufficient to demonstrate that the expression is evaluated on the server side.

In a deserialisation context, a controlled modification or a harmless callback may help to confirm the behaviour before searching for a more complex string.

This step-by-step approach minimises the risk of disruption and avoids unnecessarily triggering destructive actions.

RCE with direct response

The simplest case involves an execution where the output is returned in the application’s response.

For example, the attacker sends a command to display the current user and observes the value returned. This type of RCE makes investigations much easier, as each command can produce a result that is directly visible.

However, not all vulnerabilities work in this way.

Blind RCE and time-based validation

In a blind RCE, the command is executed but its output is never displayed. The auditor can then look for a measurable indirect effect.

A classic example is to deliberately induce a delay: sleep 5

If the response consistently takes around five extra seconds when the payload is sent, there is a signal of interest.

However, a single measurement is not sufficient. Network latencies, caching mechanisms or asynchronous processing can produce false positives. It is preferable to reproduce the behaviour several times and compare different durations.

Out-of-band validation

Some RCEs produce neither a visible output nor a usable delay. Out-of-Band (OOB) validation can therefore be used within the controlled environment of a penetration test.

The principle involves triggering a DNS or HTTP request from the server to an infrastructure controlled by the auditor. Observing this interaction confirms that the command or code has indeed been executed.

This technique is particularly useful for blind deserialisation, asynchronously executed injections or certain file processing operations.

Common RCE Exploitation Examples

Command injection: from user input to RCE

Command injection is one of the most direct routes to an RCE.

It occurs when an application constructs a system command using user-supplied data without ensuring sufficient separation between the arguments and the syntax interpreted by the shell.

How does command injection work?

Let’s consider a feature that allows you to test connectivity to a machine.

The server runs:

<?php
$host = $_GET['host'];
system("ping -c 4 " . $host);
?>

A legitimate request containing:

127.0.0.1

results in:

ping -c 4 127.0.0.1

However, a value such as:

127.0.0.1 && whoami

may cause the shell to execute ‘ping’ and then ‘whoami’ in succession.

The application can no longer distinguish between the part that was intended to represent an IP address and the part that introduces new instructions.

Shell operators and differences between environments

Shells support numerous operators that allow commands to be chained or modified: ;, &&, ||, pipes, command substitutions or line breaks.

However, the exact behaviour depends on the shell and the target system.

Syntax that is valid in Bash is not necessarily the same as that in cmd.exe or PowerShell. This difference is important during testing: an ineffective payload does not necessarily mean that the parameter is secure.

Examples of vulnerable code

  • In Python: os.system(‘ping -c 4 ’ + user_input)
  • In Node.js: exec(‘ping -c 4 ’ + host)
  • In PHP: system(‘ping -c 4 ’ . $_GET[“host”]);

The common feature here is not the language itself, but the dynamic construction of a command, part of which is controlled by the user.

A preferable Python implementation might use:

subprocess.run(
    ["ping", "-c", "4", user_input],
    shell=False,
    check=False
)

The arguments are separated here and are not interpreted as a new shell syntax.

This does not mean that the value of `user_input` does not need to be validated, but it significantly reduces the risk of data being interpreted as an additional command.

Blind command injection

Sometimes, the command output is not included in the HTTP response.

A payload that causes a delay can then be used to determine whether the input actually reaches the shell.

Similarly, a controlled out-of-band (OOB) interaction can confirm that the system is capable of initiating external communication following the processing of the payload.

Tests must be adapted to the context: certain commands may be filtered, unavailable in the system image or executed in a highly restricted environment.

Why blacklists don’t work properly

A defence that relies solely on removing ‘;’ or ‘&&’ is rarely sufficient.

Shells have numerous syntaxes; several layers of decoding may be involved, and the rules may differ depending on the operating system.

Encoding transformations, line breaks or shell-specific constructs can bypass a defence that only checks for a few characters.

The correct strategy is therefore to eliminate the need for shell interpretation, rather than attempting to list all potentially dangerous syntaxes.

How to prevent command injections?

Where possible, the application should use native APIs rather than launching system utilities.

If it is truly necessary to run an external programme, the arguments must be provided separately without going through a shell. A strict allowlist must limit the permitted values where the functional domain allows it.

The application must also run with minimal privileges. This way, even if a development error remains, the impact of any potential execution is reduced.

Insecure deserialisation and RCE

Insecure deserialisation represents a far less intuitive path to code execution.

The attacker does not necessarily inject a command directly. Instead, they attempt to manipulate the object reconstruction process in order to cause unexpected behaviour within the application.

What is deserialisation?

Serialisation transforms an object into a representation that can be stored or transmitted. Deserialisation performs the reverse operation and reconstructs the object at runtime.

Applications use these mechanisms to manage sessions, caches, message queues, inter-service communications or persistent data.

The problem arises when an application accepts a user-controlled serialised structure and reconstructs it using a mechanism capable of instantiating classes or automatically executing certain behaviours.

Why can deserialising an object execute code?

Some languages have methods or callbacks that are automatically called during the reconstruction, access or destruction of objects.

In PHP, certain magic methods may be involved. Java, in particular, uses `readObject()`. Python’s `pickle` can reconstruct objects by invoking functions specified in their representation.

Data that appears to be merely a description of an object can therefore trigger a sequence of application calls.

Dangerous deserialisation does not automatically constitute RCE

This distinction is fundamental. The presence of `unserialize($userControlledData);` constitutes dangerous behaviour, but does not necessarily mean that an exploitable RCE chain exists immediately.

The attacker must generally identify the classes available in the application or its dependencies and determine whether any of them can be combined to achieve a sensitive operation.

Gadget chains

A gadget is an existing class or method that exhibits behaviour useful to an attacker when called under certain conditions.

Several gadgets can be chained together to form a gadget chain.

An initial class may automatically trigger a method. This method may manipulate another object, which in turn ends up invoking a function capable of writing a file, loading code or creating a process.

The attacker therefore does not necessarily provide their own code. Instead, they exploit components already present in the application.

This is one of the reasons why large Java, PHP or .NET applications can present a particularly large attack surface: their dependencies sometimes provide a large number of classes that can be used as gadgets.

Example using Python’s pickle

Behaviour such as:

import pickle

data = request.cookies.get("session")
obj = pickle.loads(data)

is particularly risky if the cookie can be controlled by an attacker.

Pickle is not designed to be a secure data format when dealing with untrusted input. Reconstruction may trigger the invocation of Python functions.

To transmit user-supplied data, it is generally preferable to use a purely declarative format that has been validated against a suitable schema.

Signature and integrity of objects

Cryptographically signing serialised data can prevent an attacker from modifying it if they do not have the key.

Whilst this measure may be useful, it does not in itself make a dangerous deserialisation mechanism secure.

If another untrusted source can provide a serialised structure, if the key is compromised, or if the application itself signs malicious objects via another feature, the risk re-emerges.

The best protection therefore remains to avoid deserialising native objects from untrusted sources whenever it is not absolutely necessary.

Server-Side Template Injection (SSTI) and RCE

Server-side template engines are used to dynamically generate HTML pages, emails, documents or various types of content.

They are normally designed to take a template defined by the developer and data to be inserted into that template.

An SSTI occurs when data controlled by an attacker becomes part of the interpreted template itself.

Data in a template or a controlled template: a key difference

A typical construction might be:

template = Template("Bonjour {{ name }}")
return template.render(name=user_input)

The variable name is treated as data. An unsafe construct would be:

template = Template("Bonjour " + user_input)
return template.render()

If user_input contains {{7*7}} and the response contains 49, this means that the user data has been incorporated into the template engine’s language.

From assessment to exploitation of the RCE

The ability to perform a multiplication does not, of course, in itself constitute an RCE. The auditor must determine which primitives are accessible in the engine in question.

Some engines allow access to internal objects, functions, classes or the application context. In certain environments, these capabilities can be used to access functions that enable file access or the execution of processes.

This progression depends heavily on the template engine, its version, its configuration and the objects exposed to rendering.

There is therefore no universal SSTI payload.

Sandboxing and limitations

Some engines have sandboxing mechanisms designed to restrict the attributes or functions accessible from templates.

This protection is useful but should not be used to justify the arbitrary evaluation of user-supplied templates. The history of several engines shows that unexpected primitives, indirectly exposed objects or sandboxing workarounds may arise.

The most robust strategy remains to prevent users from defining their own expressions that are executed by the engine.

File uploads and RCE

Upload features are commonplace: profile pictures, documents, PDFs, archives, videos or business-specific exports. The mere fact that a user can upload a file does not constitute a vulnerability.

The risk depends on what the system does with that file afterwards.

Direct execution of an uploaded file

The most common scenario involves an application that stores user files in a directory accessible to the web server, where script execution is permitted.

A vulnerable handler could simply copy the provided file to:

/uploads/

If the web server then interprets a PHP file located in this folder, an attacker capable of sending a script could gain execution capabilities.

Remediation is therefore not limited to checking file extensions.

Content should be stored outside executable directories and, where possible, served by a component that is unable to interpret scripts.

Double file extensions and MIME types: beware of oversimplifications

A file extension such as shell.php.jpg is not automatically executed as PHP. Its behaviour depends on the web server’s configuration, the handlers associated with the extensions, any rewrite rules, and the processing system used.

A double extension is therefore a potential workaround in certain environments, but not an intrinsic feature of web servers.

The same principle applies to the header:

Content-Type: image/jpeg

As this field is controlled by the client, it does not in itself constitute proof that the file is actually an image.

The server must check for the expected format and must never rely solely on the information provided by the user.

The RCE may occur even if the file is not executable

Modern applications frequently process files after they have been uploaded. An image may be resized, a PDF converted, an archive extracted, and so on.

The file may then be passed to libraries such as image engines, PDF parsers, multimedia tools or other native components.

If any of these components contains an exploitable vulnerability, a specially crafted file can trigger code execution during processing.

In this situation, the file is never executed directly by the web server. RCE occurs within the parsing or transformation chain.

From file upload to RCE

Some file upload vulnerabilities can also form part of a chain leading to code execution.

An LFI initially allows a local file to be read or included. Depending on the language and configuration, this primitive can sometimes be combined with a write capability, a temporary file, a session or another controlled source of content in order to trigger code execution.

In certain environments, an RFI may allow the direct inclusion of a remote resource.

These scenarios are highly platform-dependent and must be distinguished from simple arbitrary file reading.

From an SQL injection to RCE

An SQL injection initially gives an attacker the ability to manipulate the queries sent to a database.

It does not necessarily involve the execution of code on the system.

However, some DBMSs have features that allow interaction with the operating system, writing files, executing extensions or loading components.

Where the account used by the application has excessive privileges, an SQL injection can therefore sometimes be exploited to execute code.

This illustrates once again the importance of the principle of least privilege.

An application that only needs to read and modify a few tables should not connect to its database using an account with administrative or system-level capabilities.

Other paths that may lead to an RCE

It would be impossible to draw up an exhaustive list of vulnerabilities that could lead to an RCE.

Memory corruption issues in services written in C or C++, poorly isolated plugin systems, CI/CD pipelines that allow scripts to be executed, dynamic compilation mechanisms, or certain chains combining file writing and module loading can also result in execution capability.

The common thread is therefore not a specific technology.

It is always a matter of identifying the moment when data or an action controlled by the attacker crosses a boundary and reaches a mechanism powerful enough to execute unintended instructions.

What are the Potential Impacts of an RCE?

Obtaining an RCE is often just the start of a chain of attacks. The possibilities depend entirely on the compromised environment.

Identify the execution context

The first relevant pieces of information generally concern the runtime itself: user, operating system, current directory, environment variables, processes, accessible files and network interfaces.

The aim is to understand the security boundaries surrounding the process. RCE achieved within an isolated application does not necessarily provide visibility into the rest of the infrastructure.

Application secrets

Applications frequently require secrets to function. These may be found in configuration files, environment variables, secret management mechanisms, credentials files or mounted volumes.

These include, in particular, database credentials, API tokens, signature keys or secrets used to communicate with other services.

An RCE can therefore be used to retrieve information that grants access to resources more sensitive than the initial server.

Access to the internal network

An application exposed to the internet is often connected to services that are not directly accessible from the outside: databases, internal APIs, administration services, caches, queues or business systems.

Compromising the server therefore changes the attacker’s network position.

Appropriate segmentation must prevent a workload from communicating with any internal system without a functional justification.

RCE in a container

An RCE in a containerised application initially corresponds to a compromise of the container’s context, and not necessarily of the host node.

The impact depends in particular on the user account used, Linux capabilities, mounted volumes, any access to sensitive sockets, and the restrictions applied by the runtime.

A non-root container with a heavily restricted filesystem and no additional capabilities significantly reduces the post-exploitation attack surface.

Conversely, a privileged container or one with sensitive mounts can turn an application compromise into a much broader issue.

RCE in Kubernetes

In Kubernetes, it is also necessary to analyse the identity assigned to the pod.

A service account may have permissions allowing it to read certain Secrets, query the Kubernetes API or manipulate resources.

An application can therefore run without Linux-level privileges whilst still having a Kubernetes identity with excessive privileges.

Security then relies as much on RBAC as on operating system controls.

It is recommended to limit the permissions of service accounts, disable token mounts when they are not required, and segment communications between workloads.

RCE and cloud environments

In a cloud infrastructure, workloads often have an identity that enables them to interact with the provider’s APIs.

An RCE can therefore allow an attacker to indirectly exploit these permissions.

The risk depends on the configuration of the identity in question: access to object storage, reading secrets, service administration, deployment functions or other IAM permissions.

Metadata services are also a point of concern, although their operation and security measures vary depending on the provider and configuration.

The aim should therefore not be solely to prevent an RCE, but to ensure that the compromise of a single workload does not automatically grant access to the entire cloud environment.

How to Prevent RCE Vulnerabilities?

Effective prevention of RCE relies on a defence-in-depth strategy. It is necessary to simultaneously reduce the opportunities for obtaining an execution primitive, limit the impact of any potential compromise, and improve detection capabilities.

Prevent data from becoming instructions

The primary objective is to maintain a strict separation between user-supplied data and execution mechanisms.

  • System commands should be avoided where a native API can fulfil the same function.
  • Where an external process is essential, arguments must be passed separately without being interpreted by a shell.
  • Template engines must be supplied with variables rather than templates constructed from user input.
  • The `eval` function or equivalent must not be applied to untrusted data.
  • Deserialisation formats capable of reconstructing executable objects must be replaced by structured and strictly validated representations when processing external data.

Secure uploads and parsers

Files submitted by users must be treated as untrusted. Checks must cover the expected format, size, content and behaviour of the processing pipeline.

Files should not be stored in directories that allow scripts to be executed. Conversion, extraction or parsing operations must be isolated as much as possible, and the libraries used must be kept up to date.

Manage dependencies

Modern applications rely heavily on third-party libraries, frameworks, plugins and external services. Many RCE vulnerabilities therefore stem not from the application’s own code, but from vulnerable dependencies integrated into the software stack. Major incidents such as Log4Shell have demonstrated how a single vulnerable library can rapidly expose thousands of systems to RCE.

Attackers frequently target obsolete frameworks, vulnerable deserialisation libraries, exposed template engines, insecure media processing tools, or unpatched server components. Effective dependency management therefore plays a critical role: continuously inventorying dependencies, monitoring newly disclosed vulnerabilities, automating dependency scanning, removing unnecessary packages, and promptly applying security patches.

Apply the principle of least privilege

A vulnerable process should only have access to the resources necessary for its function.

  • Applications must not run as root without justification.
  • Service accounts must have limited permissions.
  • Access to databases, internal APIs and cloud resources must be restricted.

This approach does not eliminate the vulnerability, but it limits the attacker’s ability to turn an initial execution into a complete compromise.

Isolate workloads

Isolation mechanisms – containers, sandboxes, namespaces, system profiles, network policies – help to reduce the interactions available following an exploit.

A file-processing workload, for example, can be isolated from the internet and sensitive resources.

A document rendering service does not necessarily need to communicate with the production database.

Segmentation must reflect the actual needs of the components rather than an implicit trust in the entire internal network.

Secure Kubernetes and the cloud

Containers should be run as non-privileged users where possible and with a minimum set of capabilities.

Kubernetes workloads must have service accounts specifically tailored to their needs and restrictive RBAC.

Inter-service communications must be restricted using appropriate network policies.

In the cloud, workload identities must adhere to the principle of least privilege, and access to secrets or sensitive services must be strictly controlled.

An attacker who has compromised a single application should not be able to use that application’s identity to administer a significant part of the infrastructure.

Implement behavioural monitoring

There is no such thing as perfect prevention.

A realistic defence strategy therefore assumes that an unknown or unpatched vulnerability may one day be exploited.

Monitoring must be able to identify behaviour that is inconsistent with the normal operation of applications: the creation of shells, new processes, access to system tools, unusual outbound communications, repeated parsing errors or abnormal interactions with cloud APIs.

The combination of application logs, EDR, network telemetry and cloud platform logs significantly increases detection capabilities.

Conclusion

Remote Code Execution is not a single technical mechanism. It generally represents the culmination of a chain of events in which data or an action controlled by an attacker reaches a context capable of interpreting it as an instruction or triggering a dangerous operation.

Command injection can enable direct control of a shell. An SSTI can gradually expose internal primitives, leading to the creation of a process. Deserialisation can hijack classes present within an application. A file can be interpreted directly or exploit a vulnerable parser. Finally, a third-party component can introduce an RCE independently of the code developed by the organisation.

However, the actual severity does not end with gaining execution.

It depends on what the compromised process is authorised to do: read secrets, access the internal network, use a cloud identity, manipulate Kubernetes resources or interact with other systems.

Preventing RCE must therefore be approached at several levels. Secure development must prevent untrusted data from reaching execution primitives. Dependency management must reduce exposure to known vulnerabilities. The principle of least privilege and isolation must limit the impact of any potential compromise. Finally, behavioural monitoring must enable the identification of exploitation attempts that might nevertheless manage to breach these first lines of defence.

It is this combination of prevention, impact mitigation and detection that enables the risk associated with RCEs to be effectively managed.