Commit graph

7274 commits

Author SHA1 Message Date
djmaze
650133ae8a @ZaphodB fix(security): SSRF gate let through CGNAT, multicast and the server's own IPs
URIHasPublicHost() relied on FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
alone. On PHP 8.4 that passes 100.64.0.0/10 (shared space: CGNAT, Tailscale,
Alibaba metadata at 100.100.100.200), 192.0.0.0/24, 198.18.0.0/15,
224.0.0.0/4, fec0::/10 and ff00::/8. It also passed the server's own public
addresses: a fetch to them never leaves the host, so it reaches services that
listen on every address but are firewalled from outside (on our host: grafana,
forgejo, prometheus and the exporters).

Both checks, on the literal host and on every resolved address, now go through
IsPublicIP(): PHP's flags, then an explicit list of the ranges above, then the
addresses net_get_interfaces() reports for this host (cached per request).

test/ssrf-ranges.php checks the new ranges and their edges, that ordinary
public addresses still pass, and the own-address rule via the cache. Before
this change 100.64.0.1 was treated as public. DNS rebinding between check and
connect is still not covered; that needs the connection pinned to the checked
address.
2026-10-27 01:30:31 +01:00
djmaze
8678c0e8fd Not Tachyon 2026-10-26 00:39:16 +01:00
djmaze
49dd9a7c7c @ZaphodB fix(security): attachments rendered inline as the type their sender declared
Raw view served an attachment inline with the Content-Type from the message,
only forcing PDF by file name. A part named photo.png declared as text/html is
previewed as an image by extension, and its link opened an HTML page on the
webmail origin. The raw CSP (script-src 'none'; child-src 'none') stopped
scripts, but replaced the global policy, so forms, meta refresh, base and
remote styles and images all worked: a convincing phishing page under the real
hostname.

