http://nz2.php.net/manual/en/security.errors.php Standard attack tactic • One of the initial steps any attacker would do would be to try and get some information on what programming language you’re using in your website. • feed the system with improper data • check for the kinds, and contexts, of the errors which are returned • Moreover, they would try to investigate the structure and organisation of your files in the webserver, bugs and weaknesses, permissions the webserver has, order of authentication, etc. • By feeding it with the wrong data and looking at the response of your web application, they may be able to determine that a system was built with PHP. Attacking Variables with a custom HTML page <form method="post" action="attacktarget.php?username=badfoo&amp;password=badfoo"> <input type="hidden" name="username" value="badfoo" /> <input type="hidden" name="password" value="badfoo" /> </form> http://nz2.php.net/manual/en/security.errors.php Prevent your code from being probed by attackers 1. The first step is to scrutinize all functions, and attempt to compensate for the bulk of the errors. 2. The second is to disable error reporting entirely on the running code. 3. The third is to use PHP's custom error handling functions to create your own error handler. http://nz2.php.net/manual/en/security.errors.php Prevent your code from being probed by attackers One way of catching this issue ahead of time is to make use of PHP's own error_reporting(), to help you secure your code and find variable usage that may be dangerous. By testing your code, prior to deployment, with E_ALL, you can quickly find areas where your variables may be open to poisoning or modification in other ways. PHP.ini approach error_reporting = E_ALL Once you are ready for deployment, you should either disable error reporting completely by setting error_reporting() to 0, or turn off the error display using the php.ini option display_errors, to insulate your code from probing. PHP script approach error_reporting(0); http://nz2.php.net/manual/en/security.errors.php Variables outside PHP Recall two methods of transferring data to a server-side script: 1. Using POST: the data is transferred to the Web server as part of the http header. 2. Using GET: the data is appended to the URL of the script, • e.g. http://www.web.com/scriptname.php?name=Joe&age=32 Server-side PHP scripts access incoming data via • $_POST • $_GET • $_REQUEST Includes all data from POST, GET and COOKIE Register_Globals Using register_globals (php.ini): php_flag register_globals on/off With “register_globals” turned on, the URL http://www.example.com/test.php?id=3 • This will produce a variable $id in the server script. • Big security problem. Nowadays by default, register_globals is (or should be) turned OFF (> PHP 4.2.0) so that data can then only be transferred via the predefined superglobals $_POST and $_GET. From /php.ini... (php 4.x.x) .... ;You should do your best to write your scripts so that they do ;not require register_globals to be on ;Using form variables as globals can easily lead ;to possible security problems, if the code is not very well ;thought of. register_globals = Off .... What could possibly go wrong if we have the following case: register_globals = ON Uninitialised variable for authorisation Example of misuse of register_globals: if (check_authorization($name, $passwd)) { $authorized = true; } . . . if ($authorized) { download_highly_top_secret_files(); } • With register_globals turned ON, $authorized can be directly manipulated by hackers, i.e. http://www.server.com/login.php?authorized=1 GET /login.php?authorized=1 Example of misuse of register_globals: if (check_authorization($name, $passwd)) { $authorized = true; PHP allows us to use uninitialised variables, } that’s why setting register_globals=ON could . lead to security problems. . Initialising the variable properly before using it . should also solve this problem: $authorized=false if ($authorized) { download_highly_top_secret_files(); } • With register_globals turned ON, our program logic may be compromised. • With register_globals turned OFF, $authorized can’t be set via a web resource request. • In this example, with proper initalisation of the variable $authorized, regardless of the setting of register_globals, our code would be safe. Processing form input Make sure register_globals is turned off, then the only data that can be manipulated by the client are those in $_GET and $_POST In PHP 6, the register_globals option is totally REMOVED. However, care still needs to be taken in processing form output A similar case, but this time the attack coming from a hidden text field. register_globals = ON Uninitialised variable for authorisation Adapted from Dynamic Web Application Development using PHP and MySQL by Stobart & Parsons HTML Form asking for a username and password <h2>Please enter your Username and Password:</h2> <form action=‘authorise.php' method='post'> <p> <label for="strUserName">Username: </label> <input type="text" name="strUserName" id="intUserName"/> </p> <p> <label for="strPassword">Password: </label> <input type="password" name="strPassword" id="intPassword"/> </p> <p><input type="submit" name="submit"/></p> </form> This data is passed to a PHP script. This form could be altered by a malicious user to inject a hidden text field. * ALTERED VERSION * HTML Form asking for a username and password <h2>Please enter your Username and Password:</h2> <form action=‘authorise.php' method='post'> <p> <label for="strUserName">Username: </label> <input type="text" name="strUserName" id="intUserName"/> </p> <p> <label for="strPassword">Password: </label> <input type="password" name="strPassword" id="intPassword"/> </p> <p><input type="hidden" name="intOkay" value="1"/></p> <p><input type="submit" name="submit"/></p> </form> This data is passed to a PHP script. A hidden text field with the name intOkay and value set to 1 is injected. PHP script with an uninitialised variable <?php // File: authorise.php if (checkusernamepassword($strUserName, $strPassword)) $intOkay = 1; if ($intOkay) echo "Valid User confirmed"; function checkusernamepassword($strUserName, $strPassword) { return false; Very simple } codes- for testing ?> purposes only! When this script is invoked, the user will be cleared as a valid user because the hidden text field will set the variable $intOkay to 1. This will happen if register_globals = ON. * IMPROVED VERSION * PHP script for authentication <?php // File: authorise.php $intOkay=0; if (isset($_POST["submit"])) { if (checkusernamepassword($_POST["strUserName"], $_POST["strPassword"])) $intOkay = 1; } if ($intOkay) echo "Valid User confirmed"; function checkusernamepassword($strUserName, $strPassword) { return false; } ?> • Initialise the variable properly. • Access the variables through the $_POST superglobal array • Make sure that register_globals=OFF. Form Data Ideally users will enter perfect data in perfect forms - don’t assume this will happen. Example, if a form field asks for an age, check that it is numeric if ( !is_numeric( $_POST[“age”] ) ) { // error handling goes here } Example of filter for form data <?php .... Returns the portion of string specified by the start and length parameters $srtVar=substr(htmlentities($_POST['myvar']),0,80); $strVar=preg_replace('/[^\w\.\-\& ]/','',$strVar); .... ?> Performs replacement, based on a matching pattern (Perl Compatible Regular Expression – PCRE) Converts all applicable characters to HTML entities (e.g. ‘<‘ becomes ‘&lt;’ ) http://www.regexlib.com/Search.aspx?k=email (PCRE) Perl Compatible Regular Expression Metacharacter . \ ^ $ () [] [^] | Description Matches any single character Identifies the next character as a literal value Anchors characters to the beginning of a string Anchors characters to the end of a string Specifies required characters to include in a pattern match Specifies alternate characters allowed in a pattern match Specifies alternate characters to exclude in a pattern match Identifies a possible range of characters to match Specifies alternate sets of characters to include in a pattern match Sample Regular Expression <?php $str = 'dug'; if(preg_match("/.../",$str)==0){ echo "<p> Not a valid name! </p>"; } else { echo "welcome to paradise falls " . $str . "!"; } ?> The following pattern requires that there must be at least 3 characters. Sample Regular Expression <?php $str = 'http://www.paradisefalls.com'; if(preg_match("/^http/",$str)==0){ echo "<p> Not a valid name! </p>"; } else { echo "welcome to paradise falls " . $str . "!"; }?> The following pattern requires that $str must start with ‘http’. Sample Regular Expression <?php $str = 'http://www.paradisefalls.co.nz'; if(preg_match("/com$/",$str)==0){ echo "<p> Not a valid name! </p>"; } else { echo "welcome to paradise falls " . $str . "!"; } ?> The following pattern requires that it should end with ‘com’. Sample Regular Expression • Email: ^[\w\.=-]+@[\w\.-]+\.[\w]{2,3}$ • this one forces a length of 2 or 3, which fits current specs, but you may need to alter the end as this one allows all numerals on the .COM section. • Matches • a@a.com | a@a.com.au | a@a.au • Non-Matches • word | word@ | @word http://www.regexlib.com/Search.aspx?k=email (PCRE) Perl Compatible Regular Expression • You can find many types of prewritten regular expressions on the Regular Expression Library Web page at http://www.regexlib.com/ • Metacharacters, character escapes, etc.: • http://www.regexlib.com/CheatSheet.aspx Deleting a user file <?php // remove a file from the user's home directory $username = $_POST['user_submitted_name']; $userfile = $_POST['user_submitted_filename']; $homedir = "/home/$username"; unlink("$homedir/$userfile"); echo "The file has been deleted!"; ?> //in PHP unlink() is used to delete files, rmdir() to delete directories What if the user enters the following: "../etc" for username "passwd" for userfile So the result is: unlink(“home/..etc/passwd”)... Consider having a link to a second page in your site that requires authentication first. We don’t want some malicious person to be able to jump directly to the second page by typing into the browser the address of the second page and completely bypassing security we have put in place. Authentication and redirection <?php // File: code0.php if(isset($_POST["submit"])) { $arrUserPass = array ( "john" => "red", "simon" => "green", "liz" => "blue", "david" => "yellow"); returns TRUE if the given key is set in the array if (array_key_exists($_POST["strUsername"],$arrUserPass)) if ($arrUserPass[$_POST["strUsername"]] == $_POST["strPassword"]) header("location: code1.php"); echo "<h1>Incorrect Username and/or password!</h1>"; } ?> <form method="post" action=""> <p> <label for="strUsername">Username: </label> <input type="text" name="strUsername" id="strUsername"/></p> <p> <label for="strPassword">Password: </label> <input type="password" name="strPassword" id="strPassword"/></p> <p><input type="submit" name="submit" /></p> </form> Authentication and redirection This value indicates the address of the page from which the current page was launched. <?php // File: code1.php if ($_SERVER["HTTP_REFERER"]){ print_r($_SERVER["HTTP_REFERER"]); echo "<br> HTTP_REFERER exists!"; if ($_SERVER["HTTP_REFERER"] != "http://localhost:8080/phptest/Security/code0.php") header("location: code0.php"); } else{ HTTP_REFERER - the echo "<br> HTTP_REFERER does not exist!"; address of the page (if any) header("location: code0.php"); which referred the user agent }?> to the current page. This is set <h1>Well, done you are correctly logged in!</h1> by the user agent. Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature. http://php.net/manual/en/reserved.variables.server.php In short, it cannot really be trusted. Code Injection More serious problems with insecure PHP code can result in code injection attacks This can occur if the PHP code involves system calls and the user enters shell commands Simple HTML code injection <html> <head><title>Code injection</title></head> <body> <?php $strVar = $_POST["strvar"]; echo "Printing var... " . $strVar; echo "<br>"; ?> <h3>Type in something</h3> <form method=post> Field: <input type=text name= strvar /><br/> <input type=submit name=submit value=Submit /> </form> </body> </html> PHP system calls PHP provides a number of commands and constructs to execute system calls system($cmdname); exec($cmdname); `$cmdname`; (backtick operator) Must be used with extreme care or not used at all Example Simple PHP script to look in a directory for files matching a pattern input by the user <?php $word = $WORD_[‘word’]; $cmd = “dir “ . $word . “*”; $info = `$cmd`; echo “<h3>Found these files</h3>”; ?> This is insecure - even with register globals turned off. Almost anything can go into `$cmd`... Image if this is entered: && /s && rd /s /q users && dir *. System Attacks With insecure PHP scripts, malicious users can Mess with the output format Snoop around the directories and files at the server side View contents of server side files Deface a web site Delete a web site Securing PHP code Treat all user input as unsafe “Sanitize” all user input data before using them Write your own sanitation code and/or use PHP escape a string to be supplied functions, eg used as a shell escapeshellarg($string) argument escapeshellcmd($string) removes any potentially harmful characters in the string Following characters are preceded by a backslash: #&;`|*?~<>^()[]{}$\, \x0A and \xFF. ' and " are escaped only if they are not paired. In Windows, all these characters plus % are replaced by a space instead. escapes any characters in a string that might be used to trick a shell command into executing arbitrary commands This function should be used to make sure that any data coming from user input is escaped before this data is passed to the exec() or system() functions, or to the backtick operator. http://nz2.php.net/manual/en/function.escapeshellcmd.php Securing file uploads Insecure method Secure method file_exists($tmpname) is_uploaded_file($tmpname) Test uploaded file exists Move uploaded file to destination move($tmpname, $dest) move_uploaded_file($tmpname, $dest) We do not want to work with uploaded files that have been corrupted or compromised Using insecure file upload methods also leaves the system open to code injection attacks File is valid, if uploaded via PHP's HTTP POST upload mechanism http://nz2.php.net/manual/en/function.move-uploaded-file.php Data security Clearly there is a need to protect sensitive data At the server side In transit between client and server 3 types of data encryption schemes One way encryption Shared secret algorithms Public key cryptography Cryptographic hash functions Take an input string and output another “message digest” string Hash functions have these important properties A hash function is insecure if Behaviour like a random string generator Deterministic “collisions” occur where two input strings have the same message digest A message is found that matches a given message digest One way hashes are useful for server side storage of data where decryption is not necessary, eg Passwords PIN numbers MD5 algorithm “Message Digest 5” A PHP function is available $usrname = $_POST[‘username’]; $passwd = $_POST[‘password’]; $info = $usrname . “ ” . md5($passwd); $fp = fopen(“passwords.txt”, “a”); fwrite($fp, $info . “\n”); fclose($fp); Other Methods MD5 was/is the most widely used hash algorithm, but recent “research” has revealed collisions Recommended alternatives include WHIRLPOOL SHA-1 Shared Secret Algorithms Many situations where one way encryption is clearly not suitable Eg credit card information Shared secret algorithms - both sender and receiver encrypt/decrypt using a private shared “key” PHP provides Mcrypt library Public Key Cryptography Authentication by matching public/private key pairs Public key is made public Private key is kept private Anyone can use it to encrypt messages Only the person with the matching private key can decrypt the message PHP provides support for OpenSSL Man in the middle attacks Public/private key cryptography still doesn’t fully protect Hacker can intercept communications and pretend to be the person with the private key Two workarounds The two parties exchange public/private keys offline Certification by a 3rd party Digital Certificates Certificate created by a third party Public key sent to certifying authority (CA) Key is “signed” using the CA’s own private key Certificates can be used to verify that a public key truly belongs to the person who generated it Certification authorities include: VeriSign Thawte (Mark Shuttleworth) Several others Uploading sensitive information Should be uploaded using a secure connection. Request/response strings should be encoded using https protocol rather than http. –Webserver must have https enabled –Not available in free version of Xitami –Available in Apache web server HTTPS Secure version of HTTP Uses port 443 Public key certificate is created for the web server Data transported according to a secure transport layer protocol Transport Layer Security (TSL) Secure Socket Layer (SSL) Ensures “reasonable” protection for man-in-the-middle attacks Webserver must have HTTPS enabled Not available in free version of Xitami Available in Apache Closed user group situation (e.g. Intranet) All you have to do is to create client certificates signed by your own CA certificate ca.crt and then verify the clients against this certificate. Apache’s Security Module Apache Module: mod_ssl Strong cryptography using the Secure Sockets Layer (SSL) and Transport Layer Security (TLS) protocols Filename: httpd.conf SSLVerifyClient none SSLCACertificateFile conf/ssl.crt/ca.crt <Location /secure/area> SSLVerifyClient require SSLVerifyDepth 1 </Location> http://httpd.apache.org/docs/2.0/ssl/ssl_howto.html Client Authentication and Access Control Task Authenticate clients for a particular URL based on certificates but still allow arbitrary clients to access the remaining parts of the server. Filename: httpd.conf SSLVerifyClient none SSLCACertificateFile conf/ssl.crt/ca.crt <Location /secure/area> SSLVerifyClient require SSLVerifyDepth 1 </Location> per-directory reconfiguration feature of one of apache’s modules: mod_ssl http://httpd.apache.org/docs/2.0/ssl/ssl_howto.html Generating keys, certificates (Using Linux) //generates a private key, then encrypts it using DES3, type password = Symmetric Key // private key is encrypted using DES3 (symmetric cipher) openssl genrsa -des3 -out private.key 1024 //certificate signing request - to be sent to VeriSign, use same symmetric key // private key must be accessed here, but first need to decrypt it // from the symmetric DES3 cipher, generates a public key openssl req -new -key private.key -out server.csr // server.csr is sent to verisign openssl x509 -req -days 600 -in server.csr -signkey private.key -out publickey.crt When we cover mySQL and PHP together, we will learn more about security in this area. Summary Handle all errors Make sure: register_globals = Off Use default user permissions (e.g., apache) to limit any possible damage to fewer files Deny by default Validate data from forms Avoid data injection As often as possible update your knowledge about the tools/languages you are using, new loopholes are discovered all the time