TABLE OF CONTENTS (HIDE)

Advanced Webapp Development - Webapp Security

 

SQL Injection

Reference: Wikipedia's SQL Injection @ http://en.wikipedia.org/wiki/SQL_injection

SQL Injection is a special form of "Code Injection Attack", whereas the attackers try to rewrite you SQL statements to obtain undesired outputs.

For example, suppose that the following SQL statement is used in your server-side script for user authentication and username and password is read from the user inputs:

sqlStr = "SELECT username FROM users WHERE username='" + username + "' AND password='" + password + "'"

A malicious attacker could supply:

username = ' OR '1'='1
sqlStr = "SELECT username FROM users WHERE username='' OR '1'='1' AND password='xxx';"
      -- Returns true
 
username = ' OR '1'='1' --
sqlStr = "SELECT username FROM users WHERE username='' OR '1'='1' -- ...'"
      -- Forcing the rest of statement, if any, as comment
 
username = x';DROP TABLE users;
sqlStr = "SELECT username FROM users WHERE username ='a';DROP TABLE users;'..."
      -- Piggyback another malicious SQL statement.
      -- But most SQL implementations do not permit multiple statements in one query

To prevent SQL Injection, in your server-side script:

  1. Validate user inputs. Discard invalid inputs. For example, username shall accept characters of [a-zA-Z0-9]; and shall not accept single-quote, double-quote and whitespace.
  2. Use SQL Prepared-Statements (aka Parameterized statements), instead of normal SQL Statement. For example, in SELECT username FROM users WHERE username = ? AND password = ?, username and password are passed and treated as parameters, i.e., single-quote will be treated as single-quotes (\').

[TODO] explanation

Cross Site Scripting (XSS)

Reference: OWASP's "Cross-site Scripting (XSS)" @ https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29

Cross-Site Scripting (XSS) is a special form of "Code Injection Attack", whereas the attackers inject malicious client-side script (e.g., JavaScript) into your web pages (e.g., through the comment field) that would be saved in database. When another user retrieves and views the stored data, the client-side script will be executed on the victim's browser. The malicious script can access cookies, session, or other sensitive information retained by your browser. These scripts can easily rewrite the contents of the HTML page so as to de-face your web site.

XSS can even be carried out without the <script>...</script> tags, but through the event handlers such as onload(), onerror(), onmouseover(), e.g.,

<body onload=alert('hahaha!')>
<img src="http://url-to-a-non-existent-file" onerror=alert('hahaha!');>
<p onmouseover=alert('hahaha!')>click me!</p>

Via the innocent <img>'s src attribute or any attribute that accepts URL of "javascript:..."

<img src=javascript:alert('hahaha!')>
<img src=Javascript:alert('hahaha!')>
      // Hide letter j to prevent filtering 'javascript'

To prevent XSS, in your server-side script:

  • Validate user inputs. Discard invalid inputs.
  • Sanitize (or encode) the user inputs to disable all scripts BEFORE storing the data, i.e., before the data is viewed and possibly executed by other users.

[TODO] explanation

Cross-Site Request Forgery (CSRF)

Cross-Site Request Forgery (CSRF) occurs when a malicious action is injected to cause a user’s Web browser to perform an unwanted action on a trusted site for which the user is currently authenticated. For example, Alice logins to her e-banking site. Alice then visits the malicious website, which runs a (hidden) request on Alice’s browser targeting the e-banking site to do fund transfer (e.g. http://e-bank.com/transfer?id=1234&amount=9999). The request is allowed as Alice has been authenticated during the browser session.

To prevent CSRF: Each request shall include a synchronizer token to identify the user’s session. The server-side script generates a token and stores in the session, after the login is authenticated. This token will be used for each subsequent request until the session expires. It would be hard for the malicious attacker to predict or intercept this per-session token.

For example, in PHP:

session_start();
// Generate an anti-CSRF token if one doesn't exist
if ( !isset($_SESSION['token']) ) {
   $_SESSION['token'] = sha1(uniqid(mt_rand(), TRUE));
      // mt_rand - Generate a better random value.
      // uniqid(mt_rand(), TRUE) - Generate a unique ID with prefix mt_rand() and
      // more_entropy TRUE to return a 23-character string.
}

// All subsequent client's requests shall include this token, e.g.
//    <input type="hidden" name="token" value="$_SESSION[token]" />
// The server-side script shall authenticate the token before processing the requests.

User Input Validation

[TODO]

User Input Sanitization

Html Purifier (for PHP)

[TODO]

Hashed Password

Reference: "Salted Password Hashing - Doing it Right" @ https://crackstation.net/hashing-security.htm.

Plain-text password should not be stored in the database (because you use the same password for all your e-banks, and what if one is compromised). Instead, we store a hash value of the password. Hash is a one-way function, which is supposedly hard to reverse. In other words, if the password hash is compromised, the attacker is not able to get the plain-text password.

Hashing is, however, susceptible to these attacks:

  • Dictionary and Brute-Force attack: In dictionary attack, the attacker matches the hash with words in dictionary (consisting of common passwords); while in brute-force attack, he tries all combinations of characters. These attacks cannot be prevented. You can increase the password length, enforce a combination of letters, digits and punctuation characters, choose a better hashing algorithm to make brute-force impractical.
  • Lookup tables: pre-compute hashes of password dictionary. A special form called Rainbow tables is known to be very efficient in cracking hashes.

To prevent cracking password hash: Add salt to plain-text password before hashing. The salt shall be random, such that users having the same password would have different hashes. The salt effectively increases the password length which makes brute-force attack and table lookup impractical. Its randomness could avoid dictionary-based attacks and table lookup.

For example, you can compute the password hash based on 7-character random salt concatenated with the password, using SHA1 to obtain a 40-character hash. You need to store both the 7-character salt and the 40-character hash (total 47 character) in the database for password verification.

For example, in PHP, to create password hash:

define('PW_SALT_LENGTH', 7);
// create a random 7-character salt
$pwsalt = substr(md5(time()), 0, PW_SALT_LENGTH);
$pwHash = $pwsalt . sha1($pwsalt . $password);

To verify password:

// Retrieve password hash from database
$pwHash = .....;   // salt . sha1(salt . password)
// Retrieve the salt (first 7 characters)
define('PW_SALT_LENGTH', 7); 
$pwSalt = substr($pwHash, 0, PW_SALT_LENGTH);
// Compute password hash for the input password, using the retrieved salt
$inPwHash = $pwSalt . sha1($pwSalt . $password);
// Compare password hashes
if ($inPwHash === $pwHash) { .... }
PHP 5.5 Function password_hash()

Using bcrypt instead of the older and weaker MD5 or SHA1, with automatically generated random salt.

It consists of 4 functions:

password_hash($password, $algo, [$options])
   // Hash the given password using the algorithm, with options (of salt and cost).
   // Return the hash.
   //
   // $algo:
   // PASSWORD_DEFAULT: used the default algorithm, which is bcrypt in PHP 5.5,
   //    but may change in future releases. The hash length is 60 but may expand to 255.
   // PASSWORD_BCRYPT: use bcrypt to produce a 60-character hash.
   //
   // $option: associative array of 'cost' (default 10) and 'salt' (default auto generated).

password_verify($password, $hash)
    // Verify the password against the hash. Return true/false.
    
password_needs_rehash($hash, $algo, [$options])
    // Check if the given hash is created using the algorithm and options.
    // Return true/false.
 
password_get_info($hash)
    // Returns the hashing algorithm and options used in the given hash.

For example,

$password = 'secured';
$hash = password_hash($password, PASSWORD_DEFAULT);
echo "$hash\n";
   // $2y$10$WAhQfgaZoQmjDZjA2kgcOO/UXonfjqs9JWT/AuV93saKS5v6oL05i
   // $2y$ (blowfish algorithm), 10 (cost), $, 60-character hash using a random salt
   // If you run again, you will get a different hash, due to the randomly generated salt, e.g.,
   // $2y$10$smFyfxYl1rsSV4LFyEOzduTmPP0KgBa6H2xTzNOWXaw6jz9GgYixu
   // The salt is incorporated into the hash.

// Verify
echo password_verify($password, $hash) ? "matched!\n" : "not matched!\n";
echo password_verify('attack', $hash) ? "matched!\n" : "not matched!\n";

// With $options of salt and cost
$options = [
   'salt' => 'abcdefghijklmnopqrstuv', // use our own salt, at least 22 characters
   'cost' => 12  // instead of the default cost of 10
];
$hash = password_hash($password, PASSWORD_DEFAULT, $options);
echo $hash . "\n";
   // $2y$12$abcdefghijklmnopqrstuuHYKfFDux.AXqYz/kBRhcjazKAMQBm0m
   // If you run again, you get the same hash because the same salt is used.
   // Clearly, the first 22 characters in the hash is the salt.
   // To generate a random salt, you may use (but not recommended):
   //   mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)
   
// Rehash?
if (password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 14])) {
    // the password needs to be rehashed as it was not generated with
    // the specified algorithm or the cost
    $hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 14]);
    echo "new hash is $hash\n";
    // don't forget to store the new hash!
}

