VULNARCHIVE

[0day-rubbish] Synametrics Syncrify 6.2 build 1178 authenticated PublicLink chunk-upload path traversal to /etc/cron.d and root command execution (7.5)

disclosure via Fulldisclosure · · Full Disclosure

Original source

0day Rubbish Research Team is publicly disclosing a vulnerability in Synametrics
Syncrify 6.2 build 1178, the closed-source incremental backup and
file-replication server from Synametrics Technologies, Inc. (United States). The
product is written in Java, distributed for Linux as a tarball archive, and
served by an embedded Tomcat 9.0.113 through the vendor's in-house sradef
framework, whose single front controller routes every request as
/app?operation=<op>&st=<step> to a handler registered in WorkerMappings.xml.

Type and root cause. Path traversal, CWE-22 primary, with CWE-269 improper
privilege management and CWE-78 OS command injection as contributing
classifications that describe the deployment shape converting a file write into
an execution rather than a defect inside the application. The PublicLink
handler, operation=pb, implements the server's PublicLink client protocol. Its
chunked-upload branch, selected by st=u, reads a length-prefixed binary frame
straight from request.getInputStream(), which means the server's parameter
parsing, form decoding and any framework-level parameter validation are all
bypassed by construction: the field that decides the write destination is not a
parameter. At PublicLink.java:70-110 the decompiled method reads four big-endian
int32 values and one length-prefixed UTF-8 string, then does this:

    int protocol    = readInt(in);     // 4-byte big-endian
    int chunkNumber = readInt(in);     // read, then not used to bound
                                       // or place the write
    int totalChunks = readInt(in);     // read, then not used either
    int size        = readInt(in);     // declared length of the body
    String filename = readString(in);  // [int32 len][UTF-8 bytes],
                                       // attacker-controlled

    File base   = E();                 // this user's PublicLinks folder
    File target = new File(base, filename);
    if (!target.getParentFile().exists()) {
        target.getParentFile().mkdirs();
    }
    BufferedOutputStream out = new BufferedOutputStream(
        new FileOutputStream(target, true));   // append mode
    byte[] buf = new byte[size];
    readFully(buf);
    out.write(buf);
    out.close();
    return 0;                          // 0 means success to the caller

new File(base, filename) is reached with no rejection of "..", no rejection of
path separators, no base-name extraction and no canonical containment check.
base is /opt/Syncrify/PublicLinks/<user>/<generated-folder>, five components
below the filesystem root, so a chunk name of ../../../../../etc/cron.d/<name>
leaves the per-user PublicLinks folder and lands a cron fragment, creating any
missing parent directory on the way because of the mkdirs() call. The branch
returns 0, f() at PublicLink.java:127 converts that into the client reply
code="1" with <payload>0</payload>, and the host cron daemon then reads the
fragment as a root-authored job definition and runs its command field through a
shell on the next minute boundary. The application itself never constructs,
quotes or invokes a command line, and every execution site in the product uses
an argument vector with no shell, so this could not have been fixed by argument
hygiene at the application layer; the execution happens one component later.

The identity reached was observed rather than inferred, and it is uid=0(root)
gid=0(root) groups=0(root). Two independent artifacts establish that the writer
process held uid 0: the fragment landed as -rw-r--r-- 1 root root at mode 0644,
and a file's owner after creation is the effective uid of the process that
created it; and the scheduled job's own output reported uid 0, the fragment
naming root in its user field, which a scheduler will not honour for a job file
it does not already trust.

What makes this an oversight rather than a design decision is the asymmetry
with the rest of the same tree. Three sibling families in the same product,
written against the same client protocol, check the same kind of field and
reject the same kind of content: the profile-upload, profile-fetch and
file-splitter handlers refuse names containing path separators by testing
character codes with indexOf(47) and indexOf(92); the multipart upload path
collapses the client-supplied name through getName() of a constructed File,
which discards every directory component; and the download path runs the name
through indexOf("..") and answers 404 on a match. The chunk-upload branch calls
none of them. When one member of a family omits the defense all of its siblings
implement, the economical explanation is that the check was not written rather
than that it lives somewhere else. There is also no decoding step between the
socket and the constructor on this branch, so no encoding trick is involved and
none is needed: what the client puts in the frame is what the path is built
from.

