Yeah, clikker will rejoice in reading that statement comming from me. But I really feel like one right now. For years, I’ve been programming in PHP, and when creating webpages with user validating, I’ve usually saved the userID in a session variable in order to have other pages validate that a user is logged in or not. I know this is a very standard way of doing it.
However I’ve just recently become aware that the way I validate users as being logged in, is flawed. Usually I do so something like this:
if !(isset($_SESSION['id']) && is_numeric($_SESSION['id']) && $_SESSION['id'] > 0) header("location: index.php")
So, if the $_SESSION['id'] isn’t set, isn’t a numeric value, or isn’t greater than 0 (all which should be true, if the user is logged in) then we return a redirect header to the browser, forcing it to go to index.php instead.
Good, right? Wrong!
In all the time, I’ve been doing this, I’ve always tested this and the rest of my webpages in many different browsers. All redirected as desired. But, what I didn’t realize was that PHP doesn’t stop running the script after it hits the header() function, and continues to compile the page and send it to the browser.
I’ve made a CMS system for a local BMX club. The admin.php (the control panel) contains links to itself with GET values that defines what the admin wants to do. For example, to delete a menu item, there would be a link like this: <a href="admin.php?action=deleteMenu&menuId=1">delete</a>. Of course the admin.php checks to see if the user is logged in, and if the user is the admin user before it does anything else. And if the admin isn’t logged in? header("location: index.php")! And all seemed to be fine.
Then the administrator from the BMX club called me and told me that all the pages and menus had been deleted, that only he, another and myself knew the password, and that it seemed like either we had been hacked, or that the database had been reset somehow. I confirmed that the pages and menus had been deleted, but everything else seemed intact. I received the appache access log files from the webhost, and started going through them. In the files I saw that a webcrawler had managed to get access to admin.php and started to, as webcrawlers do, follow every link. So of course it followed all the delete links, and thus a damn webcrawler managed to delete all the CMS pages and menus.
But how did it get access to admin.php without logging in as admin? My first theory was that this particular webcrawler used a browser plugin to crawl the pages a user were visiting, using the visitors session. However I wanted to do a simple test first. SSH’ing into my linux box, I first tried to do curl -I http://bmxclub/admin.php. It resulted in HTTP/1.1 302 Moved Temporarily which seemed about right. For some reason I tried without the -I option, and saw all the raw HTML, and at first glance that too seemed fine. It showed me the header, the menu, the table to subscribe for newsletters, the loginbox, the footer… but then I saw that the content were actually the control panel, with the links to add, move, and delete menus and pages…
So that’s how I learned to NOT trust browsers only when testing PHP code and redirecting with header values. And looking at it afterwards, it’s kind of logical that it all behaved as it does. I should have known that I should always place a PHP die(); call whenever I don’t want to execute any more of the PHP script. Not just redirect a visiting browser once it gets the HTML headers. I’m a n00b.
~Talant