Mitigating Remote Code Execution (RCE) via eval block syntax flaws in Custom Perl Implementations
Understanding the `eval` Vulnerability in Custom Perl
Many custom Perl implementations, particularly those developed in-house or by less experienced teams, often leverage the `eval` construct for dynamic code execution. While powerful, `eval` is a double-edged sword. When used with untrusted input, it becomes a direct gateway for Remote Code Execution (RCE). The core issue lies not in `eval` itself, but in how it’s invoked. If any part of the string passed to `eval` originates from an external source – be it user input, network data, or even configuration files not strictly controlled – an attacker can inject malicious Perl code that will be executed with the privileges of the running Perl script.
Consider a common, albeit dangerous, pattern where a script might dynamically construct a Perl expression based on user-provided parameters. A naive implementation might look like this:
Illustrative Vulnerable Code Snippet
use strict;
use warnings;
# Assume $user_input is obtained from a web request, e.g., CGI parameter
my $user_input = $ENV{'QUERY_STRING'}; # Highly insecure example
# Vulnerable eval block
my $result = eval($user_input);
if ($@) {
# Handle evaluation error
print "Error: $@\n";
} else {
print "Result: $result\n";
}
In this simplified example, if an attacker sends a query string like "system('rm -rf /')", the `eval` function will execute the `system` call, leading to catastrophic data loss. The attacker doesn’t need to upload a file or exploit a buffer overflow; they simply provide the code to be executed.
Identifying and Analyzing `eval` Usage
The first step in mitigation is identification. Static analysis tools can help, but manual code review is often indispensable for custom implementations. Look for direct calls to `eval()`. More insidious are indirect calls, where a variable holding a string is passed to `eval`. Pay close attention to any string concatenation or manipulation that precedes an `eval` call, as this is where untrusted data is most likely to be introduced.
A common pattern to search for during code audits is:
# Search for 'eval(' in your codebase
grep -r 'eval(' /path/to/your/perl/code
However, this is a blunt instrument. A more refined approach involves understanding the data flow. Trace where the arguments to `eval` originate. If any part of the string can be influenced by external input (HTTP requests, database entries, file uploads, environment variables, command-line arguments), it’s a potential vulnerability.
Mitigation Strategies: Sandboxing and Input Sanitization
Directly removing `eval` might break existing functionality. Therefore, mitigation often involves a combination of rigorous input sanitization and, where possible, sandboxing the execution environment.
Strategy 1: Strict Input Sanitization
The most straightforward approach is to ensure that any data passed to `eval` is strictly validated and sanitized. This means defining an “allow-list” of acceptable characters, patterns, or even complete expressions. Anything not explicitly permitted should be rejected or escaped.
For example, if `eval` is intended to parse simple arithmetic expressions, you would:
use strict;
use warnings;
my $user_input = $ENV{'QUERY_STRING'}; # Still insecure, for illustration
# Define allowed characters for arithmetic expressions
my $allowed_chars = qr/^[0-9+\-*/().\s]+$/;
if ($user_input =~ /$allowed_chars/) {
my $result = eval($user_input);
if ($@) {
print "Error: $@\n";
} else {
print "Result: $result\n";
}
} else {
print "Invalid input detected.\n";
}
This approach is effective but can be brittle. Attackers are adept at finding ways to bypass simple regexes, especially if the allowed syntax becomes complex. For instance, if the input is expected to be a Perl hash key, and the attacker can inject characters that break out of string context, they might achieve RCE.
Strategy 2: Using `Safe` Module for Sandboxing
Perl’s `Safe` module provides a more robust solution by creating isolated execution environments (compartments). Code executed within a compartment has restricted access to built-in functions and modules. This significantly reduces the attack surface.
Here’s how you might use `Safe` to evaluate user input:
use strict;
use warnings;
use Safe;
my $user_input = $ENV{'QUERY_STRING'}; # Example input source
# Create a new Safe compartment
my $compartment = Safe->new();
# Restrict available methods and functions.
# This is crucial. By default, many dangerous functions are available.
# You must explicitly allow what is needed.
# For example, to allow basic arithmetic and string operations:
$compartment->permit(qw(
+
-
*
/
%
==
!=
<
>
<=
>=
==
!=
&&
||
!
&
|
^
~
<<
>>
( )
.
undef
print
length
substr
chr
ord
uc
lc
index
rindex
sprintf
quotemeta
scalar
defined
ref
bless
isa
package
require
use
no
sub
return
my
our
local
if
elsif
else
for
foreach
while
until
next
last
redo
goto
eval
bareword_as_string
bareword_as_symbol
glob
caller
keys
values
each
shift
pop
push
splice
grep
map
sort
reverse
qw
q
qq
qx
qr
split
join
chomp
chop
hex
oct
atan2
cos
sin
exp
log
sqrt
int
abs
rand
time
localtime
gmtime
sleep
getc
open
close
read
write
seek
tell
eof
fileno
select
pipe
fork
wait
waitpid
kill
alarm
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getrusage
gettimeofday
settimeofday
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
getaddrinfo
getnameinfo
socketpair
sendmsg
recvmsg
sendto
recvfrom
setsockopt
getsockopt
getpeername
getsockname
bind
listen
accept
connect
shutdown
select
poll
epoll_create
epoll_ctl
epoll_wait
kqueue
kevent
pipe
fork
vfork
exec
system
popen
pclose
signal
alarm
sleep
usleep
nanosleep
getitimer
setitimer
getrusage
gettimeofday
settimeofday
getppid
getpgrp
setpgrp
getpriority
setpriority
getrlimit
setrlimit
getuid
geteuid
getgid
getegid
getlogin
gethostid
sethostid
gethostname
sethostname
getdomainname
setdomainname
getgroups
setgroups
getpeername
getsockname
getpeername
getsockopt
setsockopt
getprotoent
getnetent
gethostent
getservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnetbynumber
gethostbyaddr
getservbyport
socket
bind
listen
accept
connect
send
recv
shutdown
select
sysopen
sysread
syswrite
sysseek
sysclose
ioctl
fcntl
stat
lstat
chmod
chown
utime
link
symlink
readlink
unlink
rename
mkdir
rmdir
chdir
fchdir
getcwd
getwd
umask
mktemp
tmpnam
tempnam
tempfile
tempdir
opendir
readdir
closedir
telldir
seekdir
rewinddir
glob
crypt
passwd
shadow
group
getpwnam
getpwuid
getgrnam
getgrgid
getprotoent
getnetent
gethostent
getservent
setprotoent
setnetent
sethostent
setservent
endprotoent
endnetent
endhostent
endservent
getprotobyname
getnetbyname
gethostbyname
getservbyname
getprotobynumber
getnet