- inlineContentType() allows only raster images, PDF, audio, video and
  text/plain to be shown as declared. Other text/* is shown as text/plain (an
  HTML attachment displays its source); anything else, SVG and XML included,
  is sent as an application/octet-stream download.
- The raw CSP adds default-src 'none', form-action 'none' and base-uri 'none',
  with explicit img, media, style and object sources for what raw view
  legitimately shows. object-src 'self' keeps the browser PDF viewer usable
  (not verified in a browser here).

test/raw-inline-type.php covers the type decision, parameters and case.
2026-10-26 00:29:19 +01:00
djmaze
bf0aa84fe7 @ZaphodB fix(security): ?/Test integrity diagnostics answered without a login
ServiceTest() ran Integrity::test() for any request. The test builds its base
URL from $_SERVER['HTTP_HOST'], sends HEAD requests to it and prints the status
and every response header. The client therefore chose where the server sent
requests and read the result. Behind nginx the vhost is picked by server_name
with the port ignored, so Host: <this host>:<port> reaches the app and turns the
diagnostic into a probe of the server's own TLS ports and their headers.

Answer 404 unless an admin session is present. An admin's browser sends the
real Host, so the diagnostic still works for the people it is for.

Checked end to end with php -S (4 workers, no network): without a session the
old code returned 200 with the diagnostics, this one returns 404 and nothing.
2026-10-25 23:42:42 +01:00
djmaze
7f1b1e6288 @ZaphodB fix(security): tokens could be forged by choosing the xxtea algorithm
Crypt::Decrypt() calls "{name}Decrypt" with the algorithm name taken from the
token itself, so a client could ask for xxtea on any server. XxteaDecrypt used
$salt . Passphrase() as the key, but XXTEA reads only the first 16 bytes of its
key and the salt is 16 bytes stored in the token: the server secret never took
part. Anyone could therefore make a token every Crypt::Decrypt* consumer would
accept as genuine (remember-me, secure cookies, DecodeKeyValuesQ raw keys, ...),
and any real xxtea token could be read. The consumers traced so far still
require a server-side record or a real IMAP login, so this is not a known
account takeover, but the integrity check they rely on was not there.

- Decrypt refuses xxtea whenever sodium or openssl is available, i.e. whenever
  Encrypt() would never have produced it, compares the name case-insensitively
  (PHP method names are), and dispatches only sodium, openssl and xxtea rather
  than any method name ending in Decrypt.
- The xxtea key is now the first 16 bytes of sha256(salt . passphrase), so on a
  host left with nothing but xxtea it depends on the secret. Tokens written under
  the old derivation stop decrypting there; they were never keyed by the server.

test/crypt-forgery.php forges a token with an attacker-chosen salt (accepted
before this change), checks it and an arbitrary method name are refused, and
that sodium, url-safe and xxtea round trips still work only with the right key.
2026-10-25 23:36:57 +01:00
djmaze
08f34b9954 @ZaphodB fix(security): message images loaded without consent through srcset and CSS
The sanitizer sends img src, and url() in background-image, list-style-image
and content, through the remote content handling: blocked until the user
allows it, rewritten to the proxy when it is on. Two other ways to load an
image were left alone:

- srcset (and sizes) were on the attribute allowlist and never rewritten, so
  <img srcset="https://..."> loaded when the message opened. With the proxy on,
  the CSP keeps img-src to 'self', but a relative srcset="?/ProxyExternal/<b64>"
  is same-origin and made the server fetch an attacker URL at render time: a
  read receipt with no click. With the proxy off the browser fetched directly.
- url() in any other style property (border-image-source, mask-image, filter,
  ...) kept its value, with the same two outcomes.

Drop srcset and sizes from the allowlist (attributes not on it are deleted),
and remove any other style property whose value holds url(). Relative img src
was already safe: anything not cid:, http(s) or data:image/ is parked as
data-x-src-broken.

Checked the style sweep in Chromium: border-image-source, mask-image (both
spellings), filter and cursor lose the url(), other declarations survive, and
the background/list-style shorthands are left to the existing handling.
2026-10-25 23:28:14 +01:00
the-djmaze
3776ac6b32 @ZaphodB fix(security): S/MIME verify reported altered messages as validly signed
OpenSSL::verify() called openssl_pkcs7_verify with PKCS7_NOSIGS, which skips
the signature check, and returned success whenever that call did. Any
well-formed signed message therefore came back as success: true, including one
whose content had been changed after signing, and the client shows that as a
good signature (Message.js smimeSigned). Applies to the message view, the
explicit verify action and signed content inside decrypted mail.

The call is also how the content of an opaque signed message is extracted for
display, so it stays as it is for that, and a second openssl_pkcs7_verify
without PKCS7_NOSIGS now decides success and does not throw on failure. A
message with a broken signature remains readable but is no longer reported as
signed. PKCS7_NOVERIFY is kept on purpose: the signer certificate is not checked
against a CA, so success means the content is what the certificate holder
signed, not that the certificate is trusted; the comment says so.

test/smime-verify.php signs detached and opaque messages with a throwaway key,
checks the intact ones verify, and that altering one byte of signed content
flips success to false while the content stays readable. Before this change
the altered detached message was reported as validly signed.
2026-10-24 11:57:38 +02:00
the-djmaze
b6607bf6e1 @ZaphodB fix(security): strip line breaks from decoded and outgoing header values
DecodeHeaderValue removed CR and LF before decoding encoded-words, so an
encoded-word could still decode to a line break. A message whose Message-ID
was =?utf-8?b?...?= with CRLF inside reached the client with the break intact,
the client copies Message-ID into In-Reply-To when replying or forwarding, and
SetInReplyTo only trimmed it. The reply then carried header lines chosen by the
sender of the original, or a body if the payload held a blank line, sent and
DKIM-signed as the user and saved to Sent. Subject on Edit-as-new had the same
shape through the ASCII path.

Strip CR, LF and NUL in two places: after decoding in DecodeHeaderValue, and in
Header itself, which every outgoing header is built through, so a value that
arrives by another route (client parameters, plugins) cannot break the line
either. Folding is unaffected; __toString adds it back.

test/header-injection.php covers B and Q encoded-words, parsing, outgoing
In-Reply-To and Subject, and that long values still fold.
2026-10-24 11:50:10 +02:00
the-djmaze
61ebcb6ed4 CSP block legacy HTML element types such as <object>, <embed>, and <applet> on Raw actions 2026-10-22 18:04:40 +02:00
Maarten
ceb9f68ded
Merge pull request #1922 from leojonathanoh/fix/docker-ensure-nginx-does-not-listen-on-ipv6-in-ipv4-only-environments
Fix (docker): Ensure nginx does not listen on ipv6 in ipv4-only environments
2026-10-22 16:11:58 +02:00
Maarten
d7e7250b16
Merge pull request #2060 from FathiBenNasr/fix/carddav-sync-empty-set-guard
AddressBook: do not let an empty side wipe the other during sync
2026-10-22 16:09:56 +02:00
Maarten
0a8e37ccc8
Merge pull request #2070 from FathiBenNasr/feature/ldap-suggestions-multiple-base-dns
ldap-contacts-suggestions: search several base DNs
2026-10-22 16:07:51 +02:00
Maarten
2cff632b2d
Merge pull request #2021 from kristofer84/login-o365-bugfix
Corrected OAuth2 login flow and added better error handling
2026-10-22 14:15:28 +02:00
the-djmaze
2c658b7c31 Fix year for #1882 2026-10-22 14:12:26 +02:00
Maarten
4997853b78
Merge pull request #1882 from ZephOne/also-use-ldap-login-mapping-for-smtp-user
Also use ldap login mapping for smtp user
2026-10-22 14:10:42 +02:00
Maarten
6fba843d15
Merge pull request #2011 from juschleg/patch-1
Update SSLContext.php: fixed typo in line 42, to reference correct class
2026-10-22 14:06:53 +02:00
Maarten
3659552fac
Merge pull request #2035 from alterative-editions/plugin-login-http
Add HTTP based SSO plugin for Apache using PHP_AUTH_USER/PHP_AUTH_PW
2026-10-22 13:54:05 +02:00
the-djmaze
3432795930 Fix formatting and bump version for #2019 2026-10-22 13:50:36 +02:00
Maarten
fb4a247916
Merge pull request #2019 from oGrzegorZo/fix-search-filters-keyword-crash
Fix Search Filters plugin: crash on keyword filters
2026-10-22 13:44:56 +02:00
Maarten
a174bf7ba0
Merge pull request #1879 from tribut/smime-intermediate-certs
Add intermediate certificates for S/MIME signing
2026-10-22 13:41:20 +02:00
Maarten
77be04d427
Merge pull request #1863 from SergeyMosin/pgp-improvements
PGP improvements
2026-10-22 13:39:45 +02:00
Maarten
cf185b9ab5
Merge pull request #2066 from Ron-RONZZ-org/feature/admin-trusted-header-auth
Admin panel: accept trusted proxy-auth header (X-NC-Admin) on the admin host
2026-10-22 13:37:52 +02:00
Maarten
30bb5c934d
Merge pull request #2069 from SebastianAlvarezGajardo/fix/messagelist-keep-page-and-search
Don't reset the message list to page 1 and clear the search on message.show
2026-10-22 13:23:57 +02:00
Maarten
b9397e6e20
Merge pull request #2007 from Murena-SAS/dev/fix-autologin
Fix(auth): Correct OIDC login by using SensitiveString object.
2026-10-22 13:18:56 +02:00
the-djmaze
fd6beedc0c Merge branch 'master' of github.com:the-djmaze/snappymail 2026-10-22 13:16:11 +02:00
the-djmaze
45b94153c1 Resolve #1906 2026-10-22 13:15:59 +02:00
Maarten
02e96a9339
Merge pull request #2012 from juschleg/patch-2
fixed typo in imapsync.php: was "RainLoop\API" instead of "RainLoop\Api"
2026-10-22 13:12:04 +02:00
the-djmaze
b3edb5fb0a Versionnumber for #2064 2026-10-22 13:10:09 +02:00
Maarten
58d553b1a2
Merge pull request #2064 from FathiBenNasr/fix/ics-viewer-attachments-observable
ics-viewer: unwrap attachments and widen the calendar part match
2026-10-22 13:07:14 +02:00
Maarten
2f26779f4a
Merge pull request #2061 from FathiBenNasr/feature/avatars-contact-photo
avatars: use the PHOTO stored on a matching contact
2026-10-22 13:01:15 +02:00
Maarten
9f0e6d946a
Merge pull request #2074 from FathiBenNasr/fix/getcontactbyemail-missing-columns
PdoAddressBook: GetContactByEmail() passed the wrong columns
2026-10-22 12:59:25 +02:00
Maarten
85b142f559
Merge pull request #2039 from gocowim/fix_autocrypt_compliance
FIX non-compliant Autocrypt Header
2026-10-22 12:50:26 +02:00
the-djmaze
1be5abd122 Version number for #2024 2026-10-22 12:48:32 +02:00
Maarten
a9ca71bae1
Merge pull request #2024 from bohwaz/fix-remote-login
Fix login-remote plugin password handling
2026-10-22 12:45:17 +02:00
Maarten
0805f71461
Merge pull request #2005 from LoredCast/patch-1
Update PostgreSQL DSN in Docker.md
2026-10-22 12:44:25 +02:00
Maarten
abb83e48b0
Merge pull request #1967 from PhilippMundhenk/master
Plugin proxy-auth: Update instructions for Dovecot 2.4, "check proxy" setting fix, troubleshooting hints
2026-10-22 12:36:44 +02:00
Maarten
f52249b77b
Merge pull request #1973 from HeySora/add-sqlite-to-docker
Add pdo_sqlite installation to Docker image
2026-10-22 12:32:48 +02:00
Maarten
1bbd7f635a
Merge pull request #2052 from alexgabi/patch-1
Update user.json
2026-10-22 12:30:13 +02:00
Maarten
8a8304e81a
Merge pull request #2075 from FathiBenNasr/fix/htmlutils-data-attributes
HtmlUtils: strip every data-* attribute, not just the first one
2026-10-22 12:28:44 +02:00
Maarten
e7a491b0f5
Merge pull request #2079 from MiDniGG/fix/autosave-duplicate-contacts
Fix duplicate contacts on autosave for addresses with uppercase letters
2026-10-22 12:18:12 +02:00
the-djmaze
6c9d3edc18 Docker upgrade to PHP 8.5 #2086 2026-10-22 12:13:49 +02:00
Kristofer Nilsson
5dca6d4f65 Change viewModelTemplateID check for account popups 2026-10-22 12:12:51 +02:00
Cursor Agent
2af0af173e Refactor O365 login to use server-side state and add account support
Co-authored-by: kristofer.john.nilsson <kristofer.john.nilsson@gmail.com>
2026-10-22 12:12:51 +02:00
Kristofer Nilsson
7fdc77ce5f Corrected OAuth2 login flow and added better error handling 2026-10-22 12:12:51 +02:00
Maarten
86dc155d1c
Merge branch 'master' into patch-1 2026-10-21 15:44:30 +02:00
Maarten
5de44c77c2
Merge pull request #2084 from sasogu/add-catalan-localization
Add Catalan localization
2026-10-21 15:42:25 +02:00
the-djmaze
dfc9420385 Sabre XML v4.1.0 2026-10-21 15:18:43 +02:00
the-djmaze
e66f325aa3 Sabre VObject v5.0.0 2026-10-21 15:18:17 +02:00
the-djmaze
aad651f2a1 Remove buggy Squire ImageResizer 2026-10-20 23:41:06 +02:00
the-djmaze
67a68915db Knockout use Reflection binding 2026-10-20 23:34:48 +02:00