Hi Torsten.
I hadn’t checked before, so I looked it up now. The login attempts of the same IP are counted and saved as a transient in the WP “options” table.
See: \s2member\src\includes\classes\brute-force.inc.php
/**
* Tracks failed login attempts.
*
* Prevents an attacker from guessing Usernames/Passwords.
* Allows only 5 failed login attempts every 30 minutes.
*
* @package s2Member\Brute_Force
* @since 3.5
*
* @attaches-to ``add_action('wp_login_failed');``
*
* @param string $username Expects the $username to be passed in through the Hook.
*/
public static function track_failed_logins($username = '')
{
foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
do_action('ws_plugin__s2member_before_track_failed_logins', get_defined_vars());
unset($__refs, $__v);
if(($max = $GLOBALS['WS_PLUGIN__']['s2member']['o']['max_failed_login_attempts']))
{
$exp_secs = strtotime('+'.apply_filters('ws_plugin__s2member_track_failed_logins__exp_time', '30 minutes', get_defined_vars())) - time();
// If you add Filters to this value, you should use a string that is compatible with PHP's strtotime() function.
$ip = c_ws_plugin__s2member_utils_ip::current(); // Default value.
if(!empty($GLOBALS['s2member_pro_remote_op_auth_check_user_ip'])
&& c_ws_plugin__s2member_utils_conds::pro_is_installed()
&& c_ws_plugin__s2member_pro_remote_ops::is_remote_op('auth_check_user')
) $ip = $GLOBALS['s2member_pro_remote_op_auth_check_user_ip'];
$transient = 's2m_ipr_'.md5('s2member_transient_failed_login_attempts_'.$ip);
set_transient($transient, (int)get_transient($transient) + 1, $exp_secs);
}
do_action('ws_plugin__s2member_after_track_failed_logins', get_defined_vars());
}
https://codex.wordpress.org/Transients_API
I hope that helps.