* - PasswordCannotMatchUsername - Password cannot match username to
* - PasswordCannotMatchBlacklist - Username/password combination cannot
* match a specific, hardcoded blacklist.
+ * - PasswordCannotBePopular - Blacklist passwords which are known to be
+ * commonly chosen. Set to integer n to ban the top n passwords.
+ * If you want to ban all common passwords on file, use the
+ * PHP_INT_MAX constant.
* @since 1.26
*/
$wgPasswordPolicy = array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => true,
+ 'PasswordCannotBePopular' => 25,
),
'sysop' => array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => true,
+ 'PasswordCannotBePopular' => 25,
),
'bot' => array(
'MinimalPasswordLength' => 8,
'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
+ 'PasswordCannotBePopular' => 'PasswordPolicyChecks::checkPopularPasswordBlacklist'
),
);
*/
$wgSearchRunSuggestedQuery = true;
+/**
+ * Where popular password file is located.
+ *
+ * Default in core contains 50,000 most popular. This config
+ * allows you to change which file, in case you want to generate
+ * a password file with > 50000 entries in it.
+ *
+ * @see maintenance/createCommonPasswordCdb.php
+ * @since 1.27
+ * @var string path to file
+ */
+$wgPopularPasswordFile = __DIR__ . '/../serialized/commonpasswords.cdb';
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
* @file
*/
+use \Cdb\Reader as CdbReader;
+
/**
* Functions to check passwords against a policy requirement
* @since 1.26
return $status;
}
+ /**
+ * Ensure that password isn't in top X most popular passwords
+ *
+ * @param $policyVal int Cut off to use. Will automatically shrink to the max
+ * supported for error messages if set to more than max number of passwords on file,
+ * so you can use the PHP_INT_MAX constant here safely.
+ * @param $user User
+ * @param $password String
+ * @since 1.27
+ * @return Status
+ */
+ public static function checkPopularPasswordBlacklist( $policyVal, User $user, $password ) {
+ global $wgPopularPasswordFile, $wgSitename;
+ $status = Status::newGood();
+ if ( $policyVal > 0 ) {
+ $langEn = Language::factory( 'en' );
+ $passwordKey = $langEn->lc( trim( $password ) );
+
+ // People often use the name of the current site, which won't be
+ // in the common password file. Also check '' for people who use
+ // just whitespace.
+ $sitename = $langEn->lc( trim( $wgSitename ) );
+ $hardcodedCommonPasswords = array( '', 'wiki', 'mediawiki', $sitename );
+ if ( in_array( $passwordKey, $hardcodedCommonPasswords ) ) {
+ $status->error( 'passwordtoopopular' );
+ return $status;
+ }
+
+ // This could throw an exception, but there's not a good way
+ // of failing gracefully, if say the file is missing, so just
+ // let the exception fall through.
+ // Format of cdb file is mapping password => popularity rank.
+ // See maintenance/createCommonPasswordCdb.php
+ $db = CdbReader::open( $wgPopularPasswordFile );
+
+ $res = $db->get( $passwordKey );
+ if ( $res && (int)$res <= $policyVal ) {
+ // Note: If you want to find the true number of common
+ // passwords stored (for reporting the error), you have to take
+ // the max of the policyVal and $db->get( '_TOTALENTRIES' ).
+ $status->error( 'passwordtoopopular' );
+ }
+ }
+ return $status;
+ }
+
}
"wrongpasswordempty": "Password entered was blank.\nPlease try again.",
"passwordtooshort": "Passwords must be at least {{PLURAL:$1|1 character|$1 characters}}.",
"passwordtoolong": "Passwords cannot be longer than {{PLURAL:$1|1 character|$1 characters}}.",
+ "passwordtoopopular": "Commonly chosen passwords cannot be used. Please choose a more unique password.",
"password-name-match": "Your password must be different from your username.",
"password-login-forbidden": "The use of this username and password has been forbidden.",
"mailmypassword": "Reset password",
"wrongpasswordempty": "Error message displayed when entering a blank password.\n{{Identical|Please try again}}",
"passwordtooshort": "This message is shown in [[Special:Preferences]] and [[Special:CreateAccount]].\n\nParameters:\n* $1 - the minimum number of characters in the password",
"passwordtoolong": "This message is shown in [[Special:Preferences]], [[Special:CreateAccount]], and [[Special:Userlogin]].\n\nParameters:\n* $1 - the maximum number of characters in the password",
+ "passwordtoopopular": "Shown if the user chooses a really popular password.",
"password-name-match": "Used as error message when password validity check failed.",
"password-login-forbidden": "Error message shown when the user has tried to log in using one of the special username/password combinations used for MediaWiki testing. (See [[mwr:75589]], [[mwr:75605]].)",
"mailmypassword": "Used as label for Submit button in [[Special:PasswordReset]].\n{{Identical|Reset password}}",
--- /dev/null
+<?php
+/**
+ * Create serialized/commonpasswords.cdb
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Maintenance script to create common password cdb database.
+ *
+ * Meant to take a file like
+ * https://github.com/danielmiessler/SecLists/blob/master/Passwords/rockyou.txt?raw=true
+ * as input.
+ * @see serialized/commonpasswords.cdb and PasswordPolicyChecks::checkPopularPasswordBlacklist
+ * @since 1.27
+ * @ingroup Maintenance
+ */
+class GenerateCommonPassword extends Maintenance {
+ public function __construct() {
+ global $IP;
+ parent::__construct();
+ $this->mDescription = "Generate CDB file of common passwords";
+ $this->addOption( 'limit', "Max number of passwords to write", false, true, 'l' );
+ $this->addArg( 'inputfile', 'List of passwords (one per line) to use or - for stdin', true );
+ $this->addArg(
+ 'output',
+ "Location to write CDB file to (Try $IP/serialized/commonpasswords.cdb)",
+ true
+ );
+ }
+
+ public function execute() {
+ $limit = (int)$this->getOption( 'limit', PHP_INT_MAX );
+ $langEn = Language::factory( 'en' );
+
+ $infile = $this->getArg( 0 );
+ if ( $infile === '-' ) {
+ $infile = 'php://stdin';
+ }
+ $outfile = $this->getArg( 1 );
+
+ if ( !is_readable( $infile ) && $infile !== 'php://stdin' ) {
+ $this->error( "Cannot open input file $infile for reading", 1 );
+ }
+
+ $file = fopen( $infile, 'r' );
+ if ( $file === false ) {
+ $this->error( "Cannot read input file $infile", 1 );
+ }
+
+ try {
+ $db = \Cdb\Writer::open( $outfile );
+
+ $alreadyWritten = array();
+ $skipped = 0;
+ for ( $i = 0; ( $i - $skipped ) < $limit; $i++ ) {
+ if ( feof( $file ) ) {
+ break;
+ }
+ $rawLine = fgets( $file );
+
+ if ( $rawLine === false ) {
+ $this->error( "Error reading input file" );
+ break;
+ }
+ if ( substr( $rawLine, -1 ) !== "\n" && !feof( $file ) ) {
+ // We're assuming that this just won't happen.
+ $this->error( "fgets did not return whole line at $i??" );
+ }
+ $line = $langEn->lc( trim( $rawLine ) );
+ if ( $line === '' ) {
+ $this->error( "Line number " . ( $i + 1 ) . " is blank?" );
+ $skipped++;
+ continue;
+ }
+ if ( isset( $alreadyWritten[$line] ) ) {
+ $this->output( "Password '$line' already written (line " . ( $i + 1 ) .")\n" );
+ $skipped++;
+ continue;
+ }
+ $alreadyWritten[$line] = true;
+ $db->set( $line, $i + 1 - $skipped );
+ }
+ // All caps, so cannot conflict with potential password
+ $db->set( '_TOTALENTRIES', $i - $skipped );
+ $db->close();
+
+ $this->output( "Successfully wrote " . ( $i - $skipped ) .
+ " (out of $i) passwords to $outfile\n"
+ );
+ } catch ( \Cdb\Exception $e ) {
+ $this->error( "Error writing cdb file: " . $e->getMessage(), 2 );
+ }
+
+ }
+}
+
+$maintClass = "GenerateCommonPassword";
+require_once RUN_MAINTENANCE_IF_MAIN;