Location: PHPKode > scripts > Firewall Protection > firewall.class.php
<?php
/*
   @release date: 19.07.2010
   @original-author Petter Kjelkenes from Norway
   @support kjelkenes [AT] gmail [DOT] com
   @website http://pkj.no , www.pkj.no

   @version 0.8
   @license LGPL


   This class provides your website / CMS firewall features. It's easy design makes it easy for you to allow and restrict access to users certain actions or block cetain ip's.
   You can also do input filtering on a highlevel and it's pretty easy to do. One method to totally filter all user input data.

   !Security Warning!: Notice! The firewall should be included AND configured on top of the index scripts. It should be the first to start nomatter what!! If you put the script after a lot of code the firewall is not efficient.


   Example of use:

// Start firewall.
// If you have a configscript, include it as the first arguement in the constructor.
$firewall = new Firewall();

// Block specific ips. Also block the 20.20.x.x subnet.
$firewall->blockIP(array('84.48.167.2','20.20.20.20/255.255.0.0'));

// Only allow specific IP's..
#$firewall->allowIP('84.48.167.2');

// This removed all input, meaning you cant do anything with POST and GET actions because it gets totally stripped !!
// Normally this method is not needed to be used because it makes your script very unflexible in many cases.
#$firewall->UserInput()->preventInput();

// Remove all possible XXS and SQL injections this filters every possible input that a user can do. Notice! User may not post tags when this is used, HTML tags is removed!!
// Now you dont need to escape data later in the script, because at a highlevel this filters all the $_POST,$_GET...etc.. variables.
$firewall->UserInput()->inputProtection();


try{
    // When finished setting settings you will need to run the firewall with the run() method.
    $firewall->run();
}catch(Exception $e){
    die("You are not allowed at this place.");
}



*/


class Firewall{

    // Keeps track of blocked ips.
    private $ipBlocks = array();
    // Keeps track of allowed ips.
    private $ipAllows = array();

    // Config file script.
    private $configScript = '';


    // Constants.
    const GET = 2;
    const POST = 4;
    const COOKIE = 8;
    const SESSION = 16;
    const FILES = 32;
    const SERVER = 32;

    /**
     * Creates the firewall object.
     * @param    string    $configscript    The config script (xml). You can configure the firewall by config script or by method usage.
     *
     * @return    void
     */
    public function __construct($configscript=false){
        $this->configScript = $configscript;

        if ($this->configScript)$this->loadSettingsFromFile();

    }

