We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 18913
    • 654 Posts
    I got to thinking about the idea of a sort of "self healing" index.php file and came up with this idea :

    Include this code at, say, the top of protect.inc.php :
       //start of code to check index.php integrity
       //could be contained in an included file
       $fr = fopen('index.php', 'r');
       fseek($fr, -10, SEEK_END);  //get 10 characters from the end
       $byte = fread($fr, 5);  //lets look at the first five
       fclose($fr);  //close the file
       if ($byte!='er();')  //comparison should deal with eof, newline characters, more complex comparison
       {
    		//something is at the end of the file that we don't expect
    		//should send an email and/or kick off a script to look for new/suspicious files?
    		//the _next_ time the page is loaded, it will be clean.
    		//But the current "bad" one is still loaded.  How clean it out?
    
    		if (!copy('indexbackup.php', 'index.php'))  //copy over the known good backup
    		{
    			//should log the problem to an error file, send email, etc.
    		} 
       }
       //end of code to check index.php integrity


    The idea is to have index.php check itself to make sure there’s nothing at the end unexpected. If there is, copy over a known good copy from some location, log the issues or take whatever other action might be needed, and continue. Playing around with it locally seemed to work, though it obviously assumes that the only place a problem could be created is at the end of the file. Still, as a proof-of-concept ...

    There are (at least) two issues I can forsee :
    1) you still have a bad copy loaded up in the brower
    2) there’s got to be a performance hit for doing this

    It would only serve as an early red flag, but that might be useful enough.
    Thoughts?
    MattC
    Edit : for clarity
      • 18913
      • 654 Posts
      Or would calculating and comparing a checksum be faster/better?
      MattC
        • 18913
        • 654 Posts
        You could also save / remember the last filemtime of the clean index.php file, and check against that.
        But it seems as though timestamps can be messed with.

        Looks like the checksum method would work. On a XAMPP/Vista test the performance difference was small, with an MD5 approach being a bit faster.

        The MD5 has was obtained from this little php file run from the root :
        <?php
        $chkfilename = "index.php";
        $chkmd5return = md5_file($chkfilename);
        echo 'MD5 : |'.$chkmd5return.'|';
        ?>


        And that was used in this bit of code :
           //start of code to check index.php integrity
           //could be contained in an included file
           $md5hash = 'hash_string_output_from_code_above';
           $chkmd5return = md5_file('index.php');
           if ($chkmd5return != $md5hash)    
           {
        		//something is at the end of the file that we don't expect
        		//should send an email and/or kick of a script to look for new/suspicious files?
        		//the _next_ time the page is loaded, it will be clean.
        		//But the current "bad" one is still loaded.  How clean it out?
        		if (!copy('indexbackup.php', 'index.php'))  //copy over the known good backup
        		{
        			//should log the problem to an error file
        		} 
           }
           //end of code to check index.php integrity
        


        So this would take care of unexpected code showing up elsewhere in the file. Same thing could probably be done for the document parser - setting aside performance/overhead issues for the moment.

        As I type this, I wonder if there’s something that would force a reload of the page before going any further? Or if that’s just not smart...
        MattC
        edits: for clarity
          • 18913
          • 654 Posts
          Okay, this seems to do the trick of forcing the reload :
             $md5hash = 'hash_string_output_from_code_above';
             $chkmd5return = md5_file('index.php');
             if ($chkmd5return != $md5hash)    
             //start of code to check index.php integrity
             //could be contained in an included file   
             {
          		//something is in the file that we don't expect
          		//should send an email and/or kick of a script to look for new/suspicious files?
          		$backuprestored = copy('indexbackup.php', 'index.php'); //copy over the known good backup
          		//the _next_ time the page is loaded, it will be clean.
          		//But the current "bad" one is still loaded.  How clean it out?
          		if ($backuprestored)  
          		{
                                  //this forces a reload of the page in the browser, bringing up the new one
          			header("Location: index.php");
          		}
          		else
          		{
          			//should log the problem to an error file
          		} 
             }
             //end of code to check index.php integrity
          


          Thoughts are welcome...
          MattC

          Edit : minor code comment change
            • 33337
            • 3,975 Posts
            This sounds good.

            We can also configure a cron to run it every now and then instead of each request? viable?
              Zaigham R - MODX Professional | Skype | Email | Twitter

              Digging the interwebs for #MODX gems and bringing it to you. modx.link
              • 18913
              • 654 Posts
              A quick test showed that this as a cron job seemed to work :
              <?php
                 $msgsubject = 'Index check';
                 $emailaddy = '[email protected]';
                 $md5hash = 'hash_string_output_from_code_above';
                 //the job is being run from above the root folder
                 $chkmd5return = md5_file('public_html/path/to/index.php');
                 if ($chkmd5return != $md5hash)    
                 {
                              //the backup copy is kept outside of the root folder
              		$backuprestored = copy('indexbackup.php', 'public_html/path/to/index.php');
              		if ($backuprestored)  
              		{
              			$message = 'Index.php checksum does not match. Recopied.';
              			//Double check the checkum on the copied file here?
              		}
              		else
              		{
              			$message = 'Problem copying file';
              		} 
                 }
                 else
                 {
              	$message = 'Checksums match';
                 }
              	mail($emailaddy, $msgsubject, $message);   
              ?>
              


              MattC
                • 25663 MODX Staff
                • 12,272 Posts
                What if the script kiddie is smart enough to replace the comparison checksum value with the new checksum value found after compromising those files?
                  Ryan Thrash, MODX Co-Founder
                  Follow me on Twitter at @rthrash or catch my occasional unofficial thoughts at thrash.me
                  • 18913
                  • 654 Posts
                  @rthrash :
                  No doubt I’ll display some ignorance on my part here, but ...

                  For the cron job version - where the cron job file is stored outside the root - wouldn’t that value be safe? Or what about pulling it from a database? If that file isn’t safe then I would think that the validity of the comparison value becomes one of your smallest problems, no?

                  For the in-file approach, how secure would pulling the comparison value from the database be? I would guess that the comparison values could be buried somewhere/somehow.

                  And, of course, I guess the script kids could start replacing files all over the place, trying to knock out any instance of the checksum analysis.

                  As I implied a couple of lines above, I don’t believe I appreciate all the nuances of this. So if you or others could continue to comment on weaknesses and/or provide "strengthening" suggestions, I’d be appreciative.
                  MattC
                    • 18913
                    • 654 Posts
                    In re-reading my reply, I can see that I didn’t really come across as I intended. I’m not disagreeing with the idea that the comparison value could be compromised, nor the files themselves could be in jeopardy if someone has access to the site. So don’t get me wrong - I get the idea that nothing is really "safe" in such circumstances (e.g. a compromised shared hosting environment).

                    I guess what I was wondering is whether or not hiding, encrypting or otherwise obfuscating the names, locations and contents of variables and files would raise the bar to access sufficiently high to discourage or prevent most of the scripting hacks that have been seen? Or does it just move the problem around without changing the difficulty needed in overcoming it?

                    MattC
                    Edit - re: clarity
                      • 3749
                      • 24,544 Posts
                      What about storing the correct checksum and the clean index.php outside the web root (or in the DB)?
                        Did I help you? Buy me a beer
                        Get my Book: MODX:The Official Guide
                        MODX info for everyone: http://bobsguides.com/modx.html
                        My MODX Extras
                        Bob's Guides is now hosted at A2 MODX Hosting