Skip to content

Commit 34d05f0

Browse files
authored
Add files via upload
Changelog: v0.2.0 * - Added support for WordPress 6.8+ password hashes with $wp$ prefix * - Implements WordPress 6.8 HMAC-SHA384 + Base64 password verification method * - Added support for standard BCrypt hashes ($2y$, $2a$, $2b$) without prefix * - Maintains backward compatibility with legacy phpass hashes ($P$, $H$) * - WordPress 6.8+ uses base64_encode(hash_hmac('sha384', trim($password), 'wp-sha384', true)) before BCrypt * verification, requiring different handling than standard BCrypt
1 parent 98c5d8a commit 34d05f0

1 file changed

Lines changed: 76 additions & 20 deletions

File tree

wordpressauth/src/Auth/Source/WordpressAuth.php

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22

33
/**
44
* SimpleSAMLphp WordpressAuth
5-
* Version 0.1.0
5+
* Version 0.2.0
66
*
77
* SimpleSAMLphp module to use Wordpress as a SAML 2.0 Identity Provider.
88
*
99
* WordpressAuth is a SimpleSAMLphp authentication module, that allows to use
1010
* the Wordpress user database as the authentication source. The code was written
1111
* for MySQL/MariaDB.
12-
*
13-
*
12+
*
13+
*
1414
* Documentation: https://github.com/disisto/simplesamlphp-wordpressauth
15-
*
15+
*
1616
* Forked from https://github.com/OliverMaerz/WordpressAuth
1717
* forked from https://github.com/Financial-Edge/simplesamlphp-module-wordpressauth/
1818
*
1919
* Licensed under GNU GPL v2.0 (https://github.com/disisto/simplesamlphp-wordpressauth/blob/master/LICENSE)
2020
*
21-
* Copyright (c) 2023 Roberto Di Sisto
21+
* Copyright (c) 2023-2025 Roberto Di Sisto
2222
*
2323
* Permission is hereby granted, free of charge, to any person obtaining a copy
2424
* of this software and associated documentation files (the "Software"), to deal
@@ -29,14 +29,15 @@
2929
*
3030
* The above copyright notice and this permission notice shall be included in all
3131
* copies or substantial portions of the Software.
32-
*
32+
*
3333
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3434
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3535
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3636
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3737
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3838
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3939
* SOFTWARE.
40+
*
4041
**/
4142

4243
namespace SimpleSAML\Module\wordpressauth\Auth\Source;
@@ -76,6 +77,56 @@ public function __construct($info, $config) {
7677
$this->password = $config['password'];
7778
}
7879

