wfCredentialsController.php
5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<?php
class wfCredentialsController {
const UNCACHED = 'uncached';
const NOT_LEAKED = 'not-leaked';
const LEAKED = 'leaked';
const ALLOW_LEGACY_2FA_OPTION = 'allowLegacy2FA';
const DISABLE_LEGACY_2FA_OPTION = 'disableLegacy2FA';
public static function allowLegacy2FA() {
return wfConfig::get(self::ALLOW_LEGACY_2FA_OPTION, false);
}
public static function useLegacy2FA() {
if (!self::allowLegacy2FA()) {
return false;
}
return !wfConfig::get(self::DISABLE_LEGACY_2FA_OPTION, false);
}
public static function hasOld2FARecords() {
$twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
if (is_array($twoFactorUsers) && !empty($twoFactorUsers)) {
foreach ($twoFactorUsers as &$t) {
if ($t[3] == 'activated') {
$user = new WP_User($t[0]);
if ($user instanceof WP_User && $user->exists()) {
return true;
}
}
}
}
return false;
}
public static function hasNew2FARecords() {
if (version_compare(phpversion(), '5.3', '>=') && class_exists('\WordfenceLS\Controller_DB')) {
global $wpdb;
$table = WFLSPHP52Compatability::secrets_table();
return !!intval($wpdb->get_var("SELECT COUNT(*) FROM `{$table}`"));
}
return false;
}
/**
* Queries the API and returns whether or not the password exists in the breach database.
*
* @param string $login
* @param string $password
* @return bool
*/
public static function isLeakedPassword($login, $password) {
$sha1 = strtoupper(hash('sha1', $password));
$prefix = substr($sha1, 0, 5);
$ssl_verify = (bool) wfConfig::get('ssl_verify');
$args = array(
'timeout' => 5,
'user-agent' => "Wordfence.com UA " . (defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : '[Unknown version]'),
'sslverify' => $ssl_verify,
'headers' => array('Referer' => false),
);
if (!$ssl_verify) { // Some versions of cURL will complain that SSL verification is disabled but the CA bundle was supplied.
$args['sslcertificates'] = false;
}
$response = wp_remote_get(sprintf(WORDFENCE_BREACH_URL_BASE_SEC . "%s.txt", $prefix), $args);
if (!is_wp_error($response)) {
$data = wp_remote_retrieve_body($response);
$lines = explode("\n", $data);
foreach ($lines as $l) {
$components = explode(":", $l);
$teshSHA1 = $prefix . strtoupper($components[0]);
if (hash_equals($sha1, $teshSHA1)) {
return true;
}
}
}
return false;
}
/**
* Returns the transient key for the given user.
*
* @param WP_User $user
* @return string
*/
protected static function _cachedCredentialStatusKey($user) {
$key = 'wfcredentialstatus_' . $user->ID;
return $key;
}
/**
* Returns the cached credential status for the given user: self::UNCACHED, self::NOT_LEAKED, or self::LEAKED.
*
* @param WP_User $user
* @return string
*/
public static function cachedCredentialStatus($user) {
$key = self::_cachedCredentialStatusKey($user);
$value = get_transient($key);
if ($value === false) {
return self::UNCACHED;
}
$status = substr($value, 0, 1);
if (strlen($value) > 1) {
if (!hash_equals(substr($value, 1), hash('sha256', $user->user_pass))) { //Different hash but our clear function wasn't called so treat it as uncached
return self::UNCACHED;
}
}
if ($status) {
return self::LEAKED;
}
return self::NOT_LEAKED;
}
/**
* Stores a cached leak value for the given user.
*
* @param WP_User $user
* @param bool $isLeaked
*/
public static function setCachedCredentialStatus($user, $isLeaked) {
$key = self::_cachedCredentialStatusKey($user);
set_transient($key, ($isLeaked ? '1' : '0') . hash('sha256', $user->user_pass), 3600);
}
/**
* Clears the cache for the given user.
*
* @param WP_User $user
*/
public static function clearCachedCredentialStatus($user) {
$key = self::_cachedCredentialStatusKey($user);
delete_transient($key);
}
/**
* Returns whether or not we've seen a successful login from $ip for the given user.
*
* @param WP_User $user
* @param string $ip
* @return bool
*/
public static function hasPreviousLoginFromIP($user, $ip) {
global $wpdb;
$table_wfLogins = wfDB::networkTable('wfLogins');
$id = property_exists($user, 'ID') ? $user->ID : 0;
if ($id == 0) {
return false;
}
$result = $wpdb->get_row($wpdb->prepare("SELECT id FROM {$table_wfLogins} WHERE action = 'loginOK' AND userID = %d AND IP = %s LIMIT 0,1", $id, wfUtils::inet_pton($ip)), ARRAY_A);
if (is_array($result)) {
return true;
}
$lastAdminLogin = wfConfig::get_ser('lastAdminLogin');
if (is_array($lastAdminLogin) && isset($lastAdminLogin['userID']) && isset($lastAdminLogin['IP'])) {
if ($lastAdminLogin['userID'] == $id && wfUtils::inet_pton($lastAdminLogin['IP']) == wfUtils::inet_pton($ip)) {
return true;
}
return false;
}
//Final check -- if the IP recorded at plugin activation matches, let it through. This is __only__ checked when we don't have any other record of an admin login.
$activatingIP = wfConfig::get('activatingIP');
if (wfUtils::isValidIP($activatingIP)) {
if (wfUtils::inet_pton($activatingIP) == wfUtils::inet_pton($ip)) {
return true;
}
}
return false;
}
}