// Note on cost: high cost deters brute-force and dictionary attacks,
//   as the attacker needs to run the hashing function many many times,
//   but you only need to run once.
// The cost is a value between 4 to 31. Each increment doubles the complexity.

// Hash info
$info = password_get_info($hash);
var_dump($info);
   // array(3) {
   //    ["algo"] => int(1)
   //    ["algoName"] => string(6) "bcrypt"
   //    ["options"] => array(1) {
   //       ["cost"] => int(14)
   //    }
   // }

For PHP >= 5.3.7 but < 5.5, there is a library at https://github.com/ircmaxell/password_compat that is intended to provide forward compatibility with the password_* functions.

AppArmor for Program Access Control

References:

  1. "AppArmor Wiki" at https://wiki.ubuntu.com/AppArmor.
  2. Bodhi.zazen's "Introduction to AppArmor" at http://ubuntuforums.org/showthread.php?t=1008906.
  3. "Part IV. Confining Privileges with AppArmor" in "openSUSE 12.3 Security Guide" at http://doc.opensuse.org/documentation/html/openSUSE/opensuse-security/part.apparmor.html.
  4. "AppArmor Documentation" at http://wiki.apparmor.net/index.php/Documentation.

What is AppArmor?

"AppArmor is a Mandatory Access Control (MAC) system which is a kernel (LSM) enhancement to confine programs to a limited set of resources. AppArmor's security model is to bind access control attributes to programs rather than to users. AppArmor confinement is provided via profiles loaded into the kernel. AppArmor profiles can be in one of two modes: enforcement and complain. Profiles loaded in enforcement mode will result in enforcement of the policy defined in the profile as well as reporting policy violation attempts (either via syslog or auditd). Profiles in complain mode will not enforce policy but instead report policy violation attempts."