How confirmation works, and what was deliberately not reproduced. Confirmation
is by host-side artifacts, not by the HTTP reply, and that distinction matters.
The reply is a success oracle, so an attacker learns from the network alone that
a write was accepted and can iterate on naming from there, but a successful
reply is not proof of a successful destination: the code never inspects the
resolved path, so a wrong traversal depth still yields code="1" and simply
writes somewhere else with intermediate directories created as needed. The
read-back of the marker file is the only sufficient signal. Four controls were
exercised and the published tool automates one of them. A traversing name and a
non-traversing name were driven through identical authentication, identical
framing and the identical route in the same session; the benign write was
accepted and landed inside the user's own PublicLinks folder, and the assertion
that neither the cron fragment path nor the marker path existed is what isolates
the traversal as the cause of the outcome and of nothing else. A sibling-handler
control confirmed at the Java level that the multipart family's getName()
collapse, applied to a traversing name against the same PublicLinks base,
returns the bare final component and keeps the write inside the root. A dispatch
control built against st=f instead reached a different void E() overload at
PublicLink.java:951 that parses a request parameter named ed as a Long and
performed no write anywhere; that control is what fixed the sink to this branch
rather than to the handler in general, since an initial static reading had
attributed the upload to st=f and the request built on that reading produced no
file at all. Authentication controls established that the credential gate is
live. The published advisory reproduces the identity string and the frame field
lengths derived from the field values, but deliberately not the run's own byte
capture, which was not preserved; not the generated PublicLinks folder name,
which embeds the run's timestamp; not the credential pair, which are mandatory
tool arguments with no default; and not the per-run fragment name or the marker
path. The payload was an identity capture into a marker file. No repository data
was read, no product file was modified or deleted, and the fragment and the
marker were removed after verification together with stopping the service.

Scoring. One score is published: 7.5 High,
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H. The arithmetic is shown so any
reader can reproduce it with the CVSS 3.1 base algorithm and its
round-up-to-the-tenth rule: ISS = 1 - (0.44 x 0.44 x 0.44) = 0.914816; Impact
for S:U = 6.42 x 0.914816 = 5.873119; Exploitability = 8.22 x 0.85 x 0.44 x
0.62 x 0.85 = 1.620146; Base = Roundup(5.873119 + 1.620146) =
Roundup(7.493265) = 7.5. AV:N is the honest classification of the component
because the vulnerable component is an HTTP handler on a network-bound servlet
connector and the credential it accepts exists precisely so that remote backup
clients can use it; the deployment-dependent exposure of that listener is priced
in attack complexity rather than smuggled into the attack vector. AC:H carries
two conditions outside the attacker's control: that the listener carrying this
route be reachable from the attacker's position, and that the host run a
scheduler that scans /etc/cron.d. PR:L records that the least privileged
principal proven sufficient is an authenticated backup user, the product's
lowest-trust account type. Scope is Unchanged, for the identity reason given
below.

A conditional reading of 8.8 High, CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,
is published alongside it and is labelled conditional everywhere it appears. It
applies only where the listener carrying /app?operation= is reachable from the
attacker's network position, which is the normal operating mode for a backup
server whose clients push data to it remotely, and it removes the first of the
two AC:H conditions by assuming that deployment posture. It computes as
Exploitability = 8.22 x 0.85 x 0.77 x 0.62 x 0.85 = 2.835255, Base =
Roundup(5.873119 + 2.835255) = Roundup(8.708374) = 8.8. The published score is
7.5 and never 8.8; a reader whose deployment keeps the portal on a management
network should read the 7.5.

