diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 45d1fac5..900c9ec6 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -135,7 +135,7 @@ class HttpCli(object): self.headers: dict[str, str] = {} self.mode = " " self.req = " " - self.http_ver = " " + self.http_ver = "" self.hint = "" self.host = " " self.ua = " " @@ -238,6 +238,7 @@ class HttpCli(object): if self.args.ipa_re and not self.args.ipa_re.match(self.conn.addr[0]): self.log("client rejected (--ipa)", 3) + self.terse_reply(b"", 500) return False try: @@ -372,22 +373,33 @@ class HttpCli(object): self.trailing_slash = vpath.endswith("/") vpath = undot(vpath) - zs = unquotep(arglist) - m = self.conn.hsrv.ptn_cc.search(zs) - if m: - hit = zs[m.span()[0] :] - t = "malicious user; Cc in query [{}] => [{!r}]" - self.log(t.format(self.req, hit), 1) - return False - + ptn = self.conn.hsrv.ptn_cc for k in arglist.split("&"): if "=" in k: k, zs = k.split("=", 1) # x-www-form-urlencoded (url query part) uses # either + or %20 for 0x20 so handle both - uparam[k.lower()] = unquotep(zs.strip().replace("+", " ")) + sv = unquotep(zs.strip().replace("+", " ")) else: - uparam[k.lower()] = "" + sv = "" + + k = k.lower() + uparam[k] = sv + + if k in ("doc", "move", "tree"): + continue + + zs = "%s=%s" % (k, sv) + m = ptn.search(zs) + if not m: + continue + + hit = zs[m.span()[0] :] + t = "malicious user; Cc in query [{}] => [{!r}]" + self.log(t.format(self.req, hit), 1) + self.cbonk(self.conn.hsrv.gmal, self.req, "cc_q", "Cc in query") + self.terse_reply(b"", 500) + return False if self.is_vproxied: if vpath.startswith(self.args.R): @@ -426,7 +438,7 @@ class HttpCli(object): if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"): self.log("invalid relpath [{}]".format(self.vpath)) - self.cbonk(self.conn.hsrv.g422, self.vpath, "bad_vp", "invalid relpaths") + self.cbonk(self.conn.hsrv.gmal, self.req, "bad_vp", "invalid relpaths") return self.tx_404() and self.keepalive zso = self.headers.get("authorization") @@ -542,6 +554,7 @@ class HttpCli(object): try: if pex.code == 999: + self.terse_reply(b"", 500) return False post = self.mode in ["POST", "PUT"] or "content-length" in self.headers @@ -626,8 +639,7 @@ class HttpCli(object): return False self.log("banned for {:.0f} sec".format(rt), 6) - zb = b"HTTP/1.0 403 Forbidden\r\n\r\nthank you for playing" - self.s.sendall(zb) + self.terse_reply(b"thank you for playing", 403) return True def permit_caching(self) -> None: @@ -681,6 +693,7 @@ class HttpCli(object): hit = zs[m.span()[0] :] t = "malicious user; Cc in out-hdr {!r} => [{!r}]" self.log(t.format(zs, hit), 1) + self.cbonk(self.conn.hsrv.gmal, zs, "cc_hdr", "Cc in out-hdr") raise Pebkac(999) try: @@ -757,6 +770,19 @@ class HttpCli(object): self.log(body.rstrip()) self.reply(body.encode("utf-8") + b"\r\n", *list(args), **kwargs) + def terse_reply(self, body: bytes, status: int = 200) -> None: + self.keepalive = False + + lines = [ + "%s %s %s" % (self.http_ver or "HTTP/1.1", status, HTTPCODE[status]), + "Connection: Close", + ] + + if body: + lines.append("Content-Length: " + unicode(len(body))) + + self.s.sendall("\r\n".join(lines).encode("utf-8") + b"\r\n\r\n" + body) + def urlq(self, add: dict[str, str], rm: list[str]) -> str: """ generates url query based on uparam (b, pw, all others) @@ -926,6 +952,7 @@ class HttpCli(object): if not static_path.startswith(path_base): t = "malicious user; attempted path traversal [{}] => [{}]" self.log(t.format(self.vpath, static_path), 1) + self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal") self.tx_404() return False diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 994cdcaa..2166bdeb 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -109,6 +109,7 @@ class HttpSrv(object): self.g404 = Garda(self.args.ban_404) self.g403 = Garda(self.args.ban_403) self.g422 = Garda(self.args.ban_422, False) + self.gmal = Garda(self.args.ban_422) self.gurl = Garda(self.args.ban_url) self.bans: dict[str, int] = {} self.aclose: dict[str, int] = {} diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 89efe6df..e45bfd25 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -138,7 +138,8 @@ class SvcHub(object): self.gpwd = Garda(self.args.ban_pw) self.g404 = Garda(self.args.ban_404) self.g403 = Garda(self.args.ban_403) - self.g422 = Garda(self.args.ban_422) + self.g422 = Garda(self.args.ban_422, False) + self.gmal = Garda(self.args.ban_422) self.gurl = Garda(self.args.ban_url) self.log_div = 10 ** (6 - args.log_tdec) diff --git a/tests/util.py b/tests/util.py index e2c01e10..4caa56de 100644 --- a/tests/util.py +++ b/tests/util.py @@ -115,7 +115,7 @@ class Cfg(Namespace): ex = "dotpart no_rescan no_sendfile no_voldump plain_ip" ka.update(**{k: True for k in ex.split()}) - ex = "ah_cli ah_gen css_browser hist js_browser no_forget no_hash no_idx nonsus_urls" + ex = "ah_cli ah_gen css_browser hist ipa_re js_browser no_forget no_hash no_idx nonsus_urls" ka.update(**{k: None for k in ex.split()}) ex = "s_thead s_tbody th_convt" @@ -124,7 +124,7 @@ class Cfg(Namespace): ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo" ka.update(**{k: 0 for k in ex.split()}) - ex = "ah_alg bname doctitle favico html_head lg_sbf log_fk md_sbf name textfiles unlist vname R RS SR" + ex = "ah_alg bname doctitle favico hdr_au_usr html_head lg_sbf log_fk md_sbf name textfiles unlist vname R RS SR" ka.update(**{k: "" for k in ex.split()}) ex = "on403 on404 xad xar xau xban xbd xbr xbu xiu xm"