-
CodeWatch Update – 12/4/2013
This weekend I made the first updates to CodeWatch since releasing it as a free service and am just now getting around to posting a notification. The updates included:
- Minor bugfixes throughout the system.
- Upgrades to the Brakeman scanner for Ruby on Rails vulnerability testing. We were using an outdated version but are now (at the time of this posting) using the latest and greatest .
- I added a plugin that leverages Sigcheck to identify viruses in executable code uploaded to the application. More information can be found here.
- I added a plugin that uses Retire.js to scan for known vulnerable JavaScript files included in submitted source.
As a general note, I recommend using Retire.js and DependencyCheck (which was already integrated into CodeWatch prior to this weekend) on web application penetration tests that include source containing JavaScript files or Java jar files. These tools help provide some coverage for OWASP 2013 A9: Using Known Vulnerable Components.
A link discussing Retire.js can be found here. To leverage it in a web application penetration test, you will need to:
- Install Node.js from here.
- Install the retire.js plugin with: `npm install -g retire`
- Use a tool like httrack to download the JavaScript and other files from the site. Make sure the configuration settings for the httrack download includes +*.js in the “Scan Rules” tab.
- Scan for known vulnerable JavaScript components:
`retire –jspath /path/to/httrack/website/download/folder`
To use DependencyCheck, use a similar process (minus Node.js) by including +*.jar files in the “Scan Rules” tab of httrack. Then run DependencyCheck against any downloaded jar files:
`dependency-check.bat –a “AppName” –f HTML –s /path/to/directory/containing/the/jar`
Another thing I like to do is use the Firefox Wappalyzer plugin to identify all the third party components used on a site. Then I load up Burp, spider the site, then right click on the top level site and select “Engagement tools->Search” in the “Targets->Site Map” tab. For each third party component identified by Wappalyzer, I search through the spidered results for version information and then attempt to correlate to any known vulnerabilities.
Hopefully the updates and the information are useful to someone out there!
-
OWASP A1 – Injection
Finally, we reach a more glamorous class of vulnerability. Injection attacks can take many forms; SQL injection, XPath/XML/SOAP injection, LDAP injection, and Command injection are just a few types. This post will cover some PHP countermeasures to the more common forms of injection, starting with SQL injection:
SQL Injection Mitigations:
The first step in preventing SQL injections is to perform input validation on all user supplied data. Review our coverage of input validation in the OWASP A2 – Cross-Site Scripting (XSS) series here, here, and here.
The next step is to take advantage of PHP’s support for prepared statements, also known as parameterized queries. Prepared statements utilize the database driver to pass user supplied data as a parameter in a query. This causes the database to interpret the data as a single value for a particular portion of the query. The database will interpret the parameter as data, even if SQL commands were input as part of the data, instead of interpreting the data as SQL statements.
Prepared statements, when used properly, prevent known forms of SQL injection attacks. Here is an example of a prepared statement with PHP:
// This example makes the assumption that the // database handle ($dbh) has already been setup. // It also assumes that input validation has already // been performed and the three values that were // input for the query were assigned to $param1, // $param2, and $param3. $qry = $dbh->prepare("SELECT * FROM customers WHERE User_Name = ? AND User_Pass = ? AND User_ID = ?"); $qry->bindValue(1, $param1, PDO::PARAM_STR); $qry->bindValue(2, $param2, PDO::PARAM_STR); $qry->bindValue(3, $param3, PDO::PARAM_INT); $qry->execute();
In the above example, we are running a SELECT statement against the database with three parameters. The next three lines bind user supplied data to the three parameters and the final statement executes the query. The question marks are placeholders for the user supplied data. They are referenced in order of appearance, so the “1” value in the first bindValue statement binds $param1 to the User_Name question mark placeholder.We have taken this a step further and explicitly defined the data type for added security. The bindValue statements set the data type with the third value, PDO::PARAM_STR and PDO::PARAM_INT. A list of the different data types that can be set can be found here. Where possible, it is always best to validate the input data, the input data type, and the input length/size for appropriate values.
There is also a WRONG WAY to use prepared statements. It is fairly common to see the following (THIS IS AN INVALID WAY OF USING PREPARED STATEMENTS):
$qry = $dbh->prepare("SELECT * FROM customers WHERE User_Name = $param1 AND User_Pass = $param2 AND User_ID = $param3");
The above example concatenates user supplied data in with the query string. This is just as vulnerable as running a regular query. This example does not bind user supplied data to a query parameter. DO NOT DO THIS.Prepared statements also have the benefit of improving database query performance. Running multiple queries with varying parameters without using prepared statements causes the database to analyze, compile, and optimize each query. Prepared statements only need to be analyzed, compiled, and optimized once.
LDAP Injection Mitigations:
Unfortunately, the PHP drivers that support LDAP do not have prepared statement/parameterized query support. This means we must rely on input validation for effective injection mitigation. Whitelist validation is always the preferred method, but sometimes a blacklist is the only approach for a given application.
The following characters should be filtered out of user supplied data passed to an LDAP query:
- &
- |
- =
- *
- (
- )
- ;
- !
- <
- >
- ~
- ‘
- “
- ,
If using a whitelist approach, make sure these characters are omitted from the acceptable characters filter. If using a blacklist approach, make sure these characters are included in the denied characters filter.
SOAP/XML/XPath Injection Mitigations:
As with LDAP, the PHP SOAP/XML/XPath drivers do not have prepared statement/parameterized query support. This means we must rely on input validation and encoding for effective injection mitigation. Whitelist validation is always the preferred method, but sometimes a blacklist is the only approach for a given application.
The following characters should be filtered out of user supplied data passed to a SOAP/XML/XPath query:
- ‘
- “
- <
- >
- /
- [
- ]
- !
- @
- =
- (
- )
- *
- :
- *
If using a whitelist approach, make sure these characters are omitted from the acceptable characters filter. If using a blacklist approach, make sure these characters are included in the denied characters filter.
In addition, encode user supplied data in case there is a special character that you have missed in one of your filter lists. This can be performed by forcing the data type (found in our ASVS 5.4 solution here) and then using a PHP function to encode the data (found in examples here).
Some example code as a reminder:
// Encode the data. Encode both single and double // quote characters, use UTF-8 as the character set, // and do not double encode ("false" parameter). function outputEncode($data) { $encodedData = htmlentities($data, ENT_QUOTES, "UTF-8", false); return $encodedData; } // Convert the data to the UTF-8 character set and // silently drop any characters that can't be converted. // Pass the value to the output encoder above. function utfEncode($data) { $utfEncoded = iconv("UTF-8", "UTF-8//IGNORE", (string)$data); return outputEncode($utfEncoded); } // Take a POST'ed parameter and encode it $uservalue = utfEncode($_POST['param1']);These two steps should to a long way towards preventing SOAP/XML/XPath related injection attacks.
Command Injection
The best way to avoid command injection is to not pass user supplied data to a function that calls an operating system level command. If users must be able to run commands through PHP, then I suggest indirectly mapping specific commands and their parameters to values that the user can select. We covered indirect object mapping here. If this is not an option, split the request into two sections; the command to be run and the user data to be supplied to it. Create a whitelist to deny requests to commands that aren’t on the approved list. Create a blacklist to filter out the following characters at a minimum:
- |
- &
- ‘
- “
- `
- !
- *
- ;
If possible, utilize a whitelist that limits input data to the commonly used characters for command line executable parameters:
- –
- a-zA-Z0-9
- =
- /
- .
That wraps up our post on PHP injection mitigations. We are down to one final OWASP TOP 10 issue, A8 – Failure to Restrict URL Access. See you next time.
-
OWASP A7 – Insecure Cryptographic Storage
This week we will provide some examples for encrypting data with PHP. Data encryption is necessary to protect sensitive information such as passwords, credit card numbers, medical information, etc. PHP provides some easy to use functions to encrypt data so that it can be stored securely.
There are two common but different scenarios for storing sensitive data in an unreadable and protected state; encrypted using a symmetric or asymmetric algorithm or hashed using a hash function. Credit card numbers provide a typical scenario for data encryption because the numbers might later need to be decrypted and used in future transactions. Passwords or answers to security questions provide a typical scenario for data hashing. A hash function is one way and cannot be reversed unless there are vulnerabilities (collisions) in the algorithm. A password only needs to be validated against what a user has supplied to confirm authentication, it does not usually need to be “decrypted” or “reversed”.
Now that we have provided a very high-level and broad overview of the types and reasons for encrypting data, let’s begin identifying the requirements and solutions using PHP…
ASVS 7.1 Requirement:
Verify that all cryptographic functions used to protect secrets from the application user are implemented server side.
ASVS 7.1 Solution:
Never implement encryption routines on the client side (JavaScript, etc). They can easily be reviewed and attacked or simply bypassed. Implement all encryption, decryption, and hashing functionality within your server side PHP code. In addition, do not store any sensitive information (passwords, hashes, or other credentials) on the client side (using mechanisms such as cookies, HTML5 storage, etc).
ASVS 7.2 Requirement:
Verify that all cryptographic modules fail securely.
ASVS 7.2 Solution:
Encryption should fail closed so that data does not get stored in an unprotected state. Implement functionality within your applications to ensure that an error is generated when the encryption/decryption/hashing function fails and do not store/transmit the data (stop processing of the data).
ASVS 7.3 Requirement:
Verify that access to any master secret(s) is protected from unauthorized access (A master secret is an application credential stored as plaintext on disk that is used to protect access to security configuration information).
ASVS 7.3 Solution:
The data is only as secure as the secret, key, passcode, passphrase, password, etc used to encrypt it. Store this value in as few places as possible and provide access to a minimum number of people. Ideally, the value should only be stored in an encrypted backup and on the server on which the application resides.
Protect the value on the application server by storing it in a file with limited permissions. Only the Apache user and group should be provided with access to this file and the access should be limited to read only. Access and changes to this file should be monitored.
The secret/key/passcode/passphrase/password/etc should be rotated on at least an annual basis. This can be a full rotation where existing data is decrypted with the old value and then encrypted with the new one or partial where only new data is encrypted with the new value.
ASVS 7.4 Requirement:
Verify that password hashes are salted when they are created.
ASVS 7.4 Solution:
We have already covered hashes in previous posts. Here we discussed hashing passwords using a salted value to protect against rainbow tables and other attacks. The code for reference:
// Create the salt. First generate the size, then create the salt // and encode it $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); $iv = base64_encode(mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)); // Now we hash the password with Blowfish, salted with the IV created // above, using a work factor of 12: $user_pass = base64_encode(crypt($_POST['user_pass'], "$2y$12$" . $iv));
ASVS 7.5 Requirement:
Verify that cryptographic module failures are logged.
ASVS 7.5 Solution:
Implement logic within the application such that errors are caught and logged when encryption algorithms or hash functions are used. These logs should be reviewed on a regular basis.
ASVS 7.6 Requirement:
Verify that all random numbers, random file names, random GUIDs, and random strings are generated using the cryptographic module’s approved random number generator when these random values are intended to be unguessable by an attacker.
ASVS 7.6 Solution:
Many algorithms and protection mechanisms have been broken due to the fact that they were relying on a pseudo random number generator that could be predicted with enough data. PHP provides the openssl_random_pseudo_bytes random number generator. The code for assigning a random value to a variable is simple:
$random = openssl_random_pseudo_bytes();
A length can be passed to the function as well to generate a string of random bytes with the number of bytes determined by this length parameter.ASVS 7.7 Requirement:
Verify that cryptographic modules used by the application have been validated against FIPS 140-2 or an equivalent standard. (See http://csrc.nist.gov/groups/STM/cmvp/validation.html).
ASVS 7.7 Solution:
The mcrypt functionality within PHP can be used on Linux, Unix, and Windows based systems. This set of functions supports the AES algorithm and SHA functions. AES is a FIPS 140-2 compliant algorithm and SHA-1 or greater functions are compliant secure hash standards. Use AES for encryption and SHA-1 or greater for hashing and you will be FIPS 140-2 compliant (at least for now).
ASVS 7.8 Requirement:
Verify that cryptographic modules operate in their approved mode according to their published security policies (See http://csrc.nist.gov/groups/STM/cmvp/validation.html).
ASVS 7.8 Solution:
I don’t believe that the application developer has much of a choice here when using PHP. The advice here would be to use one of the FIPS 140-2 algorithms, with the correct size (128/192/256 bit, etc), correct mode (CBC is suggested as it is commonly supported, used, and more secure than some other methods. See more here), and strong key/secret.
I am going to skip 7.9 as it is policy related and could vary depending upon the application, data to be protected, and company protecting the data.
ASVS 7.10 Requirement:
Verify that all code supporting or using a cryptographic module is not affected by any malicious code.
ASVS 7.10 Solution:
Scan your code for viruses, or any malicious backdoors. Perform manual and automated code reviews.
And now for some code
The mcrypt_encrypt and mcrypt_decrypt functions can be used to encrypt and decrypt data respectively and the mcrypt_get_iv_size and mcrypt_create_iv functions can be used to generate an Intialization Vector (IV) to be used by the encryption/decryption functions.
The mcrypt_get_iv_size requires two variables; the cipher in use and the mode. The mcrypt_create_iv function also requires two variables; the IV size (obtained with mcrypt_get_iv_size) and the random source. Source to create the IV:
// Get the IV size for AES 128 using the CBC mode and store it in // a variable: $ivsize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); // Create the IV from the IV size obtained before and store it in // a variable. Use the MCRYPT_RAND cross-platform source for // randomness: $iv = mcrypt_create_iv($ivsize, MCRYPT_RAND);
The mcrypt_encrypt and mcrypt_decrypt functions both take five variables; cipher for encryption, key/secret used to encrypt the data, data to be encrypted, mode of encryption, and IV. Source to encrypt data:// Read the key file into a variable $key = file_get_contents('/etc/keys/webapp.key'); // Unencrypted data to protect $plaintext = "asfadsfadsfadsfa"; // Encrypt the data using the AES-128 algorithm, // the key above, the CBC mode, and the IV // generated earlier: $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $iv);
Code to decrypt data:// Read the key file into a variable $key = file_get_contents('/etc/keys/webapp.key'); // Encrypted data to decrypt $ciphertext = "ohafosih9fh29/'09j0"; // Decrypt the data using the AES-128 algorithm, // the key above, the CBC mode, and the IV // generated earlier: $plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext, MCRYPT_MODE_CBC, $iv);
When storing encrypted data and IV’s, it is sometimes best to encode the data after encryption. Some systems don’t handle processing or storage of encrypted data well. Encoding the encrypted data solves this problem. Change the encryption code to the following to encode the data:$ciphertext = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $iv));
Change the above decryption code to the following:// This example assumes that the data and IV were both // retrieved as encoded values $plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($ciphertext), MCRYPT_MODE_CBC, base64_decode($iv));
That should just about do it. The above examples should get you on the way towards protecting sensitive data within your applications and data stores.