fix(security): unauth share_token leak (HIGH) + restore path-traversal, logo file-read, branding path keys (#946)
* fix(security): stop unauth share_token leak + block restore path-traversal, logo-path file read, branding path keys * test: update resolveLogoFile for the c7x5 containment (reject outside-storage absolute paths, keep inside) * fix(security): codex round-1 — escape LIKE wildcards in share-link resolve, keep in-storage absolute logos, guard restore verification - shareLinkService: escape %/_ in the link_partial LIKE fallback so an anonymous /resolve/____… wildcard can't match an arbitrary share_link and leak its bearer token (reopened GHSA-rh8r). Explicit ESCAPE for SQLite. - resolveLogoFile: re-add the raw absolute candidate but keep it subject to the storage-root containment filter (GHSA-c7x5) so legit in-storage absolute logos resolve while /etc/passwd stays rejected. - restoreService: apply the same pathEscapes guard in post-restore verification so a skipped traversal entry isn't fs.access'd/hashed. --------- Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
8cbb37310b
commit
9050affd8d
@@ -159,7 +159,16 @@ const resolveShareIdentifier = async (identifier) => {
|
||||
return { event, matchType: 'link', shareToken: getEventShareToken(event) };
|
||||
}
|
||||
|
||||
event = await baseQuery.clone().where('share_link', 'like', `%/${trimmed}`).first();
|
||||
// GHSA-rh8r hardening: `trimmed` is attacker-controlled, so escape LIKE
|
||||
// wildcards (`%`, `_`, and the escape char itself) before embedding it.
|
||||
// Otherwise an anonymous `/resolve/________…________` (32 underscores)
|
||||
// matches ANY share_link via single-char wildcards, resolves as
|
||||
// matchType 'link_partial', and the /resolve route hands back the
|
||||
// gallery's bearer token — reopening the very hole the token-withholding
|
||||
// fix closed. Explicit ESCAPE clause because SQLite has no default LIKE
|
||||
// escape character (Postgres defaults to backslash, but we set it for both).
|
||||
const likeTail = `%/${trimmed.replace(/[\\%_]/g, (c) => `\\${c}`)}`;
|
||||
event = await baseQuery.clone().whereRaw('share_link LIKE ? ESCAPE \'\\\'', [likeTail]).first();
|
||||
if (event) {
|
||||
return { event, matchType: 'link_partial', shareToken: getEventShareToken(event) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user