Traditionally, Unix associates privileges (such as file permissions) to users. On the other hand, AppArmor is meant to control programs.

In Ubuntu, AppArmor is started via a service called "apparmor" automatically, after boot.

Setting Up AppArmor Profile

As an example, suppose that there is a program called "restricted" stored in /usr/local/bin. We can build a AppArmor profile called usr.local.bin.restricted (by replacing / with dot) to specify the permissions granted to this program. Moreover, wildcard can be used in the profile, e.g., /usr/local/bin/*/*/limiteds applies to the program(s) limiteds under all the matched directories.

The profile shall be stored in "/etc/apparmor.d/".

For example,

#include <tunables/global>
 
usr.local.bin.restricted {
   #include <abstractions/base>
}

Reload the AppArmor profiles:

$ sudo service apparmor reload
// Or, restart AppArmor:
$ sudo service apparmor restart

By default, AppArmor writes syslog @ "/var/log/syslog". To show the syslog, with auto-refresh:

$ tail -F /var/log/syslog
    // Press Ctrl-Shift-C to exit

The above profile restricts the program from performing IO operations and many.... If the program perform an IO operation, the system kills the program and write the following error in syslog:

Oct 15 12:18:18 ubuntu kernel: [53604.014028] type=1400 audit(1381810698.963:69):
apparmor="DENIED" operation="open" parent=16309 profile="usr.local.bin.restricted"
name="/usr/local/bin/restricted/c.out" pid=21447 comm="restricted"
requested_mask="wc" denied_mask="wc" fsuid=1000 ouid=1000

