Daniel Kastenholz
2005-Feb-24 13:54 UTC
Suggestion: SSHD pseudo/fake mode. Source available.
Hi, SSH brute force attacks seem to enjoy increasing popularity. Call me an optimist or a misrouted kind of contributer to the community, but on our company server I actually go through the logs and report extreme cases to the providers of the originating IP's. With the increasing number of these attacks, however, I have now decided that it's better to move the SSHd to a different port. The downside is: it was actually fun to report a failed brute force attack from time to time! Alright, I know, there are IDS's available, and scanners, etc., etc., ... but one benefit of having a real daemon on port 22 is that it keeps the intruder busy and produces evidence through failed login attempts and usernames in the logfiles. So I thought it might be sensible to build a and run a fake server running on port 22 that behaves essentially like an original SSH daemon (key exchange, password request, ..) but strictly denies every attempt to login, even if the password turns out to be right. I don't know if anyone else would find such a feature useful. But I learned that it's just a few lines of additional code. I've run this against release 3-9.p1 of OpenSSH. In short, here's what I did: - added a new command line flag "-T" for trap to trigger the internal "trap_mode" flag - added a "trap" flag to the "authctxt" type that is set according to "trap_mode" when a new context is created - extended the conditionals in auth1.c etc. to circumvent "authenticated"=1 when "authctxt->trap==1", even if the authentication itself was successul. Little effort for a trap that's almost impossible to identify as such. If there's any interest in this solution, I would willingly provide a patch file! Tiny little problem: I've never contributed to an open source project before and don't know how to create this patch file thing. Is that just the output of a "diff"? If someone tells me or could point me to a short (!) tutorial, it's all yours. And if you don't like having such an option in your sshd, well, no one forces you to use it. But somebody else might be happy to have it. Regards Daniel
Daniel Kastenholz
2005-Feb-25 17:50 UTC
Suggestion: SSHD pseudo/fake mode. Source available.
Hi, - context: yesterday I suggested a command line option for sshd that turns the daemon into a fake (or pseudo) daemon to listen on port 22 while denying all logins even if the provided passwords are right. The reason for this suggestion was the increasing number of brute force attacks against SSH daemons and the wish to provide intruders a playground where they can waste their time while being monitored and without the chance to actually break anything. I received a couple of replies and there seems to be interest in such an option. As requested, I have now run a diff against the modified sources (originally 3.9p1). The whole patch needs about 30 lines including comments. It adds a command line option '-T' to sshd for enabling the trap mode. The output of the diff follows at the end of this message. Hope this helps. If you have any further remarks, suggestions, questions ... just drop me a line. I assume the patch could be modified to use even less lines, but it works like this. Do I have to file this diff anywhere else to make the patch request official? Regards, Daniel -------------------- diff --context=3 openssh-3.9p1/auth.h modified/auth.h *** openssh-3.9p1/auth.h Mon May 24 02:36:23 2004 --- modified/auth.h Wed Feb 23 16:41:51 2005 *************** *** 50,55 **** --- 50,56 ---- int success; int postponed; /* authentication needs another step */ int valid; /* user exists and is allowed to login */ + int trap; /* enforces login denial in trap mode */ int attempt; int failures; int force_pwchange; diff --context=3 openssh-3.9p1/auth1.c modified/auth1.c *** openssh-3.9p1/auth1.c Thu Aug 12 14:40:25 2004 --- modified/auth1.c Wed Feb 23 16:35:22 2005 *************** *** 74,80 **** authctxt->valid ? "" : "invalid user ", authctxt->user); /* If the user has no password, accept authentication immediately. */ ! if (options.password_authentication && #ifdef KRB5 (!options.kerberos_authentication || options.kerberos_or_local_passwd) && #endif --- 74,80 ---- authctxt->valid ? "" : "invalid user ", authctxt->user); /* If the user has no password, accept authentication immediately. */ ! if (authctxt->trap==0 && options.password_authentication && #ifdef KRB5 (!options.kerberos_authentication || options.kerberos_or_local_passwd) && #endif *************** *** 142,148 **** BN_num_bits(client_host_key->rsa->n), bits); packet_check_eom(); ! authenticated = auth_rhosts_rsa(authctxt, client_user, client_host_key); key_free(client_host_key); --- 142,148 ---- BN_num_bits(client_host_key->rsa->n), bits); packet_check_eom(); ! authenticated = (authctxt->trap==0) && auth_rhosts_rsa(authctxt, client_user, client_host_key); key_free(client_host_key); *************** *** 159,165 **** fatal("do_authloop: BN_new failed"); packet_get_bignum(n); packet_check_eom(); ! authenticated = auth_rsa(authctxt, n); BN_clear_free(n); break; --- 159,165 ---- fatal("do_authloop: BN_new failed"); packet_get_bignum(n); packet_check_eom(); ! authenticated = (authctxt->trap==0) && auth_rsa(authctxt, n); BN_clear_free(n); break; *************** *** 177,183 **** packet_check_eom(); /* Try authentication with the password. */ ! authenticated = PRIVSEP(auth_password(authctxt, password)); memset(password, 0, strlen(password)); xfree(password); --- 177,183 ---- packet_check_eom(); /* Try authentication with the password. */ ! authenticated = (authctxt->trap==0) && PRIVSEP(auth_password(authctxt, password)); memset(password, 0, strlen(password)); xfree(password); *************** *** 203,209 **** if (options.challenge_response_authentication == 1) { char *response = packet_get_string(&dlen); packet_check_eom(); ! authenticated = verify_response(authctxt, response); memset(response, 'r', dlen); xfree(response); } --- 203,209 ---- if (options.challenge_response_authentication == 1) { char *response = packet_get_string(&dlen); packet_check_eom(); ! authenticated = (authctxt->trap==0) && verify_response(authctxt, response); memset(response, 'r', dlen); xfree(response); } diff --context=3 openssh-3.9p1/auth2.c modified/auth2.c *** openssh-3.9p1/auth2.c Thu Aug 12 14:40:25 2004 --- modified/auth2.c Wed Feb 23 16:35:23 2005 *************** *** 210,215 **** --- 210,218 ---- fatal("INTERNAL ERROR: authenticated invalid user %s", authctxt->user); + /* Deny login if in trap mode */ + if (authctxt->trap!=0) authenticated = 0; + /* Special handling for root */ if (authenticated && authctxt->pw->pw_uid == 0 && !auth_root_allowed(method)) Common subdirectories: openssh-3.9p1/contrib and modified/contrib Common subdirectories: openssh-3.9p1/openbsd-compat and modified/openbsd-compat Common subdirectories: openssh-3.9p1/regress and modified/regress Common subdirectories: openssh-3.9p1/scard and modified/scard diff --context=3 openssh-3.9p1/sshd.c modified/sshd.c *** openssh-3.9p1/sshd.c Thu Aug 12 15:08:15 2004 --- modified/sshd.c Wed Feb 23 17:14:34 2005 *************** *** 125,130 **** --- 125,137 ---- */ int debug_flag = 0; + /* + * Trap mode flag. In this mode, the entire authentication procedure + * takes place, but the login always fails. The purpose of this flag + * is to enable the setup of fake servers for intrusion detection. + */ + int trap_flag = 0; + /* Flag indicating that the daemon should only test the configuration and keys. */ int test_flag = 0; *************** *** 776,782 **** fprintf(stderr, "%s, %s\n", SSH_VERSION, SSLeay_version(SSLEAY_VERSION)); fprintf(stderr, ! "usage: sshd [-46Ddeiqt] [-b bits] [-f config_file] [-g login_grace_time]\n" " [-h host_key_file] [-k key_gen_time] [-o option] [-p port] [-u len]\n" ); exit(1); --- 783,789 ---- fprintf(stderr, "%s, %s\n", SSH_VERSION, SSLeay_version(SSLEAY_VERSION)); fprintf(stderr, ! "usage: sshd [-46DdeiqtT] [-b bits] [-f config_file] [-g login_grace_time]\n" " [-h host_key_file] [-k key_gen_time] [-o option] [-p port] [-u len]\n" ); exit(1); *************** *** 918,924 **** initialize_server_options(&options); /* Parse command-line arguments. */ ! while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:dDeiqrtQR46")) != -1) { switch (opt) { case '4': IPv4or6 = AF_INET; --- 925,931 ---- initialize_server_options(&options); /* Parse command-line arguments. */ ! while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:dDeiqrtQR46T")) != -1) { switch (opt) { case '4': IPv4or6 = AF_INET; *************** *** 929,934 **** --- 936,944 ---- case 'f': config_file_name = optarg; break; + case 'T': + trap_flag = 1; + break; case 'd': if (debug_flag == 0) { debug_flag = 1; *************** *** 1675,1680 **** --- 1685,1693 ---- authctxt = xmalloc(sizeof(*authctxt)); memset(authctxt, 0, sizeof(*authctxt)); + /* set trap indicator if in trap mode */ + if (trap_flag != 0) authctxt->trap = 1; + /* XXX global for cleanup, access from other modules */ the_authctxt = authctxt;
Daniel Kastenholz
2005-Feb-26 07:30 UTC
Suggestion: SSHD pseudo/fake mode. Source available.
Hi again, it's once more about this SSH trap thing. I have received some answers which proposed to use configuration options like "DenyUsers *" to deny all logins. That approach sounds more promising, especially from the developer's perspective, because it wouldn't need tweaks in the code itself. I must admit I hadn't tried this! And, in fact, it does work: all credentials are rejected, even if they're correct. The effort is in fact a lot lower than with my circumstantial tweaks in the source code itself. However, the daemon behaves slightly different when the "DenyUsers *" option is used. By default, sshd disconnects when the third wrong set of credentials has been provided. With "DenyUsers *", this always happens after the first attempt. In some - admittedly: very rare - cases, that _might_ alert an attacker. (And as stated earlier, the intention was to have a trap that behaves essentially like an unmodified daemon does.) But in most cases this difference _should_ remain unnoticed, since brute force attackers usually disconnect after the first failed attempt anyway and reconnect. Regards Daniel
Daniel Kastenholz wrote:> However, the daemon behaves slightly different when the "DenyUsers *" > option is used. By default, sshd disconnects when the third wrong set of > credentials has been provided. With "DenyUsers *", this always happens > after the first attempt.Any such differences in behaviour ought to be found and fixed. Under what circumstances does this occur? (Compile options, config options, authentication method, valid/invalid user?) A quick test here with 3.9p1 shows the same behaviour for password and pubkey authentication (ie sshd just denies the auth attempt and the client can retry, up until the client disconnects or the MaxAuthTries limit is reached). Could you post the server-side debugging for both instances? -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement.