Secure web proxy with Symfony

For the past few months I got caught up into puppet, monitoring and other devops stuff. Wether or not security is part of devops (honestly I don’t care as long as security is taken into mind when developing) I was also tasked with securing some backend systems for the company that I work for as part of ISO 27001 certification. One of them was a PHP based application which had a code base, so old, so spaghetti bolognese that it could wake you up in the middle of night soaking in sweat when thinking about its security design. Nevertheless it was a critical application, that couldn’t be merged easily to a new code base like Symfony or another tool. So how to improve its security?

If it was built in Symfony, I could easily setup a firewall, add NelmioSecurityBundle and maybe add a 2-factor bundle and get on with it. But yeah, it wasn’t. Maybe I could just go into the spaghetti and implement some meatballs with composer and make the best of it? Meh…. There will always be the risk of open holes, maybe a “require ‘check_login.php’;” is forgotten, or login method might be vulnerable from a good old SQL injection. So this was not an option.

The app was used publicly by some of our customers, so IP whitelisting was no option. It just needed public protection from vulnerability scanners, So I wanted it to be wrapped in a secure package. More like a portal. What if I wrapped the entire application into a secured Symfony environment? But that would incur another session_start from the old application. So it needed to more separated than that. And at that point the idea of a Symfony web proxy was born. The Symfony layer would handle all the security, after which every request is proxied to the old PHP application. So the requirements would be

  • A symfony application @ my.symfony.proxy which functions as login portal
  • After logging in, every request is ported to an another site. Something like¬†https://my.symfony.proxy —> http://old_application_at_private_location:8080

Here’s how with Symfony 4.x (using flex) on PHP 7.2.

Setting it up

First we create a new project and install and setup some needed libraries. I mentioned them here below

Now that we installed everything we needed we can configure our application.

Security

Lets start with my favourite and most complex part of our application (now we’re still sharp). First we configure our security.yaml

Now comes the interesting part. I wanted to use the same user and password database provided by the old native PHP app. So I need to create an entity that respects the existing table, and add this as a user provider in the security.yaml.

Because the old app uses plain md5 hashing for the password without any salt or iterations I needed to change the encoder to some lower standards, so it integrates seamlessly with the old standard. Obviously this is not recommended security. If this is your exact same case, and would like to enhance this security. Then I recommend you to add an extra salt column, and rehash the password on login with some salt and iterations with the default encoder. On the login event you only need to check if there is already salt or not to pick the right encoder.

Now we can configure the firewall, wich is somewhat standard. Change according to your needs. Don’t forget to name the right provider.

Last but not least, add the access control

User Entity

In my case I wanted to reuse an existing user database (within the old application), which is served under MySQL. So I created an entity which reflects this table to be able to reuse this data. This is how my entity looks like:

Controllers

We need a few methods; first the login and logout method, which are pretty straight forward.

Then finally the core of this whole blog, a catchall controller. This method is called upon every other request (catchall magic is handled in the routing part further on). It just reads the current request, and passes everything within a curl request (Guzzle) to the old application that needs to be protected. In my case I placed the application in nginx under port 8080, which is shielded by a firewall and only accessible from localhost. Change the URL to your own “old application”

So now we basically created a proxy application. Now you can serve every existing site with your own custom domain (don’t use this for bad stuff, mkay?).

Security listener

Underneath all of this, we still need to add a little more custom logic to take over the login process. Instead of logging in directly with the original login of the application, I wanted to use symfony login. This is done with a security listener. Be sure that you change the code to an actual working login.

Routes

There are four routes needed for the controller. Besides the security related routes there is one route which is particularly interesting. This is the catchall route which should be named lastly to catch all other routes that are not matched by the fixed security related routes. This is done by providing a regex with just .* to match anything.

Templates

The template is a somewhat basic login template based on a simple twitter bootstrap theme. Placed under security/login.html.twig

Wrapping it up

To conclude; it’s relatively easy to setup a web proxy with modern security standards to protect your old (irreplaceable) spaghetti app. With Symfony at its base, some configuration and the usage of the right libraries, we only need a few files with satisfactional result.

I created a repository which contains all of the above, which you can use out of the box. This is found at my github account @ https://github.com/markri/symfony_web_proxy

Leave a Reply

Your email address will not be published. Required fields are marked *