Two further readings are published as explicitly rejected, both with their
arithmetic, so that a reader who disagrees with either call can see exactly what
it is worth rather than take our word for it. The changed-scope variant of the
same metrics, 8.6 High,
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H, is rejected because the identity
reached is the same root identity the server process already holds on the same
host, so no component authority boundary is crossed by the outcome: the server
process is root over this host by construction of the deployment, and the
compromised scheduler and the compromised server sit inside one authority
domain. Its arithmetic: Impact for S:C = 7.52 x (0.914816 - 0.029) - 3.25 x
(0.914816 x 0.9731 - 0.02)^13 = 6.661336 - 0.533310 = 6.128026; Exploitability
with PR at the S:C value of 0.68 = 8.22 x 0.85 x 0.44 x 0.68 x 0.85 = 1.776934;
Base = Roundup(1.08 x (6.128026 + 1.776934)) = Roundup(8.537357) = 8.6. The
privilege gain in this finding is real and substantial, and it is expressed by
the metrics meant to carry it, PR:L for the low-trust principal and C:H/I:H/A:H
for the totality of what root on this host controls; Scope is not the vehicle
for expressing privilege distance and using it that way would double-count the
same fact.

The second rejected reading is the one we want readers to notice, because it is
the one that could most easily be misused in our favour. Treating the loopback
binding we observed in our own testbed as though it were the shipped default
yields 7.8 High, CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H: Exploitability =
8.22 x 0.55 x 0.77 x 0.62 x 0.85 = 1.834577, Base = Roundup(5.873119 +
1.834577) = Roundup(7.707696) = 7.8. We reject it, and rejecting it lowers the
number we publish by 0.3 rather than raising it. That rejected local-vector
reading of 7.8 is higher than the 7.5 we publish at AV:N. So the rejection is
not a scoring maneuver and cannot be read as one: choosing AV:N with AC:H is the
conservative reading on this finding, and a reader who disagrees with us about
the factory default of that binding ends up with a larger figure than ours, not
a smaller one. The advisory says this itself, in those terms. The reason for the
rejection is evidential rather than convenient: that loopback binding was
applied by us, in the configuration, before any testing began, so that the host
carried no public exposure while research was in progress, and we hold no
shipped artifact, no configuration template, no installer script excerpt and no
documentation quotation, that states the factory value of that binding in either
direction. Not that it binds publicly by default, and not that it binds loopback
by default: the premise is unestablished by artifact, which is exactly why the
attack complexity is High and exactly why the 8.8 figure above is conditional.

Authentication. The finding is post-authentication and no unauthenticated
reachability is claimed. Nothing in this advisory is rated PR:N, and no
unauthenticated reading is offered as a hypothetical. The handler is registered
at secLevel=0 in WorkerMappings.xml, which in this framework means no
administrator session is required and is correct by design for client-protocol
operations, so the framework lets an anonymous request reach it and the only
decision left is the handler's own authentication call. That call validates a
credential from the backup-user store, config/UserMappings.xml via
syncrify.users.DefaultUserManager, and not from the web-administrator store,
config/umap.dat via sradef.config.SecurityManager. The two stores are
independent and must not be conflated. On success the method writes the session
attribute pbUser, which is the state the upload branch depends on to choose whose
base directory to use, and it is written only after the credential check returns
non-null, so a caller cannot manufacture it by forging a cookie value: session
contents are server-side. The credential lookup contains a branch that returns an
authenticated holder with no password comparison at all, and it is not reachable
from this HTTP surface. The reason is worth being precise about, because it was
confirmed dynamically rather than trusted from the decompilation: the handler
tests whether either URL parameter is absent before the lookup is ever called,
and the two gates produce distinguishable reply codes. A correct pair was
accepted with code="1" and a session cookie; a wrong password was rejected with
code="-2", so the comparison is live; an absent password was rejected with
code="-1", and an absent user with code="-1", so the earlier gate fired and the
lookup was never entered. Had a missing password reached the lookup, that branch
would have returned a holder and the reply would have been an acceptance rather
than the error code belonging to the earlier gate. The session identifier
arrives as an ordinary container cookie and the upload request must present it,
but there is no timing constraint between the two requests: the session is the
only coupling, no race to win and no window to hit, which is why the chain is
reliable rather than probabilistic.

