Rating: 5.0
# Newsletter [200]
We are presented with a email newsletter form along with the source code
```
public function subscribe(Request $request, MailerInterface $mailer)
{
$msg = '';
$email = filter_var($request->request->get('email', ''), FILTER_VALIDATE_EMAIL);
if($email !== FALSE) {
$name = substr($email, 0, strpos($email, '@'));
$content = $this->get('twig')->createTemplate(
"
Hello ${name}.
Thank you for subscribing to our newsletter.
Regards, VolgaCTF Team
" $mail = (new Email())->from('[email protected]')->to($email)->subject('VolgaCTF Newsletter')->html($content);
$mailer->send($mail);
$msg = 'Success';
} else {
$msg = 'Invalid email';
echo $msg;
}
return $this->render('main.twig', ['msg' => $msg]);
}
```
You can straight up see it is vulnerable to **template injection**, the part before @ of the email is used in creating the template.
The user email passes through filter_var with FILTER_VALIDATE_EMAIL argument.
FILTER_VALIDATE_EMAIL uses `RFC 822` to check if an email is valid that means almost all characters are allowed if enclosed in quotes.
```
"(Injection here)"@vit.fail
```
*The only limitation was that the injected code could not be more than 64 chars(62 with the quotes)*
To further continue, I created a local enviornment with the given code to work in a debug php environment.
https://twig.symfony.com/doc/3.x/
Started of with `"{{dump(app)}}"` which gives out the complete var_dump of the objects we can work with.
![dump app](dump_app.png)
### To print all environment variables
`"{{app.request.server.all|join(',')}}"`
I could not find the flag here so I moved to finding either a code execution or file read/write.
The app.request object also contained a files property, so I did a multipart file upload and the file was there.
I could access it using `"{{app.request.files.get(key}}"`
Digging the twig source code, the file object was an instance of
`Symfony::Component::HttpFoundation::File::UploadedFile` with parent class `Symfony::Component::HttpFoundation::File::File`
There was nothing interesting in these classes except a move function which allowed me to move the file with target as an argument.
So now I had file write on the server
### File write
`"{{app.request.files.get(1).move(app.request.query.get(1))}}"`
with the target director in url query `?1=/tmp/x/`
But unfortunately I did not have write permissions to `/var/www/newsletter/public/` to upload a shell so I continued my search.
The class `Symfony::Component::HttpFoundation::File::File` extended the inbuilt php class `SplFileInfo` https://www.php.net/manual/en/class.splfileinfo.php
It only contained metadata except for a openFile method which would give us a `SplFileObject` that contained methods to read and write the file.
So my plan was to call the constructor on SplFileInfo with the path of the file I want to read.
### File read
`"{{app.request.files.get(1).__construct('/etc/passwd','')}}"`
which would give us a SplFileInfo on /etc/passwd and then we could do
`"{{app.request.files.get(1).openFile.fread(99)}}"`
But now the 62 character limit became a bottleneck.
I went through the documentation again and found two possible paths that I could go through.
1. With the file write I have,upload a template file with the file read code and then `{% include %}` it
This did not work as twig did not allow including template files outside of the template directory.
2. Do something like this `{{include(template_from_string(app.request.query.get(1)))}}`
which did not work as well as `template_from_string` is not enabled by default :/
At this point I ultimately gave up and could not solve the challenge.
The solution was in easily visible in the documentation if I would have read carefully
`"{{'/etc/passwd'|file_excerpt(1,30)}}"`
:/