Notes on AppArmor Profile:

  • The <tunables/global> contains global variable definitions.
  • The <abstractions/base> includes files that should be readable and writable in all profiles.
  • There are three main types of entries in the profiles:
    • Capabilities entries determines what privileges the program can use.
    • File Permission Access: Path entries determines what files the program can access.
    • Network Access Control.
  • If not granted (as in the above example), the program cannot perform special privileged operations, IO and network operations.

Redirecting AppArmor's Log File

By default, AppArmor writes its logs to /var/log/syslog or /var/log/messages. To re-direct log entries, create the following configuration file for rsyslog called "/etc/rsyslog.d/30-apparmor.conf":

# Log kernel generated apparmor log messages to file
:msg,contains,"apparmor" /usr/local/olas/olas1.2/log/apparmor-log/apparmor.log

# Discard the written messages. Don't apply any additional rules and write again.
& ~

Restart rsyslog by:

$ sudo service rsyslog restart

The AppArmor's user "syslog" (of group "adm") must have "w" permission to the log file.

Java Security Manager

You can run a Java program under the control of Java Security Manager, with the following command-line option:

java -Djava.security.manager -Djava.security.policy==/usr/local/my/my.policy programName

You can configure the permission granted to the program thru a policy file. For example,

/*
 * Security policy file for the executing Java Programs
 */

// All programs
grant {
   permission java.util.PropertyPermission "file.encoding", "read";
   permission java.net.SocketPermission "localhost", "resolve";
   permission java.net.SocketPermission "127.0.0.1:3306", "connect,resolve";

   // entries copy from default system policy
   permission java.util.PropertyPermission "java.version", "read";
   permission java.util.PropertyPermission "java.vendor", "read";
   permission java.util.PropertyPermission "java.vendor.url", "read";
   permission java.util.PropertyPermission "java.class.version", "read";
   permission java.util.PropertyPermission "os.name", "read";
   permission java.util.PropertyPermission "os.version", "read";
   permission java.util.PropertyPermission "os.arch", "read";
   permission java.util.PropertyPermission "file.separator", "read";
   permission java.util.PropertyPermission "path.separator", "read";
   permission java.util.PropertyPermission "line.separator", "read";

   permission java.util.PropertyPermission "java.specification.version", "read";
   permission java.util.PropertyPermission "java.specification.vendor", "read";
   permission java.util.PropertyPermission "java.specification.name", "read";

   permission java.util.PropertyPermission "java.vm.specification.version", "read";
   permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
   permission java.util.PropertyPermission "java.vm.specification.name", "read";
   permission java.util.PropertyPermission "java.vm.version", "read";
   permission java.util.PropertyPermission "java.vm.vendor", "read";
   permission java.util.PropertyPermission "java.vm.name", "read";
};

// Grant only necessary permissions for programs under codeBase
// Tune by running the program without permission and observe the error messages.
grant codeBase "file:/usr/local/my/bin/-" {
   // Execute Runtime.exec()
   permission java.io.FilePermission "<<ALL FILES>>", "execute";
   
   // Uses Java Logging
   permission java.util.logging.LoggingPermission "control";
   permission java.io.FilePermission "/usr/local/my/log/-", "read,write";

   // Needs to write error.log here
   permission java.io.FilePermission "/usr/local/my/error/-", "write";

   // Redirect sysout and syserr
   permission java.lang.RuntimePermission "setIO";

   // Terminate runaway thread
   permission java.lang.RuntimePermission "stopThread";
};