The unauthenticated surface was exhausted before the post-authentication result
was accepted, and the enumeration is the content of that claim. Thirteen
operations answer anonymous requests with real content and no credential check;
each was probed without credentials and then traced into its handler code for a
sink, and all thirteen are sink-free, returning a build number, a login page, a
footer, a chart, a captcha image, an about page, click tracking, a newsletter
page, a client download and similar read-only or static content. The only
user-controlled parameter among them that reaches a function at all is the
address supplied to the password-recovery operation, and it is used as a lookup
key rather than as a destination, with the recovery message going to the address
already registered for that account, so it is an enumeration weakness under
CWE-203 and not a write or execution route. The remaining level-0 operations are
not anonymous: their handlers authenticate internally against the backup-user
store and answer code="-2" without valid credentials, and treating level 0 as
unauthenticated would overstate this product's exposure by roughly a factor of
three. All forty-seven level-0 operations were probed during enumeration on the
same listener that carries the vulnerable route, including the client-protocol
operations backup clients use. Seventeen Runtime.exec and ProcessBuilder call
sites exist in the tree and none is reachable anonymously: eleven belong to boot,
client, JRE-update, file-attribute or generic helper code with no network caller,
two sit in network handlers that are administrator-level with hardcoded argument
vectors and no injectable element, and the one structurally interesting site, a
backup job-hook executor, is blocked four separate ways, its script path being an
administrator-set value that is empty by default and returns immediately, its
executable needing to exist already, its execution being an argument vector with
no shell, and its substituted client-address value coming from the container's
peer address rather than from a client-supplied field so it cannot be forged
over TCP. Eight deserialization candidates were examined and none has a network
caller; the in-house binary protocol is length-prefixed field parsing with no
object stream in it, and the classpath carries no gadget library, no
commons-collections, Jackson, XStream, Kryo or Hessian, so there is no chain
even if a sink were reached. No template engine and no naming-service lookup
exist anywhere in the tree, and the only user-influenced outbound fetch belongs
to an administrator-level operation. Two statements belong with that enumeration.
It is not a proof that the unauthenticated surface is safe; it is a report that
no route to a write, execution or traversal sink could be found within the
operations identified, using three independent enumeration directions. And the
sweep did surface weaknesses that are real and are deliberately not claimed here
and not folded into any metric: a filter-block bypass that exposes debug pages
shipped in the web root via a manualOverride parameter, yielding a directory
listing and an environment-variable dump; the password-recovery enumeration
above; globally disabled CSRF prevention, which is irrelevant to this chain
because the chain needs credentials the attacker holds rather than a victim's
browser; vendor-default credentials for an embedded database that is configured
but disabled and was not listening; and a default trial licence serial whose
effect on user provisioning was left unresolved and which the chain does not
need.

One fact in this product is a trap for scoring and we did not fall into it. The
first-run install wizard creates a web-administrator account with the password
letmein, recorded in the code at SimpleLoginHandler.java:271, and the first
administrator login is forced through a password change before the
administration screens are served. A credential that expires on first use is not
a route to unauthenticated access. It is deployment-dependent, relevant only
where an operator skipped or defeated the forced change, and in any case it
points at the administrator store rather than the store that gates this sink. An
attacker holding it would still have to provision a backup user through a
licence-gated administrative operation before reaching this chain, which makes it
a strictly worse starting position than simply holding the backup-user credential
this product exists to hand out to client machines. Accordingly the factory
administration password is documented as a contributing fact, is used in no
vector, raises no metric, and no conditional reading in the advisory assumes it
has not been rotated.

