SoftwareSecurity2013/Group 6/Requirements/V2
Inhoud
Authentication Requirements (V2)
V2.1
Description
Verify that all pages and resources require authentication except those specifically intended to be public.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | 5 |
Owner | Loren |
Start Date | 09-06-2013 |
End Date | 15-06-2013 |
Current Status | Checked |
Outcome | PASS |
Evaluation Procedure
Look at every PHP file that is directly accessible, then evaluate them based on whether they grant any access to the system. The output of the process is documented here.
Evaluation Comments
We base the evaluation on the intent of the developers to authenticate specific actions and to allow anyone to perform others by default.
The application views unauthenticated users as an entity defined by the IP address from whence they came. Spoofing the IP address "authentication" would require a well-placed man-in-the-middle attack or TCP spoofing, so it should be effective in most cases. The IP address is then used for authorization purposes, so that when some IP address is spamming or performing other undesirable actions: those actions can be undone en mass and the offending address blocked.
Password authentication is used in other cases where IP address is not enough. (Erik: Note that in such cases it can make sense to still use the IP address as an additional authentication credential - to prevent someone from say stealling a cookie and using the same cookie from a different IP address. )
Irrespective of whether IP addresses can be considered authentication: the data accessible without authentication is certainly considered by the developers to be public by default, which could be considered a specific intention. Making everything on the system secret by default would defeat the purpose of the program, so it is only sensible that we interpret this requirement as we have: authentication use is aligned with the intent of the program.
There are various permissions. If a page asserts the need for that permission and it is not available then a user will be prompted to log in. If the logged-in user does not have the permission, a permission denied page will result in the normal case. On special pages, the code for the special page decides how to proceed.
V2.2
Description
Verify that all password fields do not echo the user’s password when it is entered, and that password fields (or the forms that contain them) have autocomplete disabled.
Evaluation Statistics
Sampling Method | none |
Difficulty Rating | |
Owner | Vincent, Roland, Loren |
Start Date | 26-05-2013 |
End Date | 04-06-2013 |
in progress | 100% |
Outcome | FAIL |
Evaluation Procedure
Check code pages with password input fields
Files
- includes/specials/ Special:UserLogin.php
- includes/templates/userlogin.php
Discussion
Mediawiki includes a template for user authentication that ensures that there is consistent presentation of username / password pages across the entire application, or at least that there can be a consistent use across the application.
The following code, found in include/templates/Userlogin.php is the piece of the template that involves passwords.
include/templates/Userlogin.php <source lang="php">
<td class="mw-label"><label for='wpPassword1'><?php $this->msg('yourpassword') ?></label></td> <td class="mw-input"> <?php echo Html::input( 'wpPassword', null, 'password', array( 'class' => 'loginPassword', 'id' => 'wpPassword1', 'tabindex' => '2', 'size' => '20' ) + ( $this->data['name'] ? array( 'autofocus' ) : array() ) ); ?>
</source>
As can be seen on the first line of the code snippet, it utilizes "Html::input", which is defined in includes/Html.php, which includes all the code for the Html helper class used throughout the application. The relevant parameters to Html::input are the first three, which are (field name, default value, and "type"). The type of password maps to the the input type in html of the same name, which instructs the browser not to display what is typed in the field.
includes/Html.php: <source lang="php">
public static function input( $name, $value = , $type = 'text', $attribs = array() ) { $attribs['type'] = $type; $attribs['value'] = $value; $attribs['name'] = $name;
return self::element( 'input', $attribs ); }
</source>
The HTML 5 autocomplete attribute is not set to "off" in the code. The HTML 5 draft specification[1] section 4.10.19.8[2] indicates that "User agents must all support the Default input mode state, which corresponds to the user agent's default input modality." Given the specification, in order to meet the requirement of having autocomplete disabled, the attribute must be set explicitly and it has not been.
Evaluation Comments
The code above is always used when a password needs to be entered. (at account creation and when you login)
V2.3
Description
Verify that if a maximum number of authentication attempts is exceeded, the account is locked for a period of time long enough to deter brute force attacks.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | 2 |
Owner | Roland |
Start Date | 07-06-2013 |
End Date | 07-06-2013 |
Current Status | Complete |
Outcome | Pass |
Evaluation Procedure
Files
includes/specials/ Special:UserLogin.php includes/DefaultSettings.php
Discussion
Login attemps are limited per username and IP. In the code below it can be seen that the ammount of attemps a user has is based on $wgPasswordAttemptThrottle .
<source lang="php">
/**
* Increment the login attempt throttle hit count for the (username,current IP) * tuple unless the throttle was already reached. * @param $username string The user name * @return Bool|Integer The integer hit count or True if it is already at the limit */
public static function incLoginThrottle( $username ) {
global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest; $username = trim( $username ); // sanity
$throttleCount = 0; if ( is_array( $wgPasswordAttemptThrottle ) ) { $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); $count = $wgPasswordAttemptThrottle['count']; $period = $wgPasswordAttemptThrottle['seconds'];
$throttleCount = $wgMemc->get( $throttleKey ); if ( !$throttleCount ) { $wgMemc->add( $throttleKey, 1, $period ); // start counter } elseif ( $throttleCount < $count ) { $wgMemc->incr( $throttleKey ); } elseif ( $throttleCount >= $count ) { return true; } }
return $throttleCount;
} </source>
according to this webpage: http://www.mediawiki.org/wiki/Manual:$wgPasswordAttemptThrottle
the default value of $wgPasswordAttemptThrottle is as followss: array( 'count' => 5, 'seconds' => 300 ) So you have 5 login attemps, and the time you have until you can try again is 300 seconds. When i looked at the code in the DefaultSettings.php file this is conmfiremd: <source lang="php"> $wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 ); </source>
But this site also stated that in order for this to work $wgMainCacheType must be set to something other than CACHE_NONE .
But according to http://www.mediawiki.org/wiki/Manual:$wgMainCacheType $wgMainCacheType is CACHE_NONE by default
Therefore the security Fails, whether the 300 seconds are long enough or not.
It is also not possible to try to change your password if your accound is "Throttled"
<source lang="php">
$throttleCount = LoginForm::incLoginThrottle( $this->mUserName );
if ( $throttleCount === true ) {
throw new PasswordError( $this->msg( 'login-throttled' )->text() );
}
</source>
Evaluation Comments
V2.4
Description
Verify that all authentication controls are enforced on the server side.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | |
Owner | Vincent, Michail |
Start Date | 07-06-2013 |
End Date | 12-06-2013 |
Current Status | 100% |
Outcome | Pass |
Evaluation Procedure
To verify that all authentication controls are enforced on the server side we looked at which authentication methodes are used. The main area's we checked where :
- Normal login
- Cookie login
- Login on creation
- Group login
- Login through plugin
The code of User.php :
Normal login goes through the following function which checks the password serverside. <source lang="php"> /** * Given unvalidated password input, return error message on failure. * * @param $password String Desired password * @return mixed: true on success, string or array of error message on failure */ public function getPasswordValidity( $password ) { .... } </source>
cookie login goes through the following function which checks the password serverside. <source lang="php"> /** * Load user data from the session or login cookie. If there are no valid * credentials, initialises the user as an anonymous user. * @return Bool True if the user is logged in, false otherwise. */ private function loadFromSession() { </source>
login on creation goes through the following function which checks serverside whether a user may create a account. <source lang="php"> /** * Get whether the user is explicitly blocked from account creation. * @return Bool|Block */ public function isBlockedFromCreateAccount() { ... } </source>
Group login goes through the following function which checks group credentials <source lang="php"> /** * Load the groups from the database if they aren't already loaded. */ private function loadGroups() { </source>
login through plugin goes through the following function which checks the password serverside. <source lang="php">
- Reject various classes of invalid names
global $wgAuth; $name = $wgAuth->getCanonicalName( $t->getText() );
switch ( $validate ) { case false: break; case 'valid': if ( !User::isValidUserName( $name ) ) { $name = false; } break; case 'usable': if ( !User::isUsableName( $name ) ) { $name = false; } break; case 'creatable': if ( !User::isCreatableName( $name ) ) { $name = false; } break; default: throw new MWException( 'Invalid parameter value for $validate in ' . __METHOD__ ); } return $name; } </source>
And from AuthPlugin (http://www.mediawiki.org/wiki/AuthPlugin) we have that: Creating new authentication plugins[edit]
If you need to write your own plugin, see the source doc at MediaWiki Source Documentation (see also the latest source code) Instantiate a subclass of AuthPlugin and set $wgAuth to it to authenticate against some external source. The default behavior is not to do anything, and use the local user database for all authentication. A subclass can require that all accounts authenticate externally, or use it only as a fallback; also you can transparently create internal wiki accounts the first time someone logs in who can be authenticated externally.
Evaluation Comments
V2.5
Description
Verify that all authentication controls (including libraries that call external authentication services) have a centralized implementation.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | |
Owner | Vincent, Michail, Loren |
Start Date | 12-06-2013 |
End Date | 12-06-2013 |
In progress | |
Outcome | Mostly Convinced |
Evaluation Procedure
Mediawiki has an authentication plugin system and all authentication, whether it be authentication from Mediawiki's database or something external like LDAP, there is still a cetral choke point through which the authentication process passes.
Evaluation Comments
V2.6
Description
Verify that all authentication controls fail securely.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | 3 |
Owner | Michail, Vincent |
Start Date | 12-06-2013 |
End Date | 12-06-2013 |
Current Status | 100% |
Outcome | Fail |
Evaluation Comments
To authenticate control fail securely two things are important to check: a. Use generic error messages. b. Use a global exception handler.
After testing the log in page, if the user give wrong credentials , it returns the message that the credentials are wrong. Note: If the user gives only wrong password, the returning message reveals that the username is correct but the password is not.
<source lang="php"> /** * Is the input a valid username? * * Checks if the input is a valid username, we don't want an empty string, * an IP address, anything that containins slashes (would mess up subpages), * is longer than the maximum allowed username size or doesn't begin with * a capital letter. * * @param $name String to match * @return Bool */ public static function isValidUserName( $name ) { ..... ...
/** * Usernames which fail to pass this function will be blocked * from user login and new account registrations, but may be used * internally by batch processes. * * If an account already exists in this form, login will be blocked * by a failure to pass this function. * * @param $name String to match * @return Bool */ public static function isUsableName( $name ) { .... ..... /** * Is the input a valid password for this user? * * @param $password String Desired password * @return Bool */ public function isValidPassword( $password ) { .... ..... /** * Given unvalidated password input, return error message on failure. * * @param $password String Desired password * @return mixed: true on success, string or array of error message on failure */ public function getPasswordValidity( $password ) { .....
</source>
V2.7
Description
Verify that the strength of any authentication credentials are sufficient to withstand attacks that are typical of the threats in the deployed environment.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | 1 |
Owner | Loren |
Start Date | 12-06-2013 |
End Date | 12-06-2013 |
Current Status | |
Outcome | UNDECIDABLE |
Evaluation Procedure
Looked at the authentication code in the User class.
Evaluation Comments
User-Selected Passwords
Most likely, this question is intended to ask whether "eye of newt restrictions" (so many numbers, so many capitals, specials, etc.) are present in the application and whether the password entered by user must appear on its surface to be string of a prescribed length, selected uniformly at random from a prescribed alphabet. It is well known that almost nothing a user does is random and thus any set of authentication credentials will be very far from uniformly distributed. Given that we have no control over how users choose "complex" passwords like "Blink-182!", apparently quite popular since eye-of-newt rules came into fashion in recent years (it is simultaneously very easy to guess and meets the requirements.) Given this information, the only correct response to this requirement in the context of user-selected passwords is to say that there is no way to know the entropy of a user-selected password by merely looking at it -- thus, password strength is undecidable in any rigorous sense. (Erik: I agree, but still, some minimum length requirements, or these progress bars that some websites show to indicate password strenght as you type it, can help to improve password strength.)
(loren: minimum length requirement is fine -- password strength meters deliver nothing more than the message that "if you put Aa1! on the end of your password, it's awesome"
Decidable Scenarios
The strength of strong authentication systems or mechanically chosen passwords like the following can be determined:
- Passwords are securely generated by the web application (users are not allowed to choose)
- Client certificate based authentication (PKCS #11 keyholder or software)
- Event- / Time-Synchronous tokens (RSA SecurID, Safeword, Alladin, etc.)
- Challenge-Response tokens (Rabobank random reader in signature mode)
Each of these examples can be evaluated because the credentials are generated in a consistent way with reasonably unbiased PRNG's.
V2.8
Description
Verify that all account management functions are at least as resistant to attack as the primary authentication mechanism.
Evaluation Statistics
Sampling Method | none |
Difficulty Rating | 2 |
Owner | Remy |
Start Date | 16-06-2013 |
End Date | 17-06-2013 |
Current Status | 100% |
Outcome | FAIL |
Evaluation Procedure
Discussion
As stated in v2.10 a user needs to be logged in to perform certain account management functionality. As the login mechanism IS the primary authentication mechanism, we can state that this item passes. THOUGH, changing the user's password is also considered an account management functionality. In v2.9 we can see that this fails. So therefore, this verification fails.
Evaluation Comments
V2.9
Description
Verify that users can safely change their credentials using a mechanism that is at least as resistant to attack as the primary authentication mechanism.
Evaluation Statistics
Sampling Method | none |
Difficulty Rating | 2 |
Owner | Remy |
Start Date | 12-06-2013 |
End Date | 12-06-2013 |
Current Status | 100% |
Outcome | FAIL |
Evaluation Procedure
Files
includes/specials/SpecialChangePassword.php
Discussion
- When implementing password reset or change features, such as through the use of secret questions, hash the answers.
Like the password, the new password on reset is hashed. Both the old and new passwords are usable to login, though.
- Lockout an account if the there are multiple failed attempts to change/reset a password.
Unlike logging in, there is NO throttle on password change attempts. Though, you have to be logged in to change your password. So we can rightfully state now, that users can NOT safely change their credentials using a mechanism that is at least as resistant to attack as the primary authentication mechanism. (Erik: Interesting point. I can see that strictly speaking you are right here. Though i can imagine that some people would consider it overkill to throttle password change attempts. )
<source lang="html"> <form method="post" action="index.php/Special:ChangePassword" id="mw-resetpass-form"> <input type="hidden" value="ccd28baa013d4f8c37a675aa5e38efd4" name="token" /> <input type="hidden" value="Username" name="wpName" /> <input type="hidden" name="wpDomain" /> <input type="hidden" name="returnto" />
</source>
Changing the Username value in the code when submitting the form enables you to (try to) change anyone's password, which in this case allows you to brute-force anyone's password.
Evaluation Comments
V2.10
Description
Verify that re-authentication is required before any application-specific sensitive operations are permitted.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | 2 |
Owner | Remy |
Start Date | 12-06-2013 |
End Date | 12-06-2013 |
Current Status | In Progress |
Outcome | FAIL |
Evaluation Procedure
There are a lot of special pages for administrators. One page being more interesting than others, but ANY action except changing the logged in user's email address or password is allowed without re-authenticating with a user password.
Exemplary actions that do not need re-authentication with a password are:
- Blocking and deblocking users, IP ranges and groups of users
- Adding users as administrator (if the logged in user is an administrator, of course)
- Setting user group rights
- Editing user-uploaded files
Evaluation Comments
V2.11
Description
Verify that after an administratively-configurable period of time, authentication credentials expire.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | (1 - 10) |
Owner | Vincent |
Start Date | (12-06-2013) |
End Date | (12-06-2013) |
Current Status | 100% |
Outcome | FAIL |
Evaluation Procedure
To verify that after an (administratively-configurable) period of time authentication credentials expire we found the following thing:
- Mediawiki enables the uses of temperal newpasswords In the class SpecialUserlogin this is hardcoded in the function mailPasswordInternal. The is time is set to 24 hours ( 86400 seconds) for $wgNewPasswordExpiry
Also explained in : http://www.mediawiki.org/wiki/Manual:Configuration_settings#Access
- Cookies expire on default after 180 days if checked by user.
Explination : http://www.mediawiki.org/wiki/Manual:$wgCookieExpiration
- Mediawiki enables ipblocks for a certain time
We did not find a time limit for "normal" authentication credentials to expire. If your password is excepted it
can remain valid for an unlimited amount of time. We also conclude this because there is no label in the SQL user data that stores
the information about when new authentication credentials were initiated besides the creation of the entire account.
thus mediawiki does not meet this security verification. Another problem than can occur if this was implemented is that the cookie expire time should be in a time range smaller than the authentication credentials expire time. Or cookies validation should be excluded for cookies corresponding to these passwords. Unsure whether that happens at this point but we expect not.
Evaluation Comments
V2.12
Description
Verify that all authentication decisions are logged.
Evaluation Statistics
Sampling Method | None |
Difficulty Rating | 1 |
Owner | Loren Weith |
Start Date | 07-06-2013 |
End Date | 07-06-2013 |
Current Status | Complete |
Outcome | FAIL |
Evaluation Procedure
Review of documentation as well as actual logs.
Evaluation Comments
Mediawiki documentation says that the no authentications are logged by default. To do so, requires a special module be added to the system.
V2.13
Description
Verify that account passwords are salted using a salt that is unique to that account (e.g., internal user ID, account creation) and hashed before storing.
Evaluation Statistics
Sampling Method | (none, percentage, etc.) |
Difficulty Rating | (4) |
Owner | Roland |
Start Date | (12-6-2013) |
End Date | (12-6-2013) |
Current Status | Complete |
Outcome | (FAIL) |
Evaluation Procedure
Evaluation Comments
A password is created with a salt as can be seen in the code below, the salt is passed on to this function as empty,false or a unique user ID. Only when the salt is passed on as a user ID the requirement is met. This is because therequirement specifically says "The Salt has to be unique to that account (e.g., internal user ID, account creation)".
<source lang="php">
public static function crypt( $password, $salt = false ) {
global $wgPasswordSalt;
$hash = ; if( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) { return $hash; }
if( $wgPasswordSalt ) { if ( $salt === false ) { $salt = MWCryptRand::generateHex( 8 ); } return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) ); } else { return ':A:' . md5( $password ); } }
</source>
By using debug_print_backtrace() function in php we were able to trace back exactly how crypt is used when a password is created.
In the code below you can see it only passes on the password string and not any unique ID. Therefore strictly taken the requirement is not met.
<source lang="php">
public function setInternalPassword( $str ) { $this->load(); $this->setToken();
if( $str === null ) { // Save an invalid hash... $this->mPassword = ; } else { $this->mPassword = self::crypt( $str ); } $this->mNewpassword = ; $this->mNewpassTime = null; }
</source>
However, when a unique id is not passed on the crypt function adds a randomly generated password (Erik:i guess you mean slat, not password) in the crypt function: $salt = MWCryptRand::generateHex( 8 ); (32 bits random string) So that is 4.29 billion possible salts which given that OpenSSL is used for the generation, should be uniformly distributed. This would in combination with a user ID form a stronger hash. (Erik: Note that it is not that important how many possible salts there are, as long as it is unique per account. After all, the attacker model against which salts is supposed to help is an attacker who gets holds of the encrypted password file; such an attacker then also knows the salts of each account. So as long as all the salts are different for all accounts, and the number of salts is large enough to make so it is infeasible for an attacker to construct a set of rainbow tables for all possible salt values , the attacker will be forced to make a rainbow table for an individual account.
(Loren: of course, it doesn't matter whether the salt is known or ahead of time or not. Ultimately, as you said, any attacker that matters will be guaranteed to know every salt. Having a large random number just reduces the probability of collision and makes TMTO's impractical. TMTO's are just very very slightly less practical if we ignore the OWASP suggestion and just pick a random number (or do both.) In the end, OWASP eliminates instantaneous collisions across all users and guarantees collisions on a single user over time -- a subtle, but very real degradation in overall security. Appending user-id to the random string eliminates all collisions within a single system and makes collisions across multiple implementations a bit less likely than if we did not do so.)
V2.14
Description
Verify that all authentication credentials for accessing services external to the application are encrypted and stored in a protected location (not in source code).
Evaluation Statistics
Sampling Method | none |
Difficulty Rating | (2) |
Owner | Roland |
Start Date | (12-06-2013) |
End Date | (12-06-2013) |
Current Status | complete |
Outcome | FAIL |
Evaluation Procedure
Evaluation Comments
Passwords are stored in a configuration file named LocalSettings.php, they are not encrypted:
<source lang ="php">
- Database settings
$wgDBtype = "mysql"; $wgDBserver = "localhost"; $wgDBname = "my_wiki2"; $wgDBuser = "root"; $wgDBpassword = "";
</source>
Encrypting them is not that useful because you would have to store the key somewhere, probably on the server too.