The vulnerability is that one could send any kind of relative path in $lang
. This is especially dangerous if users can upload files and figure out their real path on the server.
Example:
- A hacker may use some file upload functionality of your site to upload
evil.php
. The hacker may know/have found out/guess that it's stored at /var/www/uploads/evil.php
, and that your application runs in /var/www/html
.
- Now, normally nobody could run this file if
/var/www/uploads
is not accessible through HTTP.
- But, it would be possible to open
http://example.com/index.php?lang=../../uploads/evil
and guess what, it would include languages/../../uploads/evil.php
which would resolve to /var/www/uploads/evil.php
!
This of course also works without file upload if there are any other files which can be used for exploiting something by getting access to them and calling them, such as maybe files in a normally password-protected directory (phpMyAdmin for example).
And if you now think "that's quite a lot of assumptions and 'may's and 'if's in there, you would need to be very lucky to succeed with this" then watch out - although there are some obvious blatantly open vulnerabilities where you do one URL call and you can, let's say, overtake the server or delete the database, the most dangerous ones are the ones which require multiple puzzle pieces to make an exploit work, because they go undetected for a long time and it can be hard to understand how the server got actually hacked once it happens, and you will naturally have a more experienced and therefore more dangerous (possibly stealth) hacker at your doorstep. If somebody is determined to find a security hole (either because they have an actual goal attacking your site or you, or because they just enjoy owning poor webmasters to push their own ego), they will keep searching and puzzle together whatever is required to achieve what they want.
There are multiple solutions.
As suggested by u_mulder, if you have only ar
and en
then just check if it's either one.
If you have more languages, you could just create an array with a list of allowed values and check if the sent language is in that array, for example:
$languages = ['de', 'en', 'ar', 'jp', 'fr'];
if(in_array($_GET['lang'], $languages)) {
$selectedLanguage = $_GET['lang'];
} else {
$selectedLanguage = 'en'; // default
// Note that you could also show an error "invalid language" instead
}
If you want to control this by just allowing only the files existing in the folder, you could also just validate that the language contains only letters (or whatever you need, as long as you make sure it may not contain dots or slashes or such):
if(preg_match("/^[a-z]*$/", $_GET['lang'])) { ... }
Note that with this approach you should definitiely also check if the specified file exists, otherwise it would still be possible to have a language-less site by specifying an invalid language (especially since include
doesn't throw an error on non-existing files, unlike require
).