    /**
     * Prints the settings that are currently used. For debug purpses!
     * Run this method after firewall is configured with script or with the methods available.
     * Note: procedures output!
     *
     * @return    void  Returns data in html form and outputs directly to browser. Only use when debugging your firewall and testing your settings.
     */
    public function printSettings(){
        echo "<h1>Debug firewall</h1>";
        echo "<h2>Hi, your ip address is: ",$this->getClientIP(),"</h2>";
        echo "<h2>Ips that are currently blocked:</h2><ul>";
        if (count($this->ipAllows) > 0){
            echo "<li>ALL IPS EXCEPT FROM Allowed IP's.....</li>";
        }else{
            foreach($this->ipBlocks as $it){
                echo "<li>$it</li>";
            }
        }
        echo "</ul><h2>Only these ips are allowed:</h2><ul>";
        if (count($this->ipAllows) > 0){
            foreach($this->ipAllows as $it){
                echo "<li>$it</li>";
            }
        }else{
           echo "<li>All ips except from the blocked onces.</li>";
        }
        echo "</ul><h2>Userinput block: ",(!isset($_GET) ? 'ON' : 'OFF'),"</h2>";

        echo "</ul><h2>Userinput datafilter Test:</h2><br />
        ",(isset($_GET['datafilter']) ? $_GET['datafilter'] :
        "
        Include this to the URL:
        <textarea style ='width:100%; height:100px;'>?datafilter=XXS: <script type='text/javascript'>alert('Your site is XXS vulnerable!')</script>, SQL Injection: ' or '1'='1</textarea>
        "
        ),"<br /> If You dont get the javascript popup alert message, you see that the SQL is commented your site is XXS and SQL injection Safe..";
    }



    /**
     * Runs config file to class, configures firewall class to the correct config.
     * @return    void
     */
    private function loadSettingsFromFile(){
        if (!file_exists($this->configScript))throw new Exception('Could not find config script provided: '.$this->configScript);

        $xml = simplexml_load_file($this->configScript);

        if (isset($xml->IP->block) && count($xml->IP->block) > 0){
            foreach($xml->IP->block->item as $it){
                $this->blockIP((String)$it);
            }
        }
        if (isset($xml->IP->allow) && count($xml->IP->allow) > 0){
            foreach($xml->IP->allow->item as $it){
                $this->allowIP((String)$it);
            }
        }

        if (isset($xml->userinput->preventInput) && (String)$xml->userinput->preventInput=='true'){
            $types = isset($xml->userinput->preventInput['types']) && $xml->userinput->preventInput['types'] != '' ? str_replace('|',' & ',$xml->userinput->preventInput['types']) : false;
            eval('$this->UserInput()->preventInput('.$types.');');
        }
        if (isset($xml->userinput->inputProtection) && (String)$xml->userinput->inputProtection=='true'){
            $types = isset($xml->userinput->inputProtection['types']) && $xml->userinput->inputProtection['types'] != '' ? str_replace('|',' & ',$xml->userinput->inputProtection['types']) : false;
            $stripHTML = isset($xml->userinput->inputProtection['stripHTML']) && (String)$xml->userinput->inputProtection['stripHTML'] == 'true' ? 'true' : 'false';
            eval('$this->UserInput()->inputProtection('.$types.','.$stripHTML.');');
        }

    }


    /**
     * Blocks a certain IP address. You may also use format with net mask to block whole subnets 100.100.100.100/255.255.0.0 or normal mode 100.100.100.100
     * @param    string/array    $ip    The ip address to block. You can also pass an array of ips.
     *
     * @return    void
     */
    public function blockIP($ip){
        if (is_array($ip)){
            $this->ipBlocks = array_merge($this->ipBlocks,$ip);
        }else{
            $this->ipBlocks[] = $ip;
        }
    }

    /**
     * Only allow certain IP's to access script. You may also use format with net mask to block whole subnets 100.100.100.100/255.255.0.0 or normal mode 100.100.100.100
     * @param    string/array    $ip    The ip address to allow. You can also pass an array of ips.
     *
     * @return    void
     */
    public function allowIP($ip){
        if (is_array($ip)){
            $this->ipAllows = array_merge($this->ipAllows,$ip);
        }else{
            $this->ipAllows[] = $ip;
        }
    }


    /**
     * Runs the firewall. Throws exception on error.
     *
     * @return    void
     */
    public function run(){
        $clientip = $this->getClientIP();


        $ipblock=false;
        $ipallow=true;

        $iptriggerblock=false;
        if (count($this->ipBlocks) > 0){

            foreach($this->ipBlocks as $block){
                $bit = explode('/',$block);
                if (!isset($bit[1]))$bit[1] = '255.255.255.255';
                if ($this->ipCompare($clientip,$bit[0],$bit[1])){
                    $ipblock=true;
                    $iptriggerblock=true;
                    continue;
                }else{
                    if (!$iptriggerblock)$ipblock=false;
                }
                unset($bit);
            }
        }
        
        $iptriggerallow=false;

        if (count($this->ipAllows) > 0){

            foreach($this->ipAllows as $block){
                $bit = explode('/',$block);
                if (!isset($bit[1]))$bit[1] = '255.255.255.255';
                if ($this->ipCompare($clientip,$bit[0],$bit[1])){
                    $ipallow=true;
                    $iptriggerallow=true;
                    continue;
                }else{
                    if (!$iptriggerallow)$ipallow=false;
                }
                unset($bit);
            }
        }

        if ($ipblock)throw new Exception('Client blocked from php web application. Reason: IP Blocked.', 1);
        if (!$ipallow)throw new Exception('Client blocked from php web application. Reason: IP not in allow list.', 2);
    }

    /**
     * Gets the real clients ip address.
     * Thanks to http://roshanbh.com.np/2007/12/getting-real-ip-address-in-php.html.
     *
     * @return    void
     */
    private function getClientIP(){
        if (!empty($_SERVER['HTTP_CLIENT_IP'])){ //check ip from share internet
            $ip=$_SERVER['HTTP_CLIENT_IP'];
        }elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){    //to check ip is pass from proxy
            $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
        }else{
            $ip=$_SERVER['REMOTE_ADDR'];
        }
        if ($ip=='')$ip = '127.0.0.1'; // When not getting IP.
        if ($ip=='::1')$ip = '127.0.0.1'; // Workaround for windows localhost GetIp...
        return $ip;
    }

    /**
     * Compares two ips with a mask.
     * Thanks to http://roshanbh.com.np/2007/12/getting-real-ip-address-in-php.html.
     *
     * @return    boolean
     */
    private function ipCompare ($ip1, $ip2, $mask) {
        $masked1 = ip2long($ip1) & ip2long($mask); // bitwise AND of $ip1 with the mask
        $masked2 = ip2long($ip2) & ip2long($mask); // bitwise AND of $ip2 with the mask
        if ($masked1 == $masked2) return true;
        else return false;
    }


    /**
     * Data Input manager
     *
     * @return    UserInput
     */
    public function UserInput(){
        return new Firewall_UserInput($this);
    }

}

