| @@ -141,9 +141,9 @@ | ||
| 141 | 141 | * |
| 142 | 142 | * Gate checks (in order): |
| 143 | 143 | * 1. Module master switch (modules.activity_log) |
| 144 | 144 | * 2. Per-type flag via $type_flag_map (types not in the map always pass) |
| 145 | - * 3. Sub-check: failed logins respect log_failed_logins | |
| 145 | + * 3. Login: each of its two checkboxes governs its own half (see below) | |
| 146 | 146 | * 4. User/IP exclusions |
| 147 | 147 | * |
| 148 | 148 | * @param string $type Event type. |
| 149 | 149 | * @param string $action Event action. |
| @@ -160,9 +160,9 @@ | ||
| 160 | 160 | |
| 161 | 161 | // Gate 2: Per-type flag |
| 162 | 162 | $current_options = $this->get_current_options(); |
| 163 | 163 | |
| 164 | - if ( isset( self::$type_flag_map[ $type ] ) ) { | |
| 164 | + if ( isset( self::$type_flag_map[ $type ] ) && 'login' !== $type ) { | |
| 165 | 165 | $flag = self::$type_flag_map[ $type ]; |
| 166 | 166 | if ( empty( $current_options[ $flag ] ) ) { |
| 167 | 167 | return false; |
| 168 | 168 | } |
| @@ -167,11 +167,35 @@ | ||
| 167 | 167 | return false; |
| 168 | 168 | } |
| 169 | 169 | } |
| 170 | 170 | |
| 171 | - // Gate 3: Failed logins sub-check | |
| 172 | - if ( 'login' === $type && in_array( $action, array( 'failed', 'lockout' ), true ) ) { | |
| 173 | - if ( empty( $current_options['log_failed_logins'] ) ) { | |
| 171 | + /* | |
| 172 | + * Gate 3: login is the one type with two checkboxes, offered side by | |
| 173 | + * side as "Successful logins" and "Failed login attempts". Until 2.11.10 | |
| 174 | + * the per-type gate above cut first, so unchecking the first one also | |
| 175 | + * silenced the second and with it everything security relevant this type | |
| 176 | + * carries: failed attempts, lockouts, logins blocked by a forced reset | |
| 177 | + * and the probes of the hidden wp-admin and login. The audit alerts of | |
| 178 | + * the login category went quiet at the same time, because they only fire | |
| 179 | + * for events that get stored, so a site under attack looked calm on both | |
| 180 | + * screens. Found by the file-by-file review of 2.11.10. | |
| 181 | + * | |
| 182 | + * Each checkbox governs its own half now: a successful login answers to | |
| 183 | + * log_logins, and everything else about login, which is attack traffic, | |
| 184 | + * answers to log_failed_logins. | |
| 185 | + */ | |
| 186 | + if ( 'login' === $type ) { | |
| 187 | + /* | |
| 188 | + * Written as the list of what is attack traffic, not as "everything | |
| 189 | + * that is not a success": login_url_notified is an administrative | |
| 190 | + * notice and answering to the failed-attempts checkbox made it | |
| 191 | + * disappear for anyone who had that one unchecked, which is a record | |
| 192 | + * 2.11.9 did keep. Found by the cross review of 2.11.10. | |
| 193 | + */ | |
| 194 | + $attack = array( 'failed', 'lockout', 'lockout_blocked', 'hidden_admin_access', 'hidden_login_access', 'force_reset_login_blocked' ); | |
| 195 | + $flag = in_array( $action, $attack, true ) ? 'log_failed_logins' : 'log_logins'; | |
| 196 | + | |
| 197 | + if ( empty( $current_options[ $flag ] ) ) { | |
| 174 | 198 | return false; |
| 175 | 199 | } |
| 176 | 200 | } |
| 177 | 201 | |
| @@ -930,8 +954,14 @@ | ||
| 930 | 954 | * scanner probe, and a remote manager being refused apart from an intruder, |
| 931 | 955 | * so an owner looking at a surprising entry had no way to tell which one |
| 932 | 956 | * they were reading. |
| 933 | 957 | * |
| 958 | + * That first sentence was not true of the firewall, which is the module | |
| 959 | + * that logs the most: it stored the address under 'uri', so the column | |
| 960 | + * this method feeds was empty for every one of its blocks, and diagnosing | |
| 961 | + * one meant reading the table by hand. Fixed in the firewall in 2.11.1; | |
| 962 | + * 'uri' is read here as well so the entries already on disk show it too. | |
| 963 | + * | |
| 934 | 964 | * @since 2.10.2 |
| 935 | 965 | * |
| 936 | 966 | * @param string|array|null $extra_data The entry's extra data, as stored. |
| 937 | 967 | * @return string The recorded address, or '' when the entry carries none. |
| @@ -940,20 +970,26 @@ | ||
| 940 | 970 | if ( is_string( $extra_data ) ) { |
| 941 | 971 | $extra_data = json_decode( $extra_data, true ); |
| 942 | 972 | } |
| 943 | 973 | |
| 944 | - if ( ! is_array( $extra_data ) || ! isset( $extra_data['request_uri'] ) ) { | |
| 974 | + if ( ! is_array( $extra_data ) ) { | |
| 945 | 975 | return ''; |
| 946 | 976 | } |
| 947 | 977 | |
| 978 | + $key = isset( $extra_data['request_uri'] ) ? 'request_uri' : 'uri'; | |
| 979 | + | |
| 980 | + if ( ! isset( $extra_data[ $key ] ) ) { | |
| 981 | + return ''; | |
| 982 | + } | |
| 983 | + | |
| 948 | 984 | // Nothing writes anything but a string here, but the value comes back |
| 949 | 985 | // from a longtext column that any past version could have filled, and |
| 950 | 986 | // casting an array would emit a notice and print the word "Array". |
| 951 | - if ( ! is_scalar( $extra_data['request_uri'] ) ) { | |
| 987 | + if ( ! is_scalar( $extra_data[ $key ] ) ) { | |
| 952 | 988 | return ''; |
| 953 | 989 | } |
| 954 | 990 | |
| 955 | - return (string) $extra_data['request_uri']; | |
| 991 | + return (string) $extra_data[ $key ]; | |
| 956 | 992 | } |
| 957 | 993 | |
| 958 | 994 | /** |
| 959 | 995 | * Cleanup old logs based on retention settings (uses fresh options) |