add checking for vulnerable version

This commit is contained in:
icxes 2026-02-26 00:17:29 +02:00
parent d3260b27a6
commit 1feed7837c
No known key found for this signature in database
6 changed files with 102 additions and 2 deletions

View file

@ -157,6 +157,10 @@ class BrokerMp(object):
elif dest == "cb_httpsrv_up":
self.hub.cb_httpsrv_up()
elif dest == "httpsrv.set_bad_ver":
for p in self.procs:
p.q_pend.put((0, dest, list(args)))
else:
raise Exception("what is " + str(dest))

View file

@ -61,6 +61,10 @@ class BrokerThr(BrokerCli):
self.httpsrv.set_netdevs(args[0])
return
if dest == "httpsrv.set_bad_ver":
self.httpsrv.set_bad_ver(args[0])
return
# new ipc invoking managed service in hub
obj = self.hub
for node in dest.split("."):

View file

@ -155,7 +155,8 @@ ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
BADXFF = " due to dangerous misconfiguration (the http-header specified by --xff-hdr was received from an untrusted reverse-proxy)"
BADXFF2 = ". Some copyparty features are now disabled as a safety measure.\n\n\n"
BADXFP = ', or change the copyparty global-option "xf-proto" to another header-name to read this value from. Alternatively, if your reverseproxy is not able to provide a header similar to "X-Forwarded-Proto", then you must tell copyparty which protocol to assume; either "--xf-proto-fb=http" or "--xf-proto-fb=https"'
BADXFFB = "<b>NOTE: serverlog has a message regarding your reverse-proxy config</b>"
BADXFFB = "<div class='box-warning'>NOTE: serverlog has a message regarding your reverse-proxy config</div>"
BADVER = "<div class='box-warning'>The version of copyparty currently active has a known vulnerability <a class='unbox' href='https://github.com/9001/copyparty/security'>(more info)</a> that has been fixed; please update to the latest version. This message is only visible to users with admin (a/A) permissions.</div>"
H_CONN_KEEPALIVE = "Connection: Keep-Alive"
H_CONN_CLOSE = "Connection: Close"
@ -5624,7 +5625,8 @@ class HttpCli(object):
no304=self.no304(),
k304vis=self.args.k304 > 0,
no304vis=self.args.no304 > 0,
msg=BADXFFB if hasattr(self, "bad_xff") else "",
msg=(BADXFFB if not hasattr(self, "bad_xff") else "")
+ (BADVER if self.conn.hsrv.bad_ver and self.can_admin else ""),
ver=S_VERSION if show_ver else "",
chpw=self.args.chpw and self.uname != "*",
ahttps="" if self.is_https else "https://" + self.host + self.req,

View file

@ -119,6 +119,7 @@ class HttpSrv(object):
socket.setdefaulttimeout(120)
self.t0 = time.time()
self.bad_ver = False
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
self.magician = Magician()
self.nm = NetMap([], [])
@ -232,6 +233,9 @@ class HttpSrv(object):
self.th_cfg: dict[str, set[str]] = {}
Daemon(self.post_init, "hsrv-init2")
def set_bad_ver(self, val: bool) -> None:
self.bad_ver = val
def post_init(self) -> None:
try:
x = self.broker.ask("thumbsrv.getcfg")

View file

@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
import argparse
import atexit
import errno
import json
import logging
import os
import re
@ -27,6 +28,7 @@ if True: # pylint: disable=using-constant-test
from typing import Any, Optional, Union
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
from .__version__ import S_VERSION
from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who
from .bos import bos
from .cert import ensure_cert
@ -75,6 +77,7 @@ from .util import (
mp,
odfusion,
pybin,
read_utf8,
start_log_thrs,
start_stackmon,
termsize,
@ -95,11 +98,19 @@ if TYPE_CHECKING:
if PY2:
range = xrange # type: ignore
if PY2:
from urllib2 import Request, urlopen
else:
from urllib.request import Request, urlopen
VER_IDP_DB = 1
VER_SESSION_DB = 1
VER_SHARES_DB = 2
VULN_CHECK_URL = "https://api.github.com/repos/9001/copyparty/security-advisories"
VULN_CHECK_INTERVAL = 86400
class SvcHub(object):
"""
@ -1382,6 +1393,7 @@ class SvcHub(object):
Daemon(self.tcpsrv.netmon, "netmon")
Daemon(self.thr_httpsrv_up, "sig-hsrv-up2")
Daemon(self.check_ver, "ver-chk")
sigs = [signal.SIGINT, signal.SIGTERM]
if not ANYWIN:
@ -1778,3 +1790,61 @@ class SvcHub(object):
zb = gzip.compress(zb)
zs = ub64enc(zb).decode("ascii")
self.log("stacks", zs)
def parse_version(self, ver: str) -> tuple:
match = re.search(r'[\d.]+', ver)
if not match:
return ()
clean = match.group(0).strip('.')
return tuple(int(x) for x in clean.split("."))
def get_vuln_cache_path(self) -> str:
return os.path.join(self.E.cfg, "vuln_advisory.json")
def check_ver(self) -> None:
ver_cpp = self.parse_version(S_VERSION)
while not self.stopping:
fpath = self.get_vuln_cache_path()
data = None
try:
mtime = os.path.getmtime(fpath)
if time.time() - mtime < VULN_CHECK_INTERVAL:
data = read_utf8(None, fpath, True)
except Exception as e:
self.log("ver-chk", "no vulnerability advisory cache found; {}".format(e))
if not data:
try:
req = Request(VULN_CHECK_URL)
with urlopen(req, timeout=30) as f:
data = f.read().decode("utf-8")
with open(fpath, "wb") as f:
f.write(data.encode("utf-8"))
except Exception as e:
self.log("ver-chk", "failed to fetch vulnerability advisory; {}".format(e))
if data:
try:
advisories = json.loads(data)
fixes = (
self.parse_version(vuln.get("patched_versions"))
for adv in advisories
for vuln in adv.get("vulnerabilities", [])
if vuln.get("patched_versions")
)
newest_fix = max(fixes, default=None)
if newest_fix and ver_cpp < newest_fix:
self.broker.say("httpsrv.set_bad_ver", True)
except Exception as e:
self.log("ver-chk", "failed to process vulnerability advisory; {}".format(e))
for _ in range(VULN_CHECK_INTERVAL):
if self.stopping:
return
time.sleep(1)

View file

@ -257,3 +257,19 @@ html.bz {
html.bz .vols img {
filter: sepia(0.8) hue-rotate(180deg);
}
.box-warning {
background: #804;
border-bottom: 1px solid #c28;
padding: .75em;
text-align: center;
border-radius: .2em;
margin-top: 0em;
}
.box-warning + .box-warning {
margin-top: .5em;
}
.unbox {
border: unset !important;
padding: unset !important;
margin: unset !important;
}