class Firewall_UserInput{
    // Firewall object holder.
    private $fobj;

    /**
     * Creates the User Input object
     * @param    Firewall    $firewallobj    The firewall object.
     *
     * @return    void
     */
    public function __construct(&$firewallobj){
        $this->fobj = &$firewallobj;
    }


    /**
     * Remove userinput. $_POST and $_GET and $_FILES statements will be empty in the whole script. This does not block SESSION / SERVER variable by default.
     * Useful if you dont want any input at all.
     * @return    boolean
     */
    public function preventInput($methods=false){
        if ((($methods && Firewall::POST & $methods) || !$methods) && isset($_POST))unset($_POST);
        if ((($methods && Firewall::GET & $methods) || !$methods) && isset($_GET))unset($_GET);
        if ((($methods && Firewall::FILES & $methods) || !$methods) && isset($_FILES))unset($_FILES);
        if ((($methods && Firewall::COOKIE & $methods) || !$methods) && isset($_COOKIE))unset($_COOKIE);
        if (($methods && Firewall::SESSION & $methods) && isset($_SESSION))unset($_SESSION);
        if (($methods && Firewall::SERVER & $methods) && isset($_SERVER))unset($_SERVER);
        return  true;
    }

    /**
     * Prevent XXS and strips all tags, also escapes SQL injections.
     * @param    $methods Bitwise representation of what to filter. See class constants. Use forexample: Firewall::POST & Firewall::FILES & Firewall::GET . Empty arguement(false) will escape all input variables that exists in PHP.
     * @param    $stripHTML boolean   Strip HTML Completly? If not, it will remove XSS attacks from the HTML istead.
     * @return    boolean
     */
    public function inputProtection($methods=false,$stripHTML=false){
        if ((($methods && Firewall::POST & $methods) || !$methods) && isset($_POST))$_POST = $this->filterDataArray($_POST,$stripHTML);
        if ((($methods && Firewall::FILES & $methods) || !$methods) && isset($_FILES))$_FILES = $this->filterDataArray($_FILES,$stripHTML);
        if ((($methods && Firewall::GET & $methods) || !$methods) && isset($_GET))$_GET = $this->filterDataArray($_GET,$stripHTML);
        if ((($methods && Firewall::COOKIE & $methods) || !$methods) && isset($_COOKIE))$_COOKIE = $this->filterDataArray($_COOKIE,$stripHTML);
        if ((($methods && Firewall::SERVER & $methods) || !$methods) && isset($_SERVER))$_SERVER = $this->filterDataArray($_SERVER,$stripHTML);
        if ((($methods && Firewall::SESSION & $methods) || !$methods) && isset($_SESSION))$_SESSION = $this->filterDataArray($_SESSION,$stripHTML);
    }


