Mojolicious: Executing code with url_escape()
Let’s look at the url_escape function in the Mojolicious web framework for Perl 5, and how it can be used to evaluate code through the second argument of the function.
TLDR;
use Mojo::Util qw(url_escape);
url_escape('some-stuff', '\w](?{die()})|[a'); # Dies!
The url_escape
function
This function is a part of the very useful Mojo::Util library.
It accepts a list of two arguments:
- A string to escape
$str
- An optional pattern that determines what characters to escape
$pattern
Some usage examples:
url_escape('foo/bar.baz'); # Returns "foo%2Fbar.baz"
url_escape('foo/bar.baz', '/.'); # Returns "foo%2Fbar%2Ebaz"
This is the implementation in Mojolicious v8.13:
sub url_escape {
my ($str, $pattern) = @_;
if ($pattern) {
unless (exists $PATTERN{$pattern}) {
(my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g;
$PATTERN{$pattern}
= eval "sub { \$_[0] =~ s/([$quoted])/sprintf '%%%02X', ord \$1/ge }"
or croak $@;
}
$PATTERN{$pattern}->($str);
}
else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge }
return $str;
}
When url_escape
is called with a $pattern
it hasn’t seen before, it will
generate, string eval and cache a function to handle this specific pattern.
Subsequent calls with the same $pattern
will re-use the generated code.
So, an input parameter to the function is interpolated into a string which is evaled… Interesting! Let’s try to inject some code. :D
Quoting
(my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g;
The $pattern
input value is quoted with the expression s!([/\$\[])!\\$1!g;
and stored in $quoted
before it’s interpolated into the string of code to be
evaled, preventing us from ending the pattern part of the string substitution.
Damn… So close!
Code Subpattern
It doesn’t appear that we can (easily) break out of the substitution expression, so let’s try something inside the expression instead, like (?{ code }).
code subpattern: A regular expression subpattern whose real purpose is to execute some Perl code—for example, the (?{…}) and (??{…}) subpatterns.
Very nice, but this can be a bit dangerous, as the perlre doc points out:
[..] for reasons of security, use re ‘eval’ must be in scope. This is to stop user-supplied patterns containing code snippets from being executable. In situations where you need to enable this with use re ‘eval’ , you should also have taint checking enabled. Better yet, use the carefully constrained evaluation within a Safe compartment. [..]
Normally, adding (?{ code })
to a pattern through string interpolation would
result in a fatal error, unless use re 'eval'
is set. But since the code we
want to tamper with is string evaled, these restrictions do not apply.
Crafting the $pattern
argument
This is what we want to execute:
die()
Let’s wrap die()
in a code subpattern and add some stuff to both sides of it
to make it match and compile.
'\w](?{die()})|[a'
Passing this $pattern
to url_escape
makes it generate code that looks like this:
sub { $_[0] =~ s/([\w](?{die()})|[a])/sprintf '%%%02X', ord $1/ge }
So we’re left with:
url_escape('some-stuff', '\w](?{die()})|[a'); # Dies!
Is this a vulnerability?
The Mojolicious team reviewed a PoC before this post and concluded that it was not a security vulnerability.
This behaviour is not exploitable unless the function is used incorrectly. It
could for example allow for vulnerability chaining if a bug is found that allows
an attacker to control the $pattern
argument.
How to create vulnerable code:
-
The most obvious way to create a vulnerability is to expose the second
$pattern
argument ofurl_escape
directly to user input. Game Over. -
A more subtle way is by having another function return a list an attacker can control as arguments to
url_escape
.use Mojo::Util qw(url_escape); sub good { return 'some-stuff' }; # returns 1 string sub evil { return ('some-stuff', '\w](?{die()})|[a') }; # returns 2 strings url_escape( good() ); url_escape( evil() ); # Dies!
Variations of this belong to a class of vulnerabilities in Perl web applications that are generally well known.
Takeaways
Regular expressions in Perl are very powerful, functions that return lists can be scary, and library functions can have hidden features.