A Synack Red Team member discovered a remote code execution (RCE) vulnerability while testing a Synack target. In this edition of Exploits Explained, SRT member Marouane “Duty1g” Belabbassi recounts how an unsafe eval() call in the process endpoint allowed dynamic backend routing based entirely on user input.
This post explores the steps that led to a successful RCE exploit, ultimately resulting in arbitrary command execution on the server.
Identifying the Vulnerability
In every web security assessment, the first step is reconnaissance—understanding the structure and behavior of the application. During web enumeration, I encountered a JavaScript file with the following URL:https://redacted.com/javascript/Admin.jsx
Within the file, I observed a pattern in which the frontend dynamically constructs backend routes. This indicated a form of dynamic routing, where user-supplied values influenced which backend functionality was invoked. This approach, while flexible, is inherently dangerous when paired with insecure processing.
Specifically, the routing system exhibited unusual behavior:
alter_item_grid(gridId, ..., "?
a=open:Admin:RoleAlt:");
---
change_status_item_grid(gridId, ..., "?
a=process:Admin:RoleStatus:");
This pattern hinted at a backend routing mechanism that accepted user input to dynamically resolve class and method names. Such behavior strongly suggested the use of eval() or similar unsafe constructs, making the application susceptible to manipulation.
Early Attempts at Exploitation
My initial efforts focused on attempting to invoke the most obvious method-class pair:
https://redacted.com/?a=process:Admin:RoleStatus
However, no response was generated—there were no error messages, and the request failed to produce any output. The absence of errors indicated that the backend was not revealing any useful information, leaving me with no direct confirmation of the presence of exploitable code.
Abusing the Routing System
Once it became clear that the application was dynamically routing backend logic based on the a parameter, it opened a door for abuse. The routing system trusted user input to select classes and methods without verification, which meant it could be manipulated to execute unexpected code paths. By crafting variations of the a=process:<Class>:<Method> pattern, I was able to probe internal logic, identify accessible classes, and eventually chain this behavior with PHP gadgets to achieve full code execution.
Gadget Collecting
After further investigation, I began collecting potential gadgets—built-in PHP classes with methods that could be invoked without constructor arguments. Potential candidates included:Exception:getMessage
stdClass:__toString
Error:getMessage
ReflectionFunction:__toString
DateTime:format
I tested the following URLs for noticeable changes in response behavior, such as response length and page content:https://redacted.com/?a=process:Exception:getMessage;//
https://redacted.com/?a=process:stdClass:__toString;//
https://redacted.com/?a=process:ReflectionFunction:__toString;//
https://redacted.com/?a=process:DateTime:format;//
In each case, I monitored response sizes and status code to infer backend behavior. Unlike invalid combinations that returned empty or identical-length responses, some of these gadget requests showed subtle but consistent increases in content length or layout shifts—clear indicators that the input was being processed.
It was reliably invokable without constructor arguments, triggered a change in the server’s response structure and—most importantly—preserved syntax compatibility for further payload chaining. I selected it for exploitation.
Potential Server Parsing Logic
The backend server likely parses and executes the a parameter through a mechanism resembling the following pseudo-PHP code:
if (isset($_GET['a'])) {
$parts = explode(':', $_GET['a']);
if ($parts[0] === 'process') {
$class = $parts[1] ?? '';
$method = $parts[2] ?? '';
[Unsafe: Executes user input without validation or sanitization]
eval("\$result = (new $class())->$method();");
}
}
This method is dangerously susceptible to arbitrary code execution due to the lack of input validation, constructor checks, or allowlisting. The raw eval() on attacker-controlled input represents a significant security flaw.
Triggering Remote Code Execution (RCE)
After confirming the existence of exploitable gadgets, I proceeded to construct a payload that would trigger RCE on the server. I crafted the following URL:https://redacted.com/?a=process:stdClass:__toString;system('id');die();//
The stdClass::__toString magic method satisfied the required syntax for the eval() call.
The system(‘id’) command executes the server’s id function, returning the user context.
The die() function halts further execution, ensuring the output is returned cleanly.
This exploitation successfully resulted in remote command execution with the privileges of the web server user.
Alternative Exploit Path: Method Enumeration
While inspecting the login HTML form, I discovered a code snippet referencing a backend route:<form method="POST" action="/?a=process:Security:authUser" name="frm_login">
This revealed the presence of the Security:authUser method, which could be chained with malicious payloads. By leveraging this route, I was able to bypass blind guessing and directly construct a valid request for exploitation.https://redacted.com/?a=process:Security:authUser();system('id');die();//
This path served as an alternative to gadget-based RCE by targeting a known and callable backend function exposed through the frontend.
Key Takeaways
Avoid using user-supplied input: Even seemingly benign routing mechanisms can lead to serious vulnerabilities when combined with unsafe function calls.
Implement an allowlist over a denylist approach: It is crucial to restrict the classes and methods that can be invoked via user input, rather than attempting to filter out malicious patterns.
Exploiting built-in gadgets: Attackers often explore built-in PHP methods and magic methods to find vectors for arbitrary code execution.
Use timing and behavior analysis in blind tests: In the absence of error messages, look for changes in server response times or application behavior to confirm successful exploitation.
Defense in depth: Ensure comprehensive input validation, enforce least-privilege execution, and employ robust logging mechanisms to detect and mitigate attacks.
Thank you for reading.
Follow me on X @duty_1g. Feel free to reach out with any questions. If you’re interested in joining the Synack Red Team, I’m always happy to offer guidance to fellow cybersecurity enthusiasts.
Be sure to follow Synack and the Synack Red Team on LinkedIn for later additions to this series.