This post was originally published on webinuse.com
This article is connected to From 0 to 5.000.000 monthly views, so it will be good if you read it, to have some idea of how we got here. Today we are going to continue by speaking about security issues we had.
In From 0 to 5.000.000 monthly views I mentioned that because of misuse of PDO we were prone to hackers’ attacks. But that was not the only mistake we made, so let’s start from the beginning.
Form vulnerability
We had a form on the front where readers could send their stories. They just needed to fill in name, email, phone and write their story in textarea
or upload a document and send it along with personal information.
Since I was assigned to the front-end I validated every field, even I had validation for the file once the user picked it, but the problem was backend.
1. Missing CSRF token
Since this form had its own link, we could create a CSRF token when the user visits it, and then check it once the user sends data to the backend. But we did NOT. So basically, you could use let’s say Postman, and send as many requests as you wanted to backend, and the backend would still think that they’re legit.
<?php
session_start();
$secretKey = 'Here goes a secret key that you have';
$token = hash('sha256', md5(time() . $secretKey));
?>
This is simple example on how to create CSRF token using PHP (I am using PHP because it was used in a project, and I used it in the last article).
Now we have to implement it in the form. We could save our CSRF token to a hidden field, and our CSRF would be different everytime user restarts the page.
<form action="send.php" method="POST">
<!-- Here goes our code for fields -->
<input type="hidden" value="<?php echo $token ?>" />
<!-- Here goes the rest of the fields -->
</form>
After user submits form, we can check if CSRF token exists and if it is equal to the that we have saved in session, or somewhere else.
<?php
$token = $_POST['token'];
if ($token !== $_SESSION['token']) {
//Here goes a message or something else
return false; //or return whatever is appropriate
This way we could prevent huge number of requests for our backend. We could even save IP, so check if IP is saved, block access.
2. Missing appropriate file type
The second mistake regarding same form’s security is that we did not check, appropriately, for file type. According to type of form, we should allow only .txt, .doc, .pdf, .jpg, and .png. In case user sends text in text file we needed .txt, .doc and .pdf. If user sends image (e.g. accident happened) we needed .jpg and .png.
I did check for those on front-end. But all I could do is check for extension, which is not sufficient.
<form>
<input type="file" id="file" />
</form>
<script>
function validateFileExtension() {
//Create an array of acceptable values
const acceptedFiles = [".txt", ".doc", ".docx", ".pdf", ".jpg", ".jpeg", ".png"];
//Get value from form
let file = document.querySelector("#file").value;
let validFile = false;
//Iterate through array of accepted files
for (let j = 0; j < acceptedFiles.length; j++) {
//Compare extension to current value in acceptedFiles at index j
if (file.substr(file.length - acceptedFiles[j].length, acceptedFiles[j].length).toLowerCase() == acceptedFiles[j].toLowerCase()) {
//When we reach extension that is allowed and
//equal to current extension finish the loop
//and pass true to validFile variable
validFile = true;
break;
}
}
//When loop is finished we check if validFile is true or false
if (!validFile) {
//if it is not validFile, return error
return false;
}
//If it is validFile continue your code execution
}
</script>
Problem with this validation is that malicious user can change from, let’s say, .php to .jpg or .txt. This would pass the check. Normally JS validation is ok for “normal” users, but not for malicious users.
Also we should check, at least, for MIME type in our backend, but we didn’t. We did the same thing as on front. So when you upload your file, you can access it. And if it was malicious, you could do whatever you wanted.
That was the first security breach we’ve encountered. Someone uploaded a file, basically file explorer, and played around. They have deleted a bunch of stuff. Also, they included some ads in our header. At that time this website had few hundred users online at any given moment, so I belive it was pretty lucrative for those hackers.
Moreover my collegue didn’t do much of validation of the fields, either. So, after those hackers figured it out that we have no validation, what so ever, they hit us hard.
Database security issues
As we pointed out about front-end form, same thing was happening in the CMS backend. There was no form validation, no fields validation, no CRSF token validation, practically it was Swiss cheese.
Always validate session
One of the first security things you should do is validate session. Especially if user can change ANYTHING in database. So, we had those, but only in views. Since app was build in MVC pattern, only when View was loaded, we checked for session. But controllers were not covered, and there is the real problem. Same as with form above, user could use Postman and do whatever user wanted.
Eventually, we noticed that. But, it was too late. They already updated around 20k of posts with code for their ads. It was painful to clean all those, because there was several different scripts. The first thing we did was find an example of the script, then perform update of those posts. And so on, loop, until we cleaned everything.
Never use md5 for passwords
Yeah, I know, most of the people will say that they are safe, irreversible, but there is a problem with that. When hacker has access to your database, they can reverse passwords. There are even some online tools to do that, not to mention some advanced softwares with dictionaries, and everything else.
Plus if hacker sees that it is md5, they can easily change password to anything they want and access your system “legally”. If they pick not so active user, it could be days before we realise that account is compromised.
Please use more secure hashing algorithms with secret keys and salts. After everything I saw, no security is too much security.
Sanitize user input
Never trust the user. Not every user is malicious, or hacker, but every user can, unintentionally and unconciousally, compromise the security of our app.
There was an “old” journalist employed by our client, due to his experience. Once we got a call from this gentlemen stating that the text, he is about to publish, has fall apart. I didn’t understood him, so I asked him for post id, and he was right. Nothing was right. Only to find out few minutes later that he used quotes where he usually shouldn’t do that. Because of these quotes everything went haywire.
What really happened with hackers?
At some point we were under constant attack by hackers from Indonesia. The first few attacks went well, comparing to latter attacks. They did something we corrected it, if we found out how did they do it, we removed bug. But then, our DB was missing, and they contacted our client over Facebook asking for some money and they will return DB, intacted. Luckily we had backup, only 20 minutes old, so we used it.
Tomorrow the same thing. These were boys from Indonesia, not so old, maybe 17 or something. They went to high school in Jakarta, and they were best friends. One of them was really popular, we weren’t sure if he really was in that group, but we suspected, because he had hacked one big organisation. Instead of being black hat, he pointed out to the organisation about a bug, and they’ve fixed it.
Oh, I almost forgot. Our client tried to negotiate with them, without our knowledge, and sent them equivalent of $700 and never got DB back.
So after few days of “fighting” with them we already called some super security guys, who helped us patch our mistakes and improve our sites security, they contacted us. Personally. They asked for money.
We figured that they saw somewhere in the code Designed and Developed by … and when they went to our website and Facebook they found us.
My collegue, at the time, today he is my co-founding partner, made his life’s mission to find all these guys.
Once we refused to send them money they started attacking our website and some other websites that were on the same server. My collegue tried stalling by talking to them and giving himself more time to find out their real identites.
He eventually did that. He identified their parents, friends, siblings, cousins, everyone. After he found out all of these, he knew how to stop the attacks, and he did. He used something against them, and thankfully they’ve stopped.
Security today
We partnered with some great guys and we did great job together. We’ve fixed a lot of stuff and improved sites security with different techniques.
Still, we suffer from attacks every day, but most of those are prevented even before they reach server. We use CloudFlare for DDoS, we have Firewall on our server that detects database attacks and file changes (this is handled by server company). We just get notification if there is attack and if there is a bug in our code through which they’ve attacked us.
I think I didn’t breach my NDA with this post, but we will see if anyone calls me.
If you have any questions or anything you can find me on my Twitter, or you can read some of my other articles like CSS Combinators.
Top comments (0)