When talking about security issues, you must note that in addition to the actual platform and operating system security issues, you also need to ensure that you write secure applications. When writing a PHP application, apply the following seven habits to ensure the best security of the application:
• Verification Input
• Protect file systems
• Protect Databases
• Protect session data
• Cross-site scripting (XSS) vulnerability Protection
• Validation form post
• Cross-Site Request Forgeries (CSRF) Protection
Verification Input
When talking about security issues, verifying data is the most important habit you may use. When it comes to input, it is very simple: do not trust users. Your users may be excellent, and most users may use the application as expected. However, as long as the input opportunity is provided, there may be very bad input. As an application developer, you must prevent the application from receiving incorrect input. Careful consideration of the locations and correct values entered by the user will allow you to build a robust and secure application.
This article introduces the interaction between the file system and the database,
General verification tips for various verifications are listed below:
• Use values in the whitelist
• Always reverify limited options
• Use built-in escape Functions
• Verify the correct data type (such as numbers)
The value (White-listed value) in the whitelist is the correct value, which is opposite to the invalid Black-listed value. The difference between the two is that during verification, the list or range of possible values is smaller than the list or range of invalid values, many of which may be unknown values or unexpected values.
When performing verification, remember that it is generally easier to design and verify the value that the application allows to use than to prevent all unknown values. For example, to limit the field value to all numbers, you need to write a routine to ensure that all input values are numbers. Do not compile a routine that is used to search for non-numeric values and is marked as invalid when a non-numeric value is found.
Protect file systems
In July 2000, a Web site leaked customer data stored in Web server files. A visitor of the Web site uses a URL to view files containing data. Although the file is misplaced, this example highlights the importance of protecting the file system for attackers.
If the PHP application processes the file at will and contains variable data that can be entered by the user, check the user input carefully to ensure that the user cannot perform any inappropriate operations on the file system. Listing 1 shows a sample PHP site for downloading images with a specified name.
Listing 1. Download an object
Copy codeThe Code is as follows: <? Php
If ($ _ POST ['submit '] = 'Download '){
$ File = $ _ POST ['filename'];
Header ("Content-Type: application/x-octet-stream ");
Header ("Content-Transfer-Encoding: binary ");
Header ("Content-Disposition: attachment; filename = \" ". $ file ."\";");
$ Fh = fopen ($ file, 'R ');
While (! Feof ($ fh ))
{
Echo (fread ($ fh, 1024 ));
}
Fclose ($ fh );
} Else {
Echo ("Echo ("title> Guard your filesystem </title> Echo ("<body> <form id = \" myFrom \ "action = \" ". $ _ SERVER ['php _ SELF '].
"\" Method = \ "post \"> ");
Echo ("<div> <input type = \" text \ "name = \" fileName \ "value = \"");
Echo (isset ($ _ REQUEST ['filename'])? $ _ REQUEST ['filename']: '');
Echo ("\"/> ");
Echo ("<input type = \" submit \ "value = \" Download \ "name = \" submit \ "/> </div> ");
Echo ("</form> </body> }
As you can see, the dangerous scripts in Listing 1 process all the files that the Web server has read permission to, including the files in the Session Directory (see "protect session data "), it even includes some system files (such as/etc/passwd ). For demonstration, this example uses a text box that allows you to type a file name, but you can easily provide a file name in the query string.
Configuring user input and file system access permissions at the same time is very dangerous, so it is best to design the application to use the database and hide the generated file name to avoid simultaneous configuration. However, this is not always effective. Listing 2 provides sample routines for verifying file names. It uses regular expressions to ensure that only valid characters are used in the file name, and the dot character:... is checked in particular :...
Listing 2. Check valid file name characters
Copy codeThe Code is as follows: function isValidFileName ($ file ){
/* Don't allow .. and allow any "word" character \/*/
Return preg_match ('/^ (((? :\.)(?! \.) | \ W) + $/', $ file );
}
Protect Databases
In April 2008, the prison administration of a certain U.S. state used the SQL column name in the query string, which leaked confidential data. This leak allows malicious users to select columns to be displayed, submit pages, and obtain data. This leak shows how users can execute input in unpredictable ways, and shows the necessity to defend against SQL injection attacks.
Listing 3 shows the sample script for running SQL statements. In this example, SQL statements are dynamic statements that allow the same attacks. The owner of this form may think that the form is safe because they have defined the column name as a selection list. However, the Code ignores the last habit of form spoofing-limiting the option to a drop-down box does not mean that others cannot publish a form containing the required content (including the asterisk [*]).
Listing 3. Execute SQL statements
Copy codeThe Code is as follows: <Head>
<Title> SQL Injection Example </title>
</Head>
<Body>
<Form id = "myFrom" action = "<? Php echo $ _ SERVER ['php _ SELF '];?> "
Method = "post">
<Div> <input type = "text" name = "account_number"
Value = "<? Php echo (isset ($ _ POST ['account _ number'])?
$ _ POST ['account _ number']: '');?> "/>
<Select name = "col">
<Option value = "account_number"> Account Number </option>
<Option value = "name"> Name </option>
<Option value = "address"> Address </option>
</Select>
<Input type = "submit" value = "Save" name = "submit"/> </div>
</Form>
<? Php
If ($ _ POST ['submit '] = 'save '){
/* Do the form processing */
$ Link = mysql_connect ('hostname', 'user', 'Password') or
Die ('could not connect '. mysql_error ());
Mysql_select_db ('test', $ link );
$ Col = $ _ POST ['col'];
$ Select = "SELECT". $ col. "FROM account_data WHERE account_number ="
. $ _ POST ['account _ number']. ";";
Echo '<p>'. $ select. '</p> ';
$ Result = mysql_query ($ select) or die ('<p>'. mysql_error (). '</p> ');
Echo '<table> ';
While ($ row = mysql_fetch_assoc ($ result )){
Echo '<tr> ';
Echo '<td>'. $ row [$ col]. '</td> ';
Echo '</tr> ';
}
Echo '</table> ';
Mysql_close ($ link );
}
?>
</Body>
</Html>
Therefore, to protect the database, try to avoid using dynamic SQL code. If dynamic SQL code cannot be avoided, do not directly use the input for the column. Listing 4 shows that in addition to using static columns, you can also add a simple verification routine to the account number field to ensure that the input value is not a non-numeric value.
Listing 4. Protection through verification and mysql_real_escape_string ()
Copy codeThe Code is as follows: <Head>
<Title> SQL Injection Example </title>
</Head>
<Body>
<Form id = "myFrom" action = "<? Php echo $ _ SERVER ['php _ SELF '];?> "
Method = "post">
<Div> <input type = "text" name = "account_number"
Value = "<? Php echo (isset ($ _ POST ['account _ number'])?
$ _ POST ['account _ number']: '');?> "/> <Input type =" submit"
Value = "Save" name = "submit"/> </div>
</Form>
<? Php
Function isValidAccountNumber ($ number)
{
Return is_numeric ($ number );
}
If ($ _ POST ['submit '] = 'save '){
/* Remember habit #1 -- validate your data! */
If (isset ($ _ POST ['account _ number']) &
IsValidAccountNumber ($ _ POST ['account _ number']) {
/* Do the form processing */
$ Link = mysql_connect ('hostname', 'user', 'Password') or
Die ('could not connect '. mysql_error ());
Mysql_select_db ('test', $ link );
$ Select = sprintf ("SELECT account_number, name, address ".
"FROM account_data WHERE account_number = % s ;",
Mysql_real_escape_string ($ _ POST ['account _ number']);
Echo '<p>'. $ select. '</p> ';
$ Result = mysql_query ($ select) or die ('<p>'. mysql_error (). '</p> ');
Echo '<table> ';
While ($ row = mysql_fetch_assoc ($ result )){
Echo '<tr> ';
Echo '<td>'. $ row ['account _ number']. '</td> ';
Echo '<td>'. $ row ['name']. '</td> ';
Echo '<td>'. $ row ['address']. '</td> ';
Echo '</tr> ';
}
Echo '</table> ';
Mysql_close ($ link );
} Else {
Echo "<span style = \" font-color: red \ "> ".
"Please supply a valid account number! </Span> ";
}
}
?>
</Body>
</Html>
This example also shows the usage of the mysql_real_escape_string () function. This function filters your input correctly, so it does not include invalid characters. If you have been dependent on magic_quotes_gpc, you must note that it has been discarded and will be deleted in PHP V6. Avoid using it from now on and write secure PHP applications in this case. In addition, if ISP is used, it is possible that magic_quotes_gpc is not enabled for your ISP.
Finally, in the improved example, you can see that the SQL statement and output do not include the dynamic column option. If you add columns to a table that later contains different information, you can output these columns. If you want to use the framework in combination with the database, your framework may have performed SQL verification for you. Ensure that documents are reviewed to ensure the security of the Framework. If you are still unsure, verify the documents to ensure security. Other verification is required even if the framework is used for database interaction.
Protect sessions
By default, session information in PHP is written to the temporary directory. Consider the form in listing 5, which shows how to store the user ID and account number in the session.
Listing 5. storing data in a session
Copy codeThe Code is as follows: <? Php
Session_start ();
?>
<Html>
<Head>
<Title> Storing session information </title>
</Head>
<Body>
<? Php
If ($ _ POST ['submit '] = 'save '){
$ _ SESSION ['username'] =_ _ POST ['username'];
$ _ SESSION ['accountnumber'] = $ _ POST ['accountnumber'];
}
?>
<Form id = "myFrom" action = "<? Php echo $ _ SERVER ['php _ SELF '];?> "
Method = "post">
<Div> <input type = "hidden" name = "token" value = "<? Php echo $ token;?> "/>
<Input type = "text" name = "userName"
Value = "<? Php echo (isset ($ _ POST ['username'])? $ _ POST ['username']: '');?> "/>
<Br/>
<Input type = "text" name = "accountNumber"
Value = "<? Php echo (isset ($ _ POST ['accountnumber'])?
$ _ POST ['accountnumber']: '');?> "/>
<Br/>
<Input type = "submit" value = "Save" name = "submit"/> </div>
</Form>
</Body>
</Html>
Listing 6 shows the content of the/tmp directory.
Listing 6. session files in the/tmp directoryCopy codeThe Code is as follows:-rw ------- 1 _ www wheel 97 Aug 18 20:00 sess_9e4233f2cd7cae35866cd8b61d9fa42b
As you can see, during output (see listing 7), session files contain information in a very readable format. Since the file must be read and written by Web Server users, session files may cause serious problems for all users on the shared server. Someone except you can write scripts to read these files, so you can try to retrieve the value from the session.
Listing 7. session file content
Copy codeThe Code is as follows: userName | s: 5: "ngood"; accountNumber | s: 9: "123456789 ";
Storage Password
No matter in the database, session, file system, or any other form, passwords cannot be stored as plain text. The best way to process passwords is to encrypt and store passwords that are encrypted. In practice, passwords are still stored in plain text. If you use a Web site that can send a password instead of reset a password, it means that the password is stored in plain text or the code for decryption can be obtained (if encrypted ). Even the latter, you can find and use the decryption code.
You can take two actions to protect session data. The first is to encrypt all the content you put into the session. However, encrypted data does not mean absolute security. Therefore, use this method as the only way to protect sessions. The alternative is to store session data in other locations, such as databases. You still have to ensure that the database is locked, but this solution solves two problems: first, it puts data in a safer location than the shared file system; second, it makes it easier for your applications to span multiple Web servers and share sessions across multiple hosts.
To implement session persistence, see the session_set_save_handler () function in PHP. You can store session information in a database or implement a processing program used to encrypt and decrypt all data. Listing 8 provides implementation function usage and function skeleton examples. You can also view how to use the database in the reference section.
Listing 8. Sample session_set_save_handler () function
Copy codeThe Code is as follows: function open ($ save_path, $ session_name)
{
/* Custom code */
Return (true );
}
Function close ()
{
/* Custom code */
Return (true );
}
Function read ($ id)
{
/* Custom code */
Return (true );
}
Function write ($ id, $ sess_data)
{
/* Custom code */
Return (true );
}
Function destroy ($ id)
{
/* Custom code */
Return (true );
}
Function gc ($ maxlifetime)
{
/* Custom code */
Return (true );
}
Session_set_save_handler ("open", "close", "read", "write", "destroy", "gc ");
Protects against XSS vulnerabilities
XSS vulnerabilities represent most of the vulnerabilities on all archived Web sites in 2007 (see references ). When a user can inject HTML code into your Web page, an XSS vulnerability occurs. HTML code can carry JavaScript code in the script tag, so JavaScript can be run as long as the page is extracted. The form in listing 9 can represent a forum, wiki, social network, or any other site that can input text.
Listing 9. Input text form
Copy codeThe Code is as follows: <Head>
<Title> Your chance to input XSS </title>
</Head>
<Body>
<Form id = "myFrom" action = "showResults. php" method = "post">
<Div> <textarea name = "myText" rows = "4" cols = "30"> </textarea> <br/>
<Input type = "submit" value = "Delete" name = "submit"/> </div>
</Form>
</Body>
</Html>
Listing 10 demonstrates how to output results from forms that allow XSS attacks.
Listing 10. showResults. phpCopy codeThe Code is as follows: <Head>
<Title> Results demonstrating XSS </title>
</Head>
<Body>
<? Php
Echo ("<p> You typed this: </p> ");
Echo ("<p> ");
Echo ($ _ POST ['mytext']);
Echo ("</p> ");
?>
</Body>
</Html>
Listing 11 provides a basic example in which a new window is displayed and the Google homepage is opened. If your Web application is not protected against XSS attacks, it will cause serious damage. For example, a person can add links that mimic the site style to achieve the goal of spoofing (see references ).
Listing 11. Malicious text input example
Copy codeThe Code is as follows: <script type = "text/javascript"> myRef = window. open ('HTTP: // www.google.com ', 'mywin ',
'Left = 20, top = 20, width = 500, height = 500, toolbar = 1, resizable = 0'); </script>
To prevent XSS attacks, as long as the variable value is printed to the output, the input needs to be filtered using the htmlentities () function. Remember to follow the first habit: Use the value in the white list in the input of the Web application name, email address, phone number, and Bill information to verify the input data.
The following shows a safer page for displaying text input.
List 12. Safer forms
Copy codeThe Code is as follows: <Head>
<Title> Results demonstrating XSS </title>
</Head>
<Body>
<? Php
Echo ("<p> You typed this: </p> ");
Echo ("<p> ");
Echo (htmlentities ($ _ POST ['mytext']);
Echo ("</p> ");
?>
</Body>
</Html>
Protects against invalid post
Form spoofing means that someone sends a post from an inappropriate position to your form. The simplest way to cheat forms is to create a Web page that passes all values by submitting to the form. Because Web applications are stateless, there is no absolutely feasible method to ensure that published data comes from a specified location. From the IP address to the host name, all content can be spoofed. Listing 13 shows a typical form that allows input.
Listing 13. Handling text forms
Copy codeThe Code is as follows: <Head>
<Title> Form spoofing example </title>
</Head>
<Body>
<? Php
If ($ _ POST ['submit '] = 'save '){
Echo ("<p> I am processing your text :");
Echo ($ _ POST ['mytext']);
Echo ("</p> ");
}
?>
</Body>
</Html>
Listing 14 shows the form that will be published to the form shown in listing 13. To try this operation, you can put the form in the Web site and save the code in listing 14 as an HTML document on the desktop. After saving the form, open it in the browser. You can then fill in the data and submit the form to observe how to process the data.
Listing 14. Data collection forms
Copy codeThe Code is as follows: <Head>
<Title> Collecting your data </title>
</Head>
<Body>
<Form action = "processStuff. php" method = "post">
<Select name = "answer">
<Option value = "Yes"> Yes </option>
<Option value = "No"> No </option>
</Select>
<Input type = "submit" value = "Save" name = "submit"/>
</Form>
</Body>
</Html>
The potential impact of form spoofing is that if you have a form that contains a drop-down box, a single-choice button, a checkbox, or other forms that restrict input, these restrictions do not make sense when the form is spoofed. Consider the code in listing 15, which contains a form with invalid data.
Listing 15. Forms with invalid data
Copy codeThe Code is as follows: <Head>
<Title> Collecting your data </title>
</Head>
<Body>
<Form action = "http://path.example.com/processStuff.php"
Method = "post"> <input type = "text" name = "answer"
Value = "There is no way this is a valid response to a yes/no answer..."/>
<Input type = "submit" value = "Save" name = "submit"/>
</Form>
</Body>
</Html>
Think about it:If you have a drop-down box or single-choice button that limits user input, you may consider that you do not have to worry about verifying input. After all, the input form ensures that the user can only input some data, right? To restrict form spoofing, verification is required to ensure that the identity of the publisher is true. You can use a one-time tag. Although this technology still cannot ensure that the form is absolutely safe, it makes form spoofing more difficult. Because the flag is changed every time a form is called, to become an attacker, you must obtain an instance of the form to be sent, remove the flag, and put it in a false form. Using this technology can prevent malicious users from building persistent Web forms to publish Inappropriate Requests to applications. Listing 16 provides a form tag example.
Listing 16. Using a one-time form tag
Copy codeThe Code is as follows: <? Php
Session_start ();
?>
<Html>
<Head>
<Title> SQL Injection Test </title>
</Head>
<Body>
<? Php
Echo 'session token = '. $ _ Session ['Token'];
Echo '<br/> ';
Echo 'token from form = '. $ _ POST ['Token'];
Echo '<br/> ';
If ($ _ SESSION ['Token'] == _ POST ['Token']) {
/* Cool, it's all good... create another one */
} Else {
Echo '}
$ Token = md5 (uniqid (rand (), true ));
$ _ SESSION ['Token'] = $ token;
?>
<Form id = "myFrom" action = "<? Php echo $ _ SERVER ['php _ SELF '];?> "
Method = "post">
<Div> <input type = "hidden" name = "token" value = "<? Php echo $ token;?> "/>
<Input type = "text" name = "myText"
Value = "<? Php echo (isset ($ _ POST ['mytext'])? $ _ POST ['mytext']: '');?> "/>
<Input type = "submit" value = "Save" name = "submit"/> </div>
</Form>
</Body>
</Html>
CSRF Protection
Cross-Site Request Forgery (CSRF) is the result of the attack using user permissions. In CSRF attacks, your users can easily become unanticipated accomplices. Listing 17 provides page examples for performing specific operations. This page searches for user logon information from cookies. As long as the cookie is valid, the Web page will process the request.
Listing 17. CSRF example
Copy codeThe Code is as follows:
CSRF attacks usually appear in the form of a flag, because the browser will call the URL without knowing it to obtain the image. However, the image source can be the page URL of the same site that is processed based on input parameters. When this flag is combined with XSS attacks-the most common in archive attacks-users can easily perform operations on their creden without knowledge-therefore forged.
To protect you from CSRF attacks, you need to use the one-time marking method used to verify form post. In addition, use an explicit $ _ POST variable instead of $ _ REQUEST. Listing 18 demonstrates a bad example of processing the same Web page-whether to call the page through a GET request or publish a form to the page.
Listing 18. Getting data from $ _ REQUEST
Copy codeThe Code is as follows: <Head>
<Title> Processes both posts AND gets </title>
</Head>
<Body>
<? Php
If ($ _ REQUEST ['submit '] = 'save '){
Echo ("<p> I am processing your text :");
Echo (htmlentities ($ _ REQUEST ['text']);
Echo ("</p> ");
}
?>
</Body>
</Html>
Listing 19 shows a clean page that only uses form POST.
Listing 19. Only get data from $ _ POST
Copy codeThe Code is as follows: <Head>
<Title> Processes both posts AND gets </title>
</Head>
<Body>
<? Php
If ($ _ POST ['submit '] = 'save '){
Echo ("<p> I am processing your text :");
Echo (htmlentities ($ _ POST ['text']);
Echo ("</p> ");
}
?>
</Body>
</Html>
Conclusion
Starting from these seven habits, writing safer PHP Web applications can help you avoid being the victim of malicious attacks. Like many other habits, these habits may be difficult to adapt to at first, but those habits will become more and more natural over time.
Remember that the first habit is the key: Verify the input. After the input does not include invalid values, you can continue to protect the file system, database, and session. Finally, make sure that PHP code can defend against XSS, form spoofing, and CSRF attacks. These habits can help you defend against some simple attacks.