Is there functionality in golang.org/x/crypto/ssh to close a SSH connection server-side after N number of (password) tries?

403 views
Skip to first unread message

ikkini

unread,
Feb 25, 2017, 9:38:21 PM2/25/17
to golang-nuts
Hey all,
The client ssh library of golang.org/x/crypto/ssh has the maxTries variable in the RetryableAuthMethod:

func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod

However, I can't seem to find a server-side version of this.
I discovered the SessionID variable (in the ConnMetadata interface, imported into server.go from connection.go) which seems to be an excellent candidate for implementing such functionality.

I am hoping the functionality to close a connection server-side after N number of tries is there already and I am just not seeing it?
Or perhaps someone implemented this already?

Any help or pointers in the right direction/to the relevant documentation would be very much appreciated.

Thanks in advance!


Tamás Gulácsi

unread,
Feb 26, 2017, 12:47:34 AM2/26/17
to golang-nuts
Provide a proper PasswordCallback to your server, as in the NewServerConn example (https://godoc.org/golang.org/x/crypto/ssh#NewServerConn).

Thats your function, you can program any algorithm you wish.

ikkini

unread,
Mar 14, 2017, 6:34:32 AM3/14/17
to golang-nuts


On Sunday, February 26, 2017 at 6:47:34 AM UTC+1, Tamás Gulácsi wrote:
Provide a proper PasswordCallback to your server, as in the NewServerConn example (https://godoc.org/golang.org/x/crypto/ssh#NewServerConn).

Thats your function, you can program any algorithm you wish.


Thanks for the advise, I looked into it. I was hoping that ssh/server.go implemented RFC4252, par. 4::

"[...] Additionally, the implementation SHOULD limit the number of failed authentication
attempts a client may perform in a single session (the RECOMMENDED limit is 20 attempts).
If the threshold is exceeded, the server SHOULD disconnect."

The reason why I need this is that there are currently SSH brute-force bots out there which are so badly written, they retry (the same username and password combination) for pretty much as long as nothing else times out/fails.

Anyway, my attempts to build a maxTries algorithm into PasswordCallback were getting uglier by the minute, so I ended up with this solution/kludge to limit the amount of retries within a session:
The good thing about this is the maxTries works per session, which within PasswordCallback was a real problem to do (at least for me, I'm still very much out of my depth when it comes to golang).

diff --git a/ssh/server.go b/ssh/server.go
index 37df1b3..4ba569e 100644
--- a/ssh/server.go
+++ b/ssh/server.go
@@ -263,6 +263,8 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
        var err error
        var cache pubKeyCache
        var perms *Permissions
+       var sum = 0
+       const maxTries = 3
 
 userAuthLoop:
        for {
@@ -300,8 +302,11 @@ userAuthLoop:
                        if !ok || len(payload) > 0 {
                                return nil, parseError(msgUserAuthRequest)
                        }
-
                        perms, authErr = config.PasswordCallback(s, password)
+                       if sum >= maxTries {
+                               break userAuthLoop
+                       }
+                       sum += 1
                case "keyboard-interactive":
                        if config.KeyboardInteractiveCallback == nil {
                                authErr = errors.New("ssh: keyboard-interactive auth not configubred")



ikkini

unread,
Mar 16, 2017, 6:07:44 AM3/16/17
to golang-nuts
For anyone trying this: If you 'break userAuthLoop, you'll end up below the label, which equals success. Probably not what you want :/
instead, I'm now using 'break' and authErr, which gives me the desired behavior.

diff --git a/ssh/server.go b/ssh/server.go
index 37df1b3..0f4dd74 100644

--- a/ssh/server.go
+++ b/ssh/server.go
@@ -263,6 +263,8 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
        var err error
        var cache pubKeyCache
        var perms *Permissions
+       var sum = 0
+       const maxTries = 1
 
 userAuthLoop:
        for {
@@ -300,8 +302,12 @@ userAuthLoop:

                        if !ok || len(payload) > 0 {
                                return nil, parseError(msgUserAuthRequest)
                        }
-
                        perms, authErr = config.PasswordCallback(s, password)
+                       if sum >= maxTries {
+                               authErr = errors.New("ssh: maxTries reached")
+                               break

+                       }
+                       sum += 1
                case "keyboard-interactive":
                        if config.KeyboardInteractiveCallback == nil {
                                authErr = errors.New("ssh: keyboard-interactive auth not configubred")

Konstantin Khomoutov

unread,
Mar 16, 2017, 6:19:58 AM3/16/17
to ikkini, golang-nuts
On Thu, 16 Mar 2017 03:07:44 -0700 (PDT)
ikkini <ikk...@gmail.com> wrote:

> For anyone trying this: If you 'break userAuthLoop, you'll end up
> below the label, which equals success. Probably not what you want :/
> instead, I'm now using 'break' and authErr, which gives me the
> desired behavior.

A tangential issue, but have you considered coupling your Go service
with some tool like fail2ban? Basically, you'd need to make your
service output log entries on authentication failures in a way so that
they end up being written in some log file, and then teach fail2ban to
parse them. If you'll make them look exactly as the OpenSSH server
spells them, fail2ban will be ready to rock right away.

An upside of using this tool is that it bans the specific IP address
for the configured amount of time so that this lowers the pressure on
your service and raises the signal-to-noise ratio of your SSH sevrice
log.
Reply all
Reply to author
Forward
0 new messages