Impact. Arbitrary file write with an attacker-chosen path and attacker-chosen
bytes: the chunk branch writes exactly the size bytes it reads from the request
body to whatever absolute path the chunk name resolves to, in append mode,
creating missing parent directories on the way. The /etc/cron.d destination is
the escalation path demonstrated, not the limit of the primitive. On the
verified deployment shape, where the server process holds uid 0 and a cron
daemon is active, the written fragment is evaluated by the host scheduler as a
root-authored job definition and the command runs with the full authority of the
identity the server already holds. This is backup infrastructure, and that is
what gives the number its weight. Root on this server means read access to the
whole repository tree, every backup belonging to every configured client, which
for a multi-tenant server is a confidentiality breach across all of them rather
than within one account, and it means read access to the server's own
configuration, which holds administrator and backup-user credential material and
transport secrets; the backup-user store compares passwords in plaintext or
stores them under a reversible permutation transform, so a read of that file
hands over credentials directly. It also means integrity control over the backup
chain: repository contents, version and retention metadata and the server's own
binaries can all be altered in place, and a modified backup set is a failure
that stays invisible until the moment the data is needed. Persistence is
structural rather than incidental, because a cron fragment runs on every minute
boundary until someone deletes it, so one successful chunk write buys recurring
execution rather than a single shell, and it survives restarts of both the
service and the host. Availability follows the same authority: the service can
be stopped, the host rebooted or hung, repositories deleted or truncated, and
the scheduler used to keep doing so after remediation of the application alone.
The privilege boundary that fails here is inside the product, not on the host:
the principal that achieves all of it is a backup user, the lowest-trust account
type the product has, created by an administrator for the purpose of moving
bytes into one repository, and nothing about that role is supposed to reach a
host-scheduler directory. All of that is argued from the identity reached and
the deployment layout, and none of it was executed; nothing was done with any
stored backup during verification.