80+
/**
81+
* Verify password against WordPress hash
82+
* Supports:
83+
* - WordPress 6.8+ BCrypt with $wp$ prefix (uses HMAC-SHA384 + Base64 before verification)
84+
* - Standard BCrypt hashes: $2y$, $2a$, $2b$
85+
* - Legacy phpass hashes: $P$, $H$
86+
*
87+
* @param string $password The plaintext password
88+
* @param string $stored_hash The hash from database
89+
* @return bool True if password matches
90+
*/
91+
private function verifyPassword(string $password, string $stored_hash): bool {
92+
// WordPress 6.8+ prefixed BCrypt hash
93+
if (substr($stored_hash, 0, 4) === '$wp$') {
94+
Logger::debug('WordpressAuth: Detected $wp$ prefix, using WordPress 6.8+ HMAC-SHA384 method');
95+
96+
// WordPress 6.8 uses HMAC-SHA384 + Base64 encoding before BCrypt verification
97+
$password_to_verify = base64_encode(hash_hmac('sha384', $password, 'wp-sha384', true));
98+
99+
// Strip only $wp but keep the $ prefix (substr 3, not 4)
100+
$hash = substr($stored_hash, 3);
101+
102+
Logger::debug('WordpressAuth: Verifying HMAC-SHA384(password) against BCrypt hash');
103+
return password_verify($password_to_verify, $hash);
104+
}
105+
106+
// Standard BCrypt hashes ($2y$, $2a$, $2b$) without $wp$ prefix
107+
$bcrypt_prefixes = ['$2y$', '$2a$', '$2b$'];
108+
foreach ($bcrypt_prefixes as $prefix) {
109+
if (substr($stored_hash, 0, 4) === $prefix) {
110+
Logger::debug('WordpressAuth: Using password_verify() for standard BCrypt hash');
111+
return password_verify($password, $stored_hash);
112+
}
113+
}
114+
115+
// Legacy phpass hashes ($P$, $H$)
116+
$phpass_prefixes = ['$P$', '$H$'];
117+
foreach ($phpass_prefixes as $prefix) {
118+
if (substr($stored_hash, 0, 3) === $prefix) {
119+
Logger::debug('WordpressAuth: Using PasswordHash for legacy phpass hash');
120+
$hasher = new PasswordHash(8, TRUE);
121+
return $hasher->CheckPassword($password, $stored_hash);
122+
}
123+
}
124+
125+
// Unknown hash format
126+
Logger::warning('WordpressAuth: Unknown password hash format: ' . substr($stored_hash, 0, 10) . '...');
127+
return false;
128+
}
129+
79130
protected function login(string $username, string $password): array {
80131
// Connect to the database
81132
$db = new PDO($this->dsn, $this->username, $this->password);
@@ -135,23 +186,27 @@ protected function login(string $username, string $password): array {
135186
throw new Error('WRONGUSERPASS');
136187
}
137188

138-
$hasher = new PasswordHash(8, TRUE);
139-
140-
// Check the password against the hash in Wordpress wp_users table
141-
if (!$hasher->CheckPassword($password, $row['user_pass'])){
189+
// Verify password using the enhanced method
190+
if (!$this->verifyPassword($password, $row['user_pass'])) {
142191
// Invalid password
192+
Logger::info('WordpressAuth: Invalid password for user: ' . $username);
143193
throw new Error('WRONGUSERPASS');
144194
}
145195

146-
// Define the meta keys in an array
147-
$meta_keys = [
196+
Logger::info('WordpressAuth: Successful login for user: ' . $username);
197+
198+
// Define the meta keys in an array
199+
// Show keys even if values are empty/null
200+
$meta_keys = [
148201
'first_name',
149202
'last_name',
150-
'profile_photo',
151-
$table_prefix.'capabilities'
203+
'profile_photo', // Plugin: Ultimate Member (https://wordpress.org/plugins/ultimate-member/)
204+
'scopes' // Plugin: Extra User Details (https://wordpress.org/plugins/extra-user-details/)
205+
//$table_prefix.'capabilities'
152206
// ... add more keys as needed
153207
];
154208

209+
155210
// Fetch meta_keys from wp_usermeta table defined above
156211
$meta_keys_placeholders = implode("', '", $meta_keys);
157212
$meta_sql = "SELECT meta_key, meta_value FROM ".$table_prefix."usermeta WHERE user_id = :id AND meta_key IN ('$meta_keys_placeholders')";
@@ -171,15 +226,16 @@ protected function login(string $username, string $password): array {
171226
];
172227

173228
foreach ($meta_rows as $meta_row) {
174-
$meta_key = $meta_row['meta_key'];
175-
$meta_value = $meta_row['meta_value'];
229+
$meta_key = $meta_row['meta_key'];
230+
$meta_value = $meta_row['meta_value'];
176231

177-
if (in_array($meta_key, $meta_keys)) {
178-
$attribute_name = str_replace($table_prefix, '', $meta_key);
179-
$attributes[$attribute_name] = [$meta_value];
180-
}
232+
if (in_array($meta_key, $meta_keys)) {
233+
$attribute_name = str_replace($table_prefix, '', $meta_key);
234+
$attributes[$attribute_name] = [$meta_value];
235+
}
181236
}
182237

238+
183239
// Return the attributes
184240
return $attributes;
185241
}

0 commit comments

Comments
 (0)