0.19.0 #1502
mickem
announced in
Announcements
0.19.0
#1502
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
A real
nscp testprompt, a crash fix for service filters, and a collectd client that does what its settings say0.19.0 gives the interactive console a proper prompt — line editing, history,
tab completion and highlighting — and fixes a heap-corruption crash that took
the whole agent down whenever a
check_servicefilter matched nothing (#1499).A whole-codebase security review closed three findings: a client module's
configured credential could be sent to a caller-chosen host, REST script
uploads were staged at a predictable path, and a junction defeated the
modern-layout lockdown of
%ProgramData%\NSClient++. The collectd client wasreworked end to end — host names resolve,
timeoutandretriesare honoured,failed sends are reported, datagrams are sized correctly and a multicast target
no longer fans out over every local interface.
Alongside that, the WEB server's authentication limiter escalates against
rapid-fire guessing, NRDP warns about an unverified TLS link, Icinga honours a
base path in the target address, every documented query now has prose and
captured samples, and the Upgrading and Security notices pages are
assembled from one file per note with a module/version/action filter.
✨ Highlights
nscp testis a real prompt. On a terminal you get line editing,persistent per-user history, position-aware tab completion against the
command registry, hints, and highlighting that turns an unknown query or
module name red before you press enter. Log messages redraw around the line
you are typing instead of landing in the middle of it. Piping commands in now
works on Windows, and an exhausted stdin no longer spins a core at 100%.
(Give
nscp testa real prompt, and fix the console log lag #1488)check_servicefilter that matched no service no longer kills theagent.
check_service "filter=name = 'nosuchservice'"— or a filter thatmerely missed on case — terminated
nscpwith exception code0xC0000374and no result. Both
check_servicekeywords andcheck_logfile'scolumn()now answer the documented empty-result contract, and every optional read in
the tree goes through
.value()so a future miss is a reported error, not awrite to freed memory. (check_service terminates the agent process when the filter matches no services (0.18.1) #1499)
host=,port=andaddress=moved a submission's destination while the target's configuredpasswordortokencame along, so any holder ofqueries.executecouldhave the agent post the NRDP token, the Icinga login, the SMTP login or the
NSCA password to a host of their choosing. That combination is refused now;
target=also works for queries, andallow host override = truerestoresthe old behaviour per target. (Guard client credentials and secure script uploads #1492)
PUT /api/v2/scripts/…staged theupload at
${temp}/<name>, where a local user could plant a file of the samename and have it imported as a command; it is staged in a randomly named,
owner-only file now. On the opt-in modern layout, a pre-created junction at
%ProgramData%\NSClient++had the lockdown secure the junction's target;reparse points are refused and the installer, the migration and service start
all fail on one. (Guard client credentials and secure script uploads #1492)
host name threw on every metrics cycle;
timeoutandretrieswere read andignored; a failed send looked exactly like a delivered one; a value list of a
few hundred entries overflowed the 1452-byte datagram the receiver reads; and
a multicast target sent a copy through every local interface, DMZ and guest
NICs included. All fixed, with a new per-target
multicast interfacesetting(
auto, the default,all, or a list of local addresses). (CollectdClient: fix the open findings from the collectd security review #1494)after ten failures let an attacker resume at a steady rate forever — about
14 000 guesses a day per address. Each further block now doubles up to an
hour, but only for a run of failures that burned the whole budget at machine
speed, so a client retrying a stale password behind NAT cannot lock out
everyone sharing its address. (Add exponential backoff to WEB auth rate limiter and fix Icinga base path #1493)
nscp testshows log lines as they happen instead of when you press a key, and a
redirected or supervised console streams.
--no-stderrand theonelineformat finally take effect. (Give
nscp testa real prompt, and fix the console log lag #1488)against a running agent, with the errors the capture turned up corrected in
the text. The Upgrading and Security notices pages are now built from one
file per note, with a filter for the version you come from, the modules you
run and whether a note needs action. (docs: give every documented query a description and samples, and fix the dead CheckSystemUnix docs #1480, docs: assemble the Upgrading page from one file per note, with a module filter #1482)
🔍 Detailed changes
🖥️ CommandClient —
nscp testgets a real promptThe interactive console was a poll loop around
std::getline: no lineediting, no history, no colour. When both stdin and stdout are a terminal it
now runs on replxx, vendored under
libs/replxx/(byte-identical to upstream so provenance can be diffed; nonetwork needed at build time).
nscp testis routinely killed).%APPDATA%\NSClient++\console-history.txton Windows,$XDG_STATE_HOME/nscp/console-historyor~/.nscp_historyelsewhere, created0600on POSIX.desc, the query's own parameter names asname=once you are typing arguments.load/enableoffer the modules that are not yet loaded or enabled,unload/disablethe ones that are.Commands typed at a prompt can carry credentials, so a new
[/settings/cli]section controls what is kept:
history size = 0turns persistence off,history filerelocates it,colordisables colour.The first
load <tab>of a session pauses while the core scans the moduledirectory; it is done once per process.
helpis now generated from the samevocabulary as the prompt, so it lists all sixteen built-in verbs instead of the
eight it had drifted to, and an empty
Performance data:line is no longerprinted after every result that has none. See
Test mode.
With stdin not a terminal nothing changes — no prompt, no history, no
colour — except three fixes: piping commands in now works on Windows (the
readiness check used a console-only API and silently ignored a file or pipe),
an exhausted stdin parks the loop instead of spinning at 100% CPU on POSIX,
and end of input is no longer treated as a reason to exit, which is how the
agent is normally started under a supervisor.
To make this possible a module can now take the console over: the new
NSAPISetLogOptioncore API accepts the same strings as the--logswitch,and the prompt calls
set_log_option("no-console")while it owns theterminal. Fixing the one-way
consoleflag exposed thatonelineandno-std-errwere being forwarded to the log level parser, rejected withInvalid log level: no-std-err, and never applied. Both reach the log drivernow.
🔧 Core — the console log is flushed
The console log backend installed a 64 KB buffer on standard output and
nothing ever emptied it. MSVC's stream honours that buffer, so on Windows log
output sat there until something else flushed the stream — in
nscp testthat was reading the next line of input, which is why the log appeared to
catch up only when you pressed a key. A redirected console (
nscp test > log.txt, a container, a supervisor) looked mute until 64 KB had built up orthe process exited. Every message is flushed as it is written now.
🛡️ Filters — an empty filter result no longer corrupts the heap
When nothing matches a filter, the framework re-evaluates the warning and
critical expressions with no object bound to the evaluation context, so that an
expression which also reads the summary (
… or count = 0) still reaches averdict.
check_servicedefaults tonot state_is_perfect()andnot state_is_ok(), and both read the service straight off the context withoutchecking one was there. That dereferenced an empty optional, resurrecting a
destroyed
shared_ptrcontrol block out of the vacated storage; the copy takenof it wrote to freed heap memory, and the process died somewhere unrelated with
0xC0000374and no usable stack.debug=truemasked it, because with debug onthe context keeps a copy of every object and the stray write lands on live
memory.
check_logfile'scolumn()keyword had the same unguarded access.Both keywords now report an unresolved value when no object is bound, as the
built-in keywords already did, and the check returns
UNKNOWN: No services found. The accessor underneath throws a filter error instead of reading thevacated storage. Any host past
allowed hostscould trigger this over NRPEwith
allow arguments = true, and any authenticated REST client could; theUnix implementation already had the guard and was never affected. (#1499)
As a follow-up, all 214 optional dereferences across 67 files were converted
to
.value(), including the ones sitting under anif (opt)guard: the guardis what a later edit moves or deletes, and uniformity is what makes the rule
checkable.
.value()throwsbad_optional_access, which thecatcharoundevery filter evaluation turns into a reported error on the check.
🔐 Client modules — credentials pinned to their target
The shared client parser loads the module's
defaulttarget — credentialincluded — and then applies the request's arguments on top.
host=,port=and
address=moved the destination while the credential stayed, sohad the agent post the configured NRDP token to the attacker. Both seeded REST
roles carry
queries.executeand the permission policy is off by default, so achecks-only REST user was enough; over NRPE it needed
allow arguments = true.Affected are the modules whose targets carry a credential: NSCA, NSCA-NG,
NRDP, Icinga, SMTP and NSCP.
A request that moves the destination away from the target's configured address
is now refused when the credential that would travel is the target's own. The
guard decides on two facts — the resolved destination differs from the one the
target configured, and at least one credential still in the container is the
target's rather than the request's — so a target with no credential, a request
that supplies its own
password=/token=, and a request that does not movethe destination are all unaffected, and a destination moved through a header
host entry is caught too.
allow host override = trueon a target restores theold behaviour explicitly.
target=now selects a configured target on the query path as well. It wasonly ever applied when a command ran as an exec; as a query — which is what a
REST or NRPE caller gets for
check_*andsubmit_*— it was accepted andsilently ignored, so the one remedy the refusal recommends did not work where
the refusal is most likely to be met.
🔒 WEBServer — script uploads are staged privately
PUT /api/v2/scripts/…(admin only) wrote the body to${temp}/<name>—/tmp, orC:\Windows\Tempfor a SYSTEM service — with an uncheckedtruncating write, then imported it as a command. A local user who created that
file first won a race against the copy, or won outright where the service's
overwrite was refused and the failure ignored, and the planted content then ran
as the service account. Stock DEB/RPM installs were not exploitable for code
execution (the service runs as
nsclientand the script root is root-owned).Uploads now go to a randomly named file, created exclusively and owner-only,
never through a symlink, with every write checked and the file removed once
consumed; a staging failure is reported as HTTP 500 instead of importing
whatever was on disk.
🔒 Windows modern layout — the shared folder must be a real directory
The opt-in, experimental
LAYOUT=moderninstall keepsnsclient.ini, thefleet private key and the TLS material in
%ProgramData%\NSClient++and locksthe folder down by taking ownership and replacing its DACL. Every step was
path-based, and a standard user can create a junction under that name before
the installer first runs: the owner and DACL were applied to the junction's
target while the link stayed theirs to swap for a real folder with a crafted
nsclient.ini, which the next service start loaded as SYSTEM.Ownership and the DACL are now applied through a handle opened on the entry
itself (
FILE_FLAG_OPEN_REPARSE_POINT, one open per operation asking only forthe rights that operation needs), and anything that is not a plain directory
is refused — by the installer, by
nscp settings --migrate-layout modern, andat service start. Legacy installs are untouched.
📡 CollectdClient — the sender reworked
make_address()accepts IP literals only), so metrics silently never left.timeoutandretrieswere read into the connection and never used; the send was asynchronous and discarded its error code, so an unreachable target, a full socket buffer or an oversized datagram looked exactly like a delivered packet.retriestimes (default 3, 20 ms apart); the whole send, name resolution included, runs undertimeout(default 30 s,0for no limit); failures are logged once per distinct message. A datagram the receiver already has is never sent twice.239.192.74.66:25826) sent a copy of every datagram through every local interface of the matching family — unauthenticated cleartext host name, CPU, memory, uptime and process counts on every attached segment. On a Debian-style host whose name maps to127.0.1.1the enumeration yielded loopback only, so nothing left the machine at all.multicast interface:auto(default) sends one copy through the interface the routing table picks;allrestores the fan-out; a comma-separated list of local IP addresses sends through exactly those, with an unusable entry reported and skipped and a wholly unusable list sending nothing rather than falling back to the default route.sentcounted datagram × socket whilefailedcounted payloads, so the "not sent" count underflowed to about 1.8 × 10¹⁹ on a multi-interface target.sent + failedaccounts for every payload.The UDP delivery half moved out of the module into
net/collectd/, where itis unit-tested against a real loopback socket, and the integration suite gained
a target named
localhost.🔒 WEBServer — escalating block on repeated authentication failures
The per-IP limiter blocked a client for a fixed
auth rate limit block seconds(default 60) afterauth rate limit max failures(default 10)consecutive failures and then reset its counter — roughly 14 000 guesses a day
per source address, indefinitely, against Basic auth, the
passwordheader andthe legacy
?password=formcheck_nscp_apiuses. Each consecutive block fromthe same IP now doubles the wait, up to an hour; a configured block already
longer than that is used as configured. The escalation resets on a successful
authentication or after an hour of quiet.
Only a run of failures that burned the whole budget faster than one attempt
every two seconds escalates. The limiter keys on the socket peer, so behind
NAT or a reverse proxy every client shares one address, and a single
monitoring client retrying a stale password on a schedule must not be able to
ratchet that address up to the ceiling. Bearer /
?TOKEN=session tokens arenot metered: they are 256-bit random values, and counting an expired one
against the limit would let a client with a stale session lock its own address
out. This is defence in depth on top of PBKDF2 and the uniform 403; IP rotation
remains out of scope.
🔒 NRDPClient — an unverified TLS link is logged
An
httpssubmission whoseverify modecarries no peer-verifying tokensends the token — a shared secret — to whichever server answers. The module now
logs that, naming the endpoint, once per target for the life of the process
(the Icinga client already did; a first cut logged on every submission, which
at a 60-second schedule is 1 440 lines a day). The connection itself is
unchanged. The
verify modehelp text is corrected in the same pass: itrecommended
nonefor self-signed certificates and listedclient-once,workaroundsandsingle, which the client-side parser rejects. Usepeer-certwithcapointing at the certificate instead.🔧 IcingaClient — a base path in the target address is honoured
The path of a target
address(https://proxy.example.com/icinga/) wasparsed into a field nothing read, so every call went to
/v1/…on the host andan Icinga 2 master published under a reverse-proxy subpath could not be
reached. The prefix is normalised once (a doubled leading slash collapses to
one, no trailing slash) and prepended to every API path, for the submission and
the ensure-objects calls alike. Addresses without a path are unchanged.
📚 Documentation
they come from, ticks the modules they run, and can restrict to security-relevant
or action-needing notes; the selection is remembered and mirrored into the query string.
kept and how to turn it off, non-interactive behaviour, and the
stop-the-service dance on both platforms.
overrides carried copyright years from later releases than the copies
vendored here. (fix(reuse): match the bundled headers' copyright years #1486)
check_servicefilter that matched no service no longer kills theagent. Nothing to configure. If you worked around it with
service=<exact name>and nofilter=, service name patterns are usable again.credential to a destination the request names. A
submit_*/check_*call that passes
host=,port=oraddress=against a target with apasswordortoken, without supplying the credential itself, now failswith an error naming the target. Pass the credential with the request,
configure each server as its own target and select it with
target=(whichnow works for queries too), or set
allow host override = trueon thetarget. Targets without a credential are unaffected.
configuration change; a staging failure is now an HTTP 500.
directory. A
%ProgramData%\NSClient++that is a junction or symboliclink is refused by the installer, by
nscp settings --migrate-layout modern, and at service start. Relocate the folder with a[paths]overridein
boot.iniinstead. Legacy installs are unaffected.of them. If you depend on a multicast target reaching several segments,
set
multicast interface = allon it, or list the local addresses to sendthrough. Unicast targets ignore the setting.
longer each time. Nothing to do on a default install. A probe that
deliberately authenticates with bad credentials will be blocked for longer;
auth rate limit max failures = 0still disables the limiter for a testharness.
httpslink now says so in thelog, once per target. If it names a target you expected to be verified,
set
verify mode = peer(orpeer-certwith aca).nscp testwrites a per-user history file. Commands typed at theprompt can carry credentials; set
history size = 0under[/settings/cli]to keep nothing on disk. With stdin not a terminal nothing changes, except
that piping commands in now works on Windows and end of input no longer
exits.
--no-stderrand theonelinelog format now take effect. Nothing to do unless you were workingaround either.
target=now selects a configured target on the query path. A RESTor NRPE query that passed
target=and relied on reachingdefaultanywaywill now reach the target it named.
timeoutandretriesand report failedsends. Nothing to do unless you set a large
retrieson a collectdtarget — it now costs real time, bounded by
timeout(default 30 s;timeout = 0for no limit).address carries a path that is not a subpath of the API, remove it.
Full detail on the security items lives in Security notices; the operator
actions are mirrored on Upgrading.
Full Changelog: 0.18.1...0.19.0
This discussion was created from the release 0.19.0.
All reactions