Verification boundary, stated plainly. One instance, one build, 6.2 build 1178,
one operating system shape, installed from the distributed Linux tarball archive
into /opt/Syncrify and started with ./run.sh, launched as
jre/bin/java -server -Xmx512m -cp lib/*.jar com.synametrics.sradef.BootLoader
behind Tomcat 9.0.113 announcing itself as Synametrics Web Server v7. Static
analysis was a reversal of the decompiled handler tree; the dynamic work was a
live run against that instance. Two properties of the instance were ours rather
than the product's, and both are carried here rather than buried. The portal
listener was bound to the loopback address by an explicit configuration edit we
made before testing, so the host was not reachable from outside while research
was in progress; it is a lab hardening step, not an observed product default, and
no shipped artifact establishes the factory value of that binding in either
direction. And the backup-user store file did not exist after installation, so
the account used for verification was written into config/UserMappings.xml by
hand with plaintext password comparison rather than provisioned through the
administrator's user-management operation, which is additionally licence-gated
and was blocked by a zero licence count. The sink's gate does not distinguish
between those provisioning paths, and any credential pair the server accepts
satisfies it, but only one instantiation of that condition has a live run behind
it: the plaintext-comparison branch. The encrypted-entry branch and the
directory-authentication branches were examined statically and not exercised.
No administrator session and no licence state sits between a backup user and this
write, and that should not be rounded down; equally it should not be rounded up
into a claim that any account works. The chain was driven from the target host
itself over the loopback interface, because that is how the listener was bound in
the instance, and the same chain arriving from across a network segment was not
verified and is not claimed. Where the advisory treats the route as remotely
reachable, that rests on the product's own design and binding configuration, on
the front controller being the same listener that serves the client-protocol
operations backup clients use, and on a structural argument: in any deployment
where clients back up across a network, the listener carrying operation=pb must
be reachable from those clients or the product cannot perform its function. It is
argued, not measured from a separate host, and reproducing it from a remote
position would strengthen the finding rather than contradict it. Two listeners
were world-bound in the instance, an HTTP status endpoint on 52110 that answered
with server version and build, active HTTP sessions and job status, and the
accelerated-transfer channel on 27000 over TCP and UDP; neither was observed to
serve the /app front controller, so neither is offered as a demonstrated remote
entry point to this sink, and a third listener on 54222, a secondary failure-over
HTTP endpoint, was likewise loopback in the instance. Root execution was
observed, not inferred; a non-root service deployment was not tested, so no
claim is made about what the write primitive achieves under an unprivileged
service identity, where /etc/cron.d could not be written and the escalation
demonstrated here would not follow while the file write itself survived. Exactly
one payload shape was executed, an identity capture into a marker file, and only
that shape is claimed. The published proof of concept is a rewrite of the
verification tool with argument handling and a control mode; the transcripts in
the advisory are from the run that established the finding. Only Syncrify 6.2
build 1178 was decompiled and exercised: no other version, edition or platform
was tested, no cross-version comparison was performed, and no version range is
claimed, although the construction is simple enough that adjacent versions very
likely share it and that is stated as unevidenced rather than asserted. The same
in-house sradef framework also underpins the vendor's sibling products, the
shipped web.xml of this build still carries the display-name SynaMan, and the
shared lib4njre.jar supplies the commons utility package to all of them; that is
recorded because it tells a reader where else the same construction might be
audited, and this advisory makes no claim about any product other than Syncrify
6.2 build 1178, none of which was tested here.

Detection notes, including one that will otherwise produce false positives by
design. Detection cannot come from the application, because the application
considers every step of this chain to have succeeded: the upload branch performs
no path logging, produces no rejection, records no path-decision failure, and
returns exactly the acceptance reply code="1" with <payload>0</payload> that a
legitimate chunk upload produces, so there is no application-level signal to
alert on and nothing distinguishes a benign name from a traversing one. Access
logs give weak signal only, and this is the false-positive caution: a
chunk-upload request looks like ordinary client traffic, so a rule keyed on the
presence of chunk uploads to operation=pb&st=u will fire on every real backup
session. The usable anomaly is shape rather than presence, an
authenticate-then-upload-one-or-two-small-chunks pattern with no further
protocol activity, which is not what a real backup client does, since a genuine
session issues a long sequence of such requests. Filesystem integrity monitoring
is the reliable control: alert on creation or modification under /etc/cron.d,
/etc/crontab, /var/spool/cron, the init system's unit directories and the
service's own binary and configuration paths, and specifically on any new file in
a scheduler directory whose content matches a job line with a user field of root.
The PublicLinks tree is worth auditing in its own right, for files whose names or
locations do not correspond to shared content and for per-user folders created by
accounts that never used the sharing feature. Scheduler and authentication logs
correlate the two halves, and a scheduled job whose command references a path
under a temporary directory is a reasonable alert on its own. Credential hygiene
is a detection control here as well as a mitigation, because each backup-user
credential is a potential root write, so unexpected accounts in the backup-user
store and accounts created without a corresponding client deployment both
deserve review. Two properties of the write are worth knowing when designing
those rules. Because the writer appends, a repeated request does not replace the
fragment, it adds lines to it, and a malformed second line does not undo a valid
first one, so a defender cannot rely on an attacker needing to get the fragment
byte-exact in a single shot. And the write is not atomic, there being no
write-to-temporary-then-rename step, so an interrupted request can leave a
truncated fragment on disk; for a job-definition directory a truncated line is
simply skipped by the scheduler, so that does not defeat the chain.

Mitigation summary: reject or confine the chunk name at the sink, before the
file constructor, refusing any value containing a path separator or ".." and any
absolute value and reducing what remains to a single path component, which is
convergence on a pattern the tree already implements three times over rather
than new design; then add a canonical containment check after construction and
not instead of it, resolving with getCanonicalPath() and requiring that the
result start with the canonical path of the authenticated user's own PublicLinks
folder followed by a separator, since containment is the property that survives
every encoding and every absolute-name variation while name-based rejection
alone does not; stop creating whatever directory the client names, the mkdirs()
call on the resolved target's parent letting a client materialise directory
structure anywhere the service identity can write, and create the destination
once from server-side state instead; give the protocol a real chunk-placement
contract using the frame's own chunkNumber and totalChunks values, which are read
and then ignored, by allocating the destination from session state, verifying the
sequence and staging reassembly in a directory no scheduler, service manager,
shell profile or startup mechanism consumes before moving the file into place
under a server-generated name; run the service under a dedicated unprivileged
account with write access confined to the repository and PublicLinks subtrees,
which removes the escalation to root command execution because a non-root process
cannot place a usable fragment in /etc/cron.d, but is defence in depth rather
than the fix since the write primitive itself survives; close the container-level
gap that makes per-handler secLevel the only gate, the shipped web.xml security
constraint covering DELETE, OPTIONS and PUT only and saying so in its own
load-time message while every request in this chain arrives over GET or POST and
therefore depends entirely on each handler declaring a correct level; audit the
same construction across the framework family, because the front controller, the
handler registry and the shared helper library are common to the vendor's other
products and the root cause is framework-shaped rather than handler-local; and
ship a regression test that fails the release, posting chunk names consisting of
a traversal to /etc/cron.d, an absolute name, a URL-encoded name, a name with a
trailing separator and a name whose parent-directory segments exceed the
repository depth, asserting both a rejection reply and that no file appeared
outside the repository subtree. Until a patch exists, operators should treat
every backup-user credential as a potential root write and provision them
narrowly, keep the portal listener on a management network or behind an
authenticating reverse proxy and off the internet, run the service unprivileged
where the deployment permits it after verifying repository ownership still works,
enable filesystem integrity monitoring on the scheduler and service paths, audit
the PublicLinks tree, monitor the backup-user authentication path for the shape
described above, and keep a supported restore tested, ideally as an offline or
immutable copy of critical repositories outside the server's own write reach,
since that is the control which survives a full compromise of this host.

The published proof of concept is Python 3 using only the standard library, with
no third-party dependency and no hardcoded target. It takes host, port, scheme,
route, operation and upload step; a mandatory backup-user credential pair with no
default; the command, marker path, fragment path, fragment user field, fragment
schedule, traversal depth, scheduler wait and per-request timeout as arguments,
defaulting to the loopback interface, port 5800, a depth of five and an identity
capture; an --insecure flag for self-signed TLS on the https scheme; a --control
mode that performs the identical authenticated upload with a non-traversing chunk
name and then asserts that neither the fragment nor the marker exists; a
--proof-command argument that reads proof from a local command's standard output
for an operator who already holds a shell channel to the host; an --expect-root
flag that exits non-zero unless the proof text reports uid=0; and a --cleanup
mode that removes only the two paths the script created.

Full technical analysis and the reproducible proof of concept, published
unconditionally:
  https://0day-rubbish.com/blog/synametrics-syncrify-publiclink-chunk-upload-traversal-cron-root-rce

Project archive (ongoing disclosure series):
  https://github.com/Exploit-Garbage/0day-Rubbish

Synametrics Technologies, Inc. was notified on the same day through the
vendor-wide technical-support mailbox it documents on the product's own support
page, which is the address we judged appropriate for a security report and is
the only channel we used. The vendor publishes no RFC 9116 security.txt, the
request returning 404 over https while the http redirect resolves to a 404 as
well, and we found no security page, vulnerability-disclosure policy or
responsible-disclosure statement anywhere on the vendor's live hosts, whose
entire web surface is two hosts. A separate documented sales mailbox exists on
the same domain and we did not use it. We submitted nothing to any
pre-publication intake form, because none exists, and we addressed no individual
by name. This disclosure is unconditional: the research is already public, no
permission was asked for, no embargo was requested, and nothing is withheld
pending a reply. No vulnerability identifier has been assigned to this finding
yet.

--
0day Rubbish Research Team
disclosure () 0day-rubbish com
https://0day-rubbish.com
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/