    /**
     * Filters a subdirectional array with the filterData method.
     * @param    $data    array    The array of data to filter. Can be multidimensional and so forth.
     * @param    $stripHTML boolean   Strip HTML Completly? If not, it will remove XSS attacks from the HTML istead.
     * @return    boolean
     */
    private function filterDataArray($data,$stripHTML=false){
        if (is_array($data)){
            foreach($data as $k => $v){
                if (is_array($v))$data[$k] = $this->filterDataArray($v);
                else $data[$k] = $this->filterData($v,$stripHTML);
            }
        }
        return $data;
    }


    /**
     * Filter data. Removes html tags. Strip data and filter SQL attacks.
     * @param    $data     string     Filter data of string, returns the string.
     * @param    $stripHTML boolean   Strip HTML Completly? If not, it will remove XSS attacks from the HTML istead.
     * @return    string
     */
    private function filterData($data,$stripHTML=false){

        if ($stripHTML)$data = strip_tags($data); // Remove HTML
        else $data = $this->RemoveXSS($data);
        $data = htmlspecialchars($data); // Convert characters
        $data = trim(rtrim(ltrim($data))); // Remove spaces
        $data = $this->sqlEscapeString($data); // Prevent SQL Injection

        return $data;
    }
    
    
    /**
     * Removes XSS attacks completly.
     * @author http://www.nioxiao.com/archives/869
     * @reference http://www.nioxiao.com/archives/869
     */
   private function RemoveXSS($val) {
       // remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
       // this prevents some character re-spacing such as <java?script>
       // note that you have to handle splits with n, r, and t later since they *are* allowed in some inputs
       $val = preg_replace('/([x00-x08,x0b-x0c,x0e-x19])/', '', $val);

       // straight replacements, the user should never need these since they're normal characters
       // this prevents like <IMG SRC=&#X40&#X61&#X76&#X61&#X73&#X63&#X72&#X69&#X70&#X74&#X3A&#X61&#X6C&#X65&#X72&#X74&#X28&#X27&#X58&#X53&#X53&#X27&#X29>
       $search = 'abcdefghijklmnopqrstuvwxyz';
       $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
       $search .= '1234567890!@#$%^&*()';
       $search .= '~`";:?+/={}[]-_|'."'";
       for ($i = 0; $i < strlen($search); $i++) {
      // ;? matches the ;, which is optional
      // 0{0,7} matches any padded zeros, which are optional and go up to 8 chars

      // &#x0040 @ search for the hex values
      $val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ;
      // &#00064 @ 0{0,7} matches '0' zero to seven times
      $val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ;
       }

       // now the only remaining whitespace attacks are t, n, and r
       $ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
       $ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
       $ra = array_merge($ra1, $ra2);

       $found = true; // keep replacing as long as the previous round replaced something
       while ($found == true) {
          $val_before = $val;
          for ($i = 0; $i < sizeof($ra); $i++) {
         $pattern = '/';
         for ($j = 0; $j < strlen($ra[$i]); $j++) {
            if ($j > 0) {
               $pattern .= '(';
               $pattern .= '(&#[xX]0{0,8}([9ab]);)';
               $pattern .= '|';
               $pattern .= '|(&#0{0,8}([9|10|13]);)';
               $pattern .= ')*';
            }
            $pattern .= $ra[$i][$j];
         }
         $pattern .= '/i';
         $replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag
         $val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
         if ($val_before == $val) {
            // no replacements were made, so exit the loop
            $found = false;
         }
        }
        }
        return $val;
    }


    private function sqlEscapeString($value){
        $search = array("\x00", "\n", "\r", "\\", "'", "\"", "\x1a");
        $replace = array("\\x00", "\\n", "\\r", "\\\\" ,"\'", "\\\"", "\\\x1a");

        return str_replace($search, $replace, $value);
    }

}



?>
Return current item: Firewall Protection