This post is going to talk about source code reviewing PHP and demonstrate how a relatively small chunk of code can cause you lots of problems.
The Code
In this article we are going to analyze the code displayed below. The code displayed below might seem innocent for some , but obviously is not. We are going to assume that is used by some web site to validate the credentials and allow the users to login.
<?php
require_once 'commonFunctionality.php';
if (validateCredentials($someUsername, $somePassword)) {
header('Location: myIndex.php'); }
else {
header('Location: wrong_login.php'); }
?>
If you look carefully the code you will se that the code is vulnerable to the following issues:
- Reflected/Stored XSS
- Session Fixation/Session Hijacking
- Lock Out Mechanism Not In Place
Session Fixation/Session Hijacking
An adversary may on purpose exploit this vulnerability without the need of developing any costume tools (e.g. the session gets exposed in a blog post or within the same application or is passed in the http referrer and gets cached in a Web Proxy controlled by an adversary). Also this attack might be used to abuse user privileges (e.g. escalate privileges of one user by manipulating the session identifier, perform vertical and horizontal privilege escalation etc.). It should be noted at this point that the issues described above are possible only if the web application makes decisions based only on the session identifier.
Vulnerable Code:
session_unset(); // Improper handling of the session.
Explanation:
The function shown above does not properly handle the session. The session_unset function just clears the $_SESSION variable. It’s equivalent to doing $_SESSION = array(); So this does only affect the local $_SESSION variable instance, but not the session data in the session storage, everything else remains unchanged, including the session identifier. In this occasion the session_unset is used to clear the session from user information, instead of the session_destroy function in the login page (instead of the logout page), which translates into not logging out properly the previous user (e.g. the next user will possibly again access to the account of the previous user).The Web Application makes decisions without evaluating other cookie parameters to give access to Web Resources (e.g. the decision making process is the username, a variable called logged_in and the session id). Ideally this should partly be fixed by using also another variable e.g. $_SESSION[‘logged_in’] = true (see code below).
Exploitation:
An adversary may on purpose exploit this vulnerability without the need of developing any costume tools (e.g. the session gets exposed in a blog post or within the same application or is passed in the http referrer and gets cached in a Web Proxy controlled by an adversary). Also this attack might be used to abuse user privileges (e.g. escalate privileges of one user by manipulating the session identifier, perform vertical and horizontal privilege escalation etc.). It should be noted at this point that the issues described above are possible only if the web application makes decisions based only on the session identifier.
Business Impact:
The possibility of this vulnerability going public (e.g. blog posts start appearing in the internet revealing the issue) would cause severe costumer reputation and revenue loss; this vulnerability allows an adversary to potentially launch personalized phishing attacks (e.g. deceive a user in clicking a link with a fixed session etc.) abuse web application user privileges and possibly allow phishing campaigns.
Remedial Code:
function init_session() { ...
session_start(); // Start the php session
session_regenerate_id(true); // regenerated the session, delete the old one. $_SESSION['logged_in'] = true;
... }
Regenerate the session ID anytime the session's status changes. That means any of the following:-
User authentication (e.g. in the login page, other multiple authentication stages etc.).
-
Storing privilege level information in the session (e.g. temporary random variables, valid only
for the current session etc.)
- Regenerate the session identifier whenever the user's privilege level changes.
An adversary may on purpose exploit this vulnerability without the need of developing any costume tools (e.g. make use of Burp Intruder or Hydra to perform online password cracking attacks etc.).
Vulnerable Code:
$username = $_POST['username']; $password = $_POST['password'];
Note: The Web Application should implement server side controls in the login page to prevent
password brute forcing attacks.Remedial Code:
function lockout($username, $password) { $now = time();
$counter = 0
if (validateCredentials){
$counter = $counter+1// Save that in database, retrieve login attempt times and compare the
times ...
} }
The Web Application should take the following actions to prevent online dictionary attacks:
-
Make use of login attempt counters (e.g. allow 3 failed attempts within 30 minutes).
-
Associate the user IP with the session (e.g. generate proper audit trails to later on ban that ip).
Include the user's IP address from $_SERVER['REMOTE_ADDR'] in the session. Store it in
$_SESSION['remote_ip'].
-
Run integrity checks of the session (although this functionality might be included in another
function).
-
Include the user agent from $_SERVER['HTTP_USER_AGENT'] in the session. Store it in a session
variable $_SESSION['user_agent']. Then, on each subsequent request check that it matches (Note: The user agent can be very easily spoofed).
Reflected/Stored XSS
An adversary can exploit this vulnerability without the need of developing any costume tools. Point and click tools are available in the Internet and might be used to exploit this vulnerability (e.g. Social Engineering Tool etc.). Further escalating on the issue an adversary might use this attack to compromise multiple company sites (e.g. make use of it as an XSS proxy).
Note: This might also lead into unrestricted redirection attacks. Due to limited amount of time in my disposal no further investigation was conducted (e.g. load the login page to an Apache as and see if the variable username is passed the URL or the location header field.)
Vulnerable Code:
$_SESSION['username'] = $username;
Note: Even though we don’t have access to the rest of the Web App code, it is highly likely that the username value might be displayed back to the user and the Http header fields.
Remedial Code:
Provide Server Side filters for the vulnerability. Make use of regular expressions and html encode the
variables whether displayed back to the user or not (for providing security in depth and making sure
that the Set-‐Cookie header field or other fields cannot be abused).
1st Layer of defense
1st Layer of defense
$username = preg_match ("/[^a-‐zA-‐Z0-‐9_\-‐]+/", "", $username)
Note: Ideally the username should be replaced with a temporary user id (preferable random that expires along with the cookie session). Using regular expressions to replace parts of the input and proceed with further processing the input is not recommended, once a malicious input is identified should be rejected (e.g. using preg_replace instead of preg_match). Also note that this functionality should ideally be also part of the validateCredentials function or the input should be processed before used by the validateCredentials function.
2nd Layer of defense
1. // This function will convert both double and single quotes.
2. htmlentities($username , ENT_QUOTES);
Input:
<script>alert(1)</script>
Output:
<script>aler 4;(1)</script>
Note: With htmlentities, all characters which have HTML character entity equivalents are translated into these entities (displayed above).
References:
- https://www.owasp.org/index.php/Account_lockout_attack
- http://stackoverflow.com/questions/17217777/difference-‐between-‐unset-‐and-‐session-‐unset-‐ in-‐php
- http://shiflett.org/articles/session-‐fixation
- http://shiflett.org/articles/session-‐hijacking