mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
improve/simplify validation/errorhandling:
* some malicious requests are now answered with HTTP 422, so that they count against --ban-422 * do not include request headers when replying to invalid requests, in case there is a reverse-proxy inserting something interesting
This commit is contained in:
parent
2523d76756
commit
8020b11ea0
|
@ -476,12 +476,10 @@ class VFS(object):
|
||||||
err: int = 403,
|
err: int = 403,
|
||||||
) -> tuple["VFS", str]:
|
) -> tuple["VFS", str]:
|
||||||
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
||||||
if ANYWIN:
|
if relchk(vpath):
|
||||||
mod = relchk(vpath)
|
|
||||||
if mod:
|
|
||||||
if self.log:
|
if self.log:
|
||||||
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
||||||
raise Pebkac(404)
|
raise Pebkac(422)
|
||||||
|
|
||||||
cvpath = undot(vpath)
|
cvpath = undot(vpath)
|
||||||
vn, rem = self._find(cvpath)
|
vn, rem = self._find(cvpath)
|
||||||
|
|
|
@ -148,7 +148,7 @@ class FtpFs(AbstractedFS):
|
||||||
try:
|
try:
|
||||||
vpath = vpath.replace("\\", "/").strip("/")
|
vpath = vpath.replace("\\", "/").strip("/")
|
||||||
rd, fn = os.path.split(vpath)
|
rd, fn = os.path.split(vpath)
|
||||||
if ANYWIN and relchk(rd):
|
if relchk(rd):
|
||||||
logging.warning("malicious vpath: %s", vpath)
|
logging.warning("malicious vpath: %s", vpath)
|
||||||
t = "Unsupported characters in [{}]"
|
t = "Unsupported characters in [{}]"
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
|
|
|
@ -37,6 +37,7 @@ from .star import StreamTar
|
||||||
from .sutil import StreamArc, gfilter
|
from .sutil import StreamArc, gfilter
|
||||||
from .szip import StreamZip
|
from .szip import StreamZip
|
||||||
from .util import (
|
from .util import (
|
||||||
|
Garda,
|
||||||
HTTPCODE,
|
HTTPCODE,
|
||||||
META_NOBOTS,
|
META_NOBOTS,
|
||||||
MultipartParser,
|
MultipartParser,
|
||||||
|
@ -75,6 +76,7 @@ from .util import (
|
||||||
runhook,
|
runhook,
|
||||||
s3enc,
|
s3enc,
|
||||||
sanitize_fn,
|
sanitize_fn,
|
||||||
|
sanitize_vpath,
|
||||||
sendfile_kern,
|
sendfile_kern,
|
||||||
sendfile_py,
|
sendfile_py,
|
||||||
undot,
|
undot,
|
||||||
|
@ -146,6 +148,7 @@ class HttpCli(object):
|
||||||
self.rem = " "
|
self.rem = " "
|
||||||
self.vpath = " "
|
self.vpath = " "
|
||||||
self.vpaths = " "
|
self.vpaths = " "
|
||||||
|
self.gctx = " " # additional context for garda
|
||||||
self.trailing_slash = True
|
self.trailing_slash = True
|
||||||
self.uname = " "
|
self.uname = " "
|
||||||
self.pw = " "
|
self.pw = " "
|
||||||
|
@ -254,8 +257,8 @@ class HttpCli(object):
|
||||||
k, zs = header_line.split(":", 1)
|
k, zs = header_line.split(":", 1)
|
||||||
self.headers[k.lower()] = zs.strip()
|
self.headers[k.lower()] = zs.strip()
|
||||||
except:
|
except:
|
||||||
msg = " ]\n#[ ".join(headerlines)
|
msg = "#[ " + " ]\n#[ ".join(headerlines) + " ]"
|
||||||
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
|
raise Pebkac(400, "bad headers", log=msg)
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
self.mode = "GET"
|
self.mode = "GET"
|
||||||
|
@ -268,6 +271,10 @@ class HttpCli(object):
|
||||||
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if ex.log:
|
||||||
|
self.log("additional error context:\n" + ex.log, 6)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.ua = self.headers.get("user-agent", "")
|
self.ua = self.headers.get("user-agent", "")
|
||||||
|
@ -411,12 +418,9 @@ class HttpCli(object):
|
||||||
self.vpath + "/" if self.trailing_slash and self.vpath else self.vpath
|
self.vpath + "/" if self.trailing_slash and self.vpath else self.vpath
|
||||||
)
|
)
|
||||||
|
|
||||||
ok = "\x00" not in self.vpath
|
if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"):
|
||||||
if ANYWIN:
|
|
||||||
ok = ok and not relchk(self.vpath)
|
|
||||||
|
|
||||||
if not ok and (self.vpath != "*" or self.mode != "OPTIONS"):
|
|
||||||
self.log("invalid relpath [{}]".format(self.vpath))
|
self.log("invalid relpath [{}]".format(self.vpath))
|
||||||
|
self.cbonk(self.conn.hsrv.g422, self.vpath, "bad_vp", "invalid relpaths")
|
||||||
return self.tx_404() and self.keepalive
|
return self.tx_404() and self.keepalive
|
||||||
|
|
||||||
zso = self.headers.get("authorization")
|
zso = self.headers.get("authorization")
|
||||||
|
@ -549,6 +553,9 @@ class HttpCli(object):
|
||||||
zb = b"<pre>" + html_escape(msg).encode("utf-8", "replace")
|
zb = b"<pre>" + html_escape(msg).encode("utf-8", "replace")
|
||||||
h = {"WWW-Authenticate": 'Basic realm="a"'} if pex.code == 401 else {}
|
h = {"WWW-Authenticate": 'Basic realm="a"'} if pex.code == 401 else {}
|
||||||
self.reply(zb, status=pex.code, headers=h, volsan=True)
|
self.reply(zb, status=pex.code, headers=h, volsan=True)
|
||||||
|
if pex.log:
|
||||||
|
self.log("additional error context:\n" + pex.log, 6)
|
||||||
|
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
return False
|
return False
|
||||||
|
@ -559,6 +566,34 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
return self.conn.iphash.s(self.ip)
|
return self.conn.iphash.s(self.ip)
|
||||||
|
|
||||||
|
def cbonk(self, g: Garda, v: str, reason: str, descr: str) -> bool:
|
||||||
|
if not g.lim:
|
||||||
|
return False
|
||||||
|
|
||||||
|
bonk, ip = g.bonk(self.ip, v + self.gctx)
|
||||||
|
if not bonk:
|
||||||
|
return False
|
||||||
|
|
||||||
|
xban = self.vn.flags.get("xban")
|
||||||
|
if not xban or not runhook(
|
||||||
|
self.log,
|
||||||
|
xban,
|
||||||
|
self.vn.canonical(self.rem),
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
time.time(),
|
||||||
|
0,
|
||||||
|
self.ip,
|
||||||
|
time.time(),
|
||||||
|
reason,
|
||||||
|
):
|
||||||
|
self.log("client banned: %s" % (descr,), 1)
|
||||||
|
self.conn.hsrv.bans[ip] = bonk
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def is_banned(self) -> bool:
|
def is_banned(self) -> bool:
|
||||||
if not self.conn.bans:
|
if not self.conn.bans:
|
||||||
return False
|
return False
|
||||||
|
@ -678,24 +713,7 @@ class HttpCli(object):
|
||||||
or not self.args.nonsus_urls
|
or not self.args.nonsus_urls
|
||||||
or not self.args.nonsus_urls.search(self.vpath)
|
or not self.args.nonsus_urls.search(self.vpath)
|
||||||
):
|
):
|
||||||
bonk, ip = g.bonk(self.ip, self.vpath)
|
self.cbonk(g, self.vpath, str(status), "%ss" % (status,))
|
||||||
if bonk:
|
|
||||||
xban = self.vn.flags.get("xban")
|
|
||||||
if not xban or not runhook(
|
|
||||||
self.log,
|
|
||||||
xban,
|
|
||||||
self.vn.canonical(self.rem),
|
|
||||||
self.vpath,
|
|
||||||
self.host,
|
|
||||||
self.uname,
|
|
||||||
time.time(),
|
|
||||||
0,
|
|
||||||
self.ip,
|
|
||||||
time.time(),
|
|
||||||
str(status),
|
|
||||||
):
|
|
||||||
self.log("client banned: %ss" % (status,), 1)
|
|
||||||
self.conn.hsrv.bans[ip] = bonk
|
|
||||||
|
|
||||||
if volsan:
|
if volsan:
|
||||||
vols = list(self.asrv.vfs.all_vols.values())
|
vols = list(self.asrv.vfs.all_vols.values())
|
||||||
|
@ -2133,27 +2151,7 @@ class HttpCli(object):
|
||||||
logpwd = "%" + base64.b64encode(zb[:12]).decode("utf-8")
|
logpwd = "%" + base64.b64encode(zb[:12]).decode("utf-8")
|
||||||
|
|
||||||
self.log("invalid password: {}".format(logpwd), 3)
|
self.log("invalid password: {}".format(logpwd), 3)
|
||||||
|
self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
|
||||||
g = self.conn.hsrv.gpwd
|
|
||||||
if g.lim:
|
|
||||||
bonk, ip = g.bonk(self.ip, pwd)
|
|
||||||
if bonk:
|
|
||||||
xban = self.vn.flags.get("xban")
|
|
||||||
if not xban or not runhook(
|
|
||||||
self.log,
|
|
||||||
xban,
|
|
||||||
self.vn.canonical(self.rem),
|
|
||||||
self.vpath,
|
|
||||||
self.host,
|
|
||||||
self.uname,
|
|
||||||
time.time(),
|
|
||||||
0,
|
|
||||||
self.ip,
|
|
||||||
time.time(),
|
|
||||||
"pw",
|
|
||||||
):
|
|
||||||
self.log("client banned: invalid passwords", 1)
|
|
||||||
self.conn.hsrv.bans[ip] = bonk
|
|
||||||
|
|
||||||
msg = "naw dude"
|
msg = "naw dude"
|
||||||
pwd = "x" # nosec
|
pwd = "x" # nosec
|
||||||
|
|
|
@ -1563,8 +1563,8 @@ def read_header(sr: Unrecv, t_idle: int, t_tot: int) -> list[str]:
|
||||||
|
|
||||||
raise Pebkac(
|
raise Pebkac(
|
||||||
400,
|
400,
|
||||||
"protocol error while reading headers:\n"
|
"protocol error while reading headers",
|
||||||
+ ret.decode("utf-8", "replace"),
|
log=ret.decode("utf-8", "replace"),
|
||||||
)
|
)
|
||||||
|
|
||||||
ofs = ret.find(b"\r\n\r\n")
|
ofs = ret.find(b"\r\n\r\n")
|
||||||
|
@ -1774,6 +1774,9 @@ def sanitize_fn(fn: str, ok: str, bad: list[str]) -> str:
|
||||||
|
|
||||||
|
|
||||||
def relchk(rp: str) -> str:
|
def relchk(rp: str) -> str:
|
||||||
|
if "\x00" in rp:
|
||||||
|
return "[nul]"
|
||||||
|
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
if "\n" in rp or "\r" in rp:
|
if "\n" in rp or "\r" in rp:
|
||||||
return "x\nx"
|
return "x\nx"
|
||||||
|
@ -2976,9 +2979,12 @@ def hidedir(dp) -> None:
|
||||||
|
|
||||||
|
|
||||||
class Pebkac(Exception):
|
class Pebkac(Exception):
|
||||||
def __init__(self, code: int, msg: Optional[str] = None) -> None:
|
def __init__(
|
||||||
|
self, code: int, msg: Optional[str] = None, log: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
||||||
self.code = code
|
self.code = code
|
||||||
|
self.log = log
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "Pebkac({}, {})".format(self.code, repr(self.args))
|
return "Pebkac({}, {})".format(self.code, repr(self.args))
|
||||||
|
|
|
@ -3797,7 +3797,7 @@ var fileman = (function () {
|
||||||
|
|
||||||
function rename_cb() {
|
function rename_cb() {
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fr_efail + msg);
|
toast.err(9, L.fr_efail + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3846,7 +3846,7 @@ var fileman = (function () {
|
||||||
}
|
}
|
||||||
function delete_cb() {
|
function delete_cb() {
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fd_err + msg);
|
toast.err(9, L.fd_err + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3967,7 +3967,7 @@ var fileman = (function () {
|
||||||
}
|
}
|
||||||
function paste_cb() {
|
function paste_cb() {
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fp_err + msg);
|
toast.err(9, L.fp_err + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -5300,10 +5300,7 @@ document.onkeydown = function (e) {
|
||||||
|
|
||||||
function xhr_search_results() {
|
function xhr_search_results() {
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
if (msg.indexOf('<pre>') === 0)
|
|
||||||
msg = msg.slice(5);
|
|
||||||
|
|
||||||
srch_msg(true, "http " + this.status + ": " + msg);
|
srch_msg(true, "http " + this.status + ": " + msg);
|
||||||
search_in_progress = 0;
|
search_in_progress = 0;
|
||||||
return;
|
return;
|
||||||
|
@ -7194,7 +7191,7 @@ var msel = (function () {
|
||||||
xhrchk(this, L.fd_xe1, L.fd_xe2);
|
xhrchk(this, L.fd_xe1, L.fd_xe2);
|
||||||
|
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
sf.textContent = 'error: ' + this.responseText;
|
sf.textContent = 'error: ' + unpre(this.responseText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7241,7 +7238,7 @@ var msel = (function () {
|
||||||
xhrchk(this, L.fsm_xe1, L.fsm_xe2);
|
xhrchk(this, L.fsm_xe1, L.fsm_xe2);
|
||||||
|
|
||||||
if (this.status < 200 || this.status > 201) {
|
if (this.status < 200 || this.status > 201) {
|
||||||
sf.textContent = 'error: ' + this.responseText;
|
sf.textContent = 'error: ' + unpre(this.responseText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7619,7 +7616,7 @@ var unpost = (function () {
|
||||||
|
|
||||||
function unpost_delete_cb() {
|
function unpost_delete_cb() {
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.un_derr + msg);
|
toast.err(9, L.un_derr + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2402,15 +2402,12 @@ function up2k_init(subtle) {
|
||||||
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
||||||
|
|
||||||
var err = "",
|
var err = "",
|
||||||
rsp = (xhr.responseText + ''),
|
rsp = unpre(this.responseText),
|
||||||
ofs = rsp.lastIndexOf('\nURL: ');
|
ofs = rsp.lastIndexOf('\nURL: ');
|
||||||
|
|
||||||
if (ofs !== -1)
|
if (ofs !== -1)
|
||||||
rsp = rsp.slice(0, ofs);
|
rsp = rsp.slice(0, ofs);
|
||||||
|
|
||||||
if (rsp.indexOf('<pre>') === 0)
|
|
||||||
rsp = rsp.slice(5);
|
|
||||||
|
|
||||||
if (rsp.indexOf('rate-limit ') !== -1) {
|
if (rsp.indexOf('rate-limit ') !== -1) {
|
||||||
var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
|
var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
|
||||||
console.log("rate-limit: " + penalty);
|
console.log("rate-limit: " + penalty);
|
||||||
|
@ -2536,7 +2533,7 @@ function up2k_init(subtle) {
|
||||||
cdr = t.size;
|
cdr = t.size;
|
||||||
|
|
||||||
var orz = function (xhr) {
|
var orz = function (xhr) {
|
||||||
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
|
||||||
if (txt.indexOf('upload blocked by x') + 1) {
|
if (txt.indexOf('upload blocked by x') + 1) {
|
||||||
apop(st.busy.upload, upt);
|
apop(st.busy.upload, upt);
|
||||||
apop(t.postlist, npart);
|
apop(t.postlist, npart);
|
||||||
|
|
|
@ -1356,6 +1356,11 @@ function lf2br(txt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function unpre(txt) {
|
||||||
|
return ('' + txt).replace(/^<pre>/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var toast = (function () {
|
var toast = (function () {
|
||||||
var r = {},
|
var r = {},
|
||||||
te = null,
|
te = null,
|
||||||
|
|
Loading…
Reference in a new issue