From d326ba9723e3b77ff72bbceeca7c240d430bd26f Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 20 Nov 2022 11:06:07 +0000 Subject: [PATCH] ftp: ban password-bruteforcing IPs --- copyparty/ftpd.py | 40 ++++++++++++++++++++++++++++++---------- copyparty/svchub.py | 6 ++++++ copyparty/util.py | 6 +++++- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index 4becbbc8..fdd1f514 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -15,7 +15,7 @@ from pyftpdlib.servers import FTPServer from .__init__ import PY2, TYPE_CHECKING, E from .bos import bos -from .util import Daemon, Pebkac, exclude_dotfiles, fsenc +from .util import Daemon, Pebkac, exclude_dotfiles, fsenc, ipnorm try: from pyftpdlib.ioloop import IOLoop @@ -42,21 +42,41 @@ class FtpAuth(DummyAuthorizer): def validate_authentication( self, username: str, password: str, handler: Any ) -> None: + handler.username = "{}:{}".format(username, password) + + ip = handler.addr[0] + if ip.startswith("::ffff:"): + ip = ip[7:] + + ip = ipnorm(ip) + bans = self.hub.bans + if ip in bans: + rt = bans[ip] - time.time() + if rt < 0: + logging.info("client unbanned") + del bans[ip] + else: + raise AuthenticationFailed("banned") + asrv = self.hub.asrv if username == "anonymous": - password = "" + uname = "*" + else: + creds = password or username + uname = asrv.iacct.get(creds, "") if creds else "*" - uname = "*" - if password: - uname = asrv.iacct.get(password, "") + if not uname or not (asrv.vfs.aread.get(uname) or asrv.vfs.awrite.get(uname)): + g = self.hub.gpwd + if g.lim: + bonk, ip = g.bonk(ip, handler.username) + if bonk: + logging.warning("client banned: invalid passwords") + bans[ip] = bonk + + raise AuthenticationFailed("Authentication failed.") handler.username = uname - if (password and not uname) or not ( - asrv.vfs.aread.get(uname) or asrv.vfs.awrite.get(uname) - ): - raise AuthenticationFailed("Authentication failed.") - def get_home_dir(self, username: str) -> str: return "/" diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 31024af6..35c87bf5 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -35,6 +35,7 @@ from .up2k import Up2k from .util import ( VERSIONS, Daemon, + Garda, HLog, HMaccas, alltrace, @@ -85,6 +86,11 @@ class SvcHub(object): self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8) + # for non-http clients (ftp) + self.bans: dict[str, int] = {} + self.gpwd = Garda(self.args.ban_pw) + self.g404 = Garda(self.args.ban_404) + if args.sss or args.s >= 3: args.ss = True args.no_dav = True diff --git a/copyparty/util.py b/copyparty/util.py index a1392eac..b7f0fa40 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -412,8 +412,12 @@ class HLog(logging.Handler): if record.name == "pyftpdlib": m = self.ptn_ftp.match(msg) if m: - record.name = ip = m.group(1) + ip = m.group(1) msg = msg[len(ip) + 1 :] + if ip.startswith("::ffff:"): + record.name = ip[7:] + else: + record.name = ip elif record.name.startswith("impacket"): if self.ptn_smb_ign.match(msg): return