2020-08-11 01:47:54 +02:00
|
|
|
# Security model
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:53:28 +02:00
|
|
|
This section attempts to document the Zulip security model. It likely
|
2020-02-27 01:29:04 +01:00
|
|
|
does not cover every issue; if there are details you're curious about,
|
|
|
|
please feel free to ask questions in [#production
|
2024-06-06 21:48:31 +02:00
|
|
|
help](https://chat.zulip.org/#narrow/channel/31-production-help) on the
|
2021-12-09 20:15:18 +01:00
|
|
|
[Zulip community server](https://zulip.com/development-community/) (or if you
|
2020-02-27 01:29:04 +01:00
|
|
|
think you've found a security bug, please report it to
|
2020-06-09 00:58:42 +02:00
|
|
|
security@zulip.com so we can do a responsible security
|
2017-01-18 02:43:17 +01:00
|
|
|
announcement).
|
|
|
|
|
|
|
|
## Secure your Zulip server like your email server
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- It's reasonable to think about security for a Zulip server like you
|
2020-06-10 22:42:20 +02:00
|
|
|
do security for a team email server -- only trusted individuals
|
2017-01-18 02:43:17 +01:00
|
|
|
within an organization should have shell access to the server.
|
|
|
|
|
|
|
|
In particular, anyone with root access to a Zulip application server
|
|
|
|
or Zulip database server, or with access to the `zulip` user on a
|
|
|
|
Zulip application server, has complete control over the Zulip
|
|
|
|
installation and all of its data (so they can read messages, modify
|
2021-08-20 21:53:28 +02:00
|
|
|
history, etc.). It would be difficult or impossible to avoid this,
|
2017-01-18 02:43:17 +01:00
|
|
|
because the server needs access to the data to support features
|
|
|
|
expected of a group chat system like the ability to search the
|
|
|
|
entire message history, and thus someone with control over the
|
|
|
|
server has access to that data as well.
|
|
|
|
|
2020-08-11 01:47:54 +02:00
|
|
|
## Encryption and authentication
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Traffic between clients (web, desktop and mobile) and the Zulip
|
2021-08-20 21:53:28 +02:00
|
|
|
server is encrypted using HTTPS. By default, all Zulip services
|
2021-04-16 04:55:00 +02:00
|
|
|
talk to each other either via a localhost connection or using an
|
|
|
|
encrypted SSL connection.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Zulip requires CSRF tokens in all interactions with the web API to
|
2017-01-18 02:43:17 +01:00
|
|
|
prevent CSRF attacks.
|
|
|
|
|
2022-01-20 14:02:17 +01:00
|
|
|
- The preferred way to log in to Zulip is using a single sign-on (SSO)
|
|
|
|
solution like Google authentication, LDAP, or similar, but Zulip
|
|
|
|
also supports password authentication. See [the authentication
|
2022-02-24 00:17:21 +01:00
|
|
|
methods documentation](authentication-methods.md) for
|
2022-01-20 14:02:17 +01:00
|
|
|
details on Zulip's available authentication methods.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2017-01-18 05:52:52 +01:00
|
|
|
### Passwords
|
|
|
|
|
2021-05-14 21:47:55 +02:00
|
|
|
Zulip stores user passwords using the standard Argon2 and PBKDF2
|
2021-08-20 21:53:28 +02:00
|
|
|
algorithms. Argon2 is used for all new and changed passwords as of
|
2021-05-14 21:47:55 +02:00
|
|
|
Zulip Server 1.6.0, but legacy PBKDF2 passwords that were last changed
|
|
|
|
before the 1.6.0 upgrade are still supported.
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
|
|
|
|
When the user is choosing a password, Zulip checks the password's
|
2021-08-20 21:53:28 +02:00
|
|
|
strength using the popular [zxcvbn][zxcvbn] library. Weak passwords
|
|
|
|
are rejected, and strong passwords encouraged. The minimum password
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
strength allowed is controlled by two settings in
|
|
|
|
`/etc/zulip/settings.py`:
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- `PASSWORD_MIN_LENGTH`: The minimum acceptable length, in characters.
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
Shorter passwords are rejected even if they pass the `zxcvbn` test
|
|
|
|
controlled by `PASSWORD_MIN_GUESSES`.
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- `PASSWORD_MIN_GUESSES`: The minimum acceptable strength of the
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
password, in terms of the estimated number of passwords an attacker
|
|
|
|
is likely to guess before trying this one. If the user attempts to
|
|
|
|
set a password that `zxcvbn` estimates to be guessable in less than
|
|
|
|
`PASSWORD_MIN_GUESSES`, then Zulip rejects the password.
|
|
|
|
|
2017-10-03 20:45:49 +02:00
|
|
|
By default, `PASSWORD_MIN_GUESSES` is 10000. This provides
|
|
|
|
significant protection against online attacks, while limiting the
|
2018-10-19 21:18:55 +02:00
|
|
|
burden imposed on users choosing a password. See
|
2022-02-24 00:17:21 +01:00
|
|
|
[password strength](password-strength.md) for an extended
|
2018-10-19 21:18:55 +02:00
|
|
|
discussion on how we chose this value.
|
2017-10-03 20:45:49 +02:00
|
|
|
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
Estimating the guessability of a password is a complex problem and
|
|
|
|
impossible to efficiently do perfectly. For background or when
|
2017-10-03 20:45:49 +02:00
|
|
|
considering an alternate value for this setting, the article
|
2021-08-20 22:54:08 +02:00
|
|
|
["Passwords and the Evolution of Imperfect Authentication"][bhos15]
|
2021-08-20 21:53:28 +02:00
|
|
|
is recommended. The [2016 zxcvbn paper][zxcvbn-paper] adds useful
|
2017-10-03 20:45:49 +02:00
|
|
|
information about the performance of zxcvbn, and [a large 2012 study
|
2021-08-20 22:54:08 +02:00
|
|
|
of Yahoo users][bon12] is informative about the strength of the
|
2017-10-03 20:45:49 +02:00
|
|
|
passwords users choose.
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
|
|
|
|
<!---
|
|
|
|
If the BHOS15 link ever goes dead: it's reference 30 of the zxcvbn
|
2017-10-03 20:45:49 +02:00
|
|
|
paper, aka https://dl.acm.org/citation.cfm?id=2699390 , in the
|
|
|
|
_Communications of the ACM_ aka CACM. (But the ACM has it paywalled.)
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
.
|
2017-10-03 20:45:49 +02:00
|
|
|
Hooray for USENIX and IEEE: the other papers' canonical links are
|
|
|
|
not paywalled. The Yahoo study is reference 5 in BHOS15.
|
passwords: Express the quality threshold as guesses required.
The original "quality score" was invented purely for populating
our password-strength progress bar, and isn't expressed in terms
that are particularly meaningful. For configuration and the core
accept/reject logic, it's better to use units that are readily
understood. Switch to those.
I considered using "bits of entropy", defined loosely as the log
of this number, but both the zxcvbn paper and the linked CACM
article (which I recommend!) are written in terms of the number
of guesses. And reading (most of) those two papers made me
less happy about referring to "entropy" in our terminology.
I already knew that notion was a little fuzzy if looked at
too closely, and I gained a better appreciation of how it's
contributed to confusion in discussing password policies and
to adoption of perverse policies that favor "Password1!" over
"derived unusual ravioli raft". So, "guesses" it is.
And although the log is handy for some analysis purposes
(certainly for a graph like those in the zxcvbn paper), it adds
a layer of abstraction, and I think makes it harder to think
clearly about attacks, especially in the online setting. So
just use the actual number, and if someone wants to set a
gigantic value, they will have the pleasure of seeing just
how many digits are involved.
(Thanks to @YJDave for a prototype that the code changes in this
commit are based on.)
2017-10-03 19:48:06 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
[zxcvbn]: https://github.com/dropbox/zxcvbn
|
2021-08-20 22:54:08 +02:00
|
|
|
[bhos15]: http://www.cl.cam.ac.uk/~fms27/papers/2015-BonneauHerOorSta-passwords.pdf
|
2017-10-03 20:45:49 +02:00
|
|
|
[zxcvbn-paper]: https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_wheeler.pdf
|
2021-08-20 22:54:08 +02:00
|
|
|
[bon12]: http://ieeexplore.ieee.org/document/6234435/
|
2017-01-18 05:52:52 +01:00
|
|
|
|
2020-08-11 01:47:54 +02:00
|
|
|
## Messages and history
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Zulip message content is rendered using a specialized Markdown
|
2017-01-18 02:43:17 +01:00
|
|
|
parser which escapes content to protect against cross-site scripting
|
|
|
|
attacks.
|
|
|
|
|
2024-05-20 17:26:36 +02:00
|
|
|
- Zulip supports both public channels and private channels.
|
2021-08-20 22:54:08 +02:00
|
|
|
|
2024-05-20 17:26:36 +02:00
|
|
|
- Any non-guest user can join any public channel in the organization,
|
|
|
|
and can view the complete message history of any public channel
|
|
|
|
without joining the channel. Guests can only access channels that
|
2018-10-19 21:18:55 +02:00
|
|
|
another user adds them to.
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Organization owners and administrators can see and modify most
|
2024-05-20 17:26:36 +02:00
|
|
|
aspects of a private channel, including the membership and
|
2020-06-10 22:42:20 +02:00
|
|
|
estimated traffic. Owners and administrators generally cannot see
|
2024-05-20 17:26:36 +02:00
|
|
|
messages sent to private channels or do things that would
|
2020-06-10 22:42:20 +02:00
|
|
|
indirectly give them access to those messages, like adding members
|
2024-05-20 17:26:36 +02:00
|
|
|
or changing the channel privacy settings.
|
2018-10-19 21:18:55 +02:00
|
|
|
|
2024-05-20 17:26:36 +02:00
|
|
|
- Non-admins cannot easily see which private channels exist, or interact
|
|
|
|
with them in any way until they are added. Given a channel name, they can
|
|
|
|
figure out whether a channel with that name exists, but cannot see any
|
|
|
|
other details about the channel.
|
2018-10-19 21:18:55 +02:00
|
|
|
|
2024-05-20 17:26:36 +02:00
|
|
|
- See [Channel permissions](https://zulip.com/help/stream-permissions) for more details.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Zulip supports editing the content and topics of messages that have
|
2017-01-18 02:43:17 +01:00
|
|
|
already been sent. As a general philosophy, our policies provide
|
|
|
|
hard limits on the ways in which message content can be changed or
|
|
|
|
undone. In contrast, our policies around message topics favor
|
|
|
|
usefulness (e.g. for conversational organization) over faithfulness
|
2018-10-19 21:18:55 +02:00
|
|
|
to the original. In all configurations:
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Message content can only ever be modified by the original author.
|
2018-10-19 21:18:55 +02:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Any message visible to an organization owner or administrator can
|
2020-06-10 22:42:20 +02:00
|
|
|
be deleted at any time by that administrator.
|
2018-10-19 21:18:55 +02:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- See
|
2024-01-11 22:15:30 +01:00
|
|
|
[Restrict message editing and deletion](https://zulip.com/help/restrict-message-editing-and-deletion)
|
2018-10-19 21:18:55 +02:00
|
|
|
for more details.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2020-08-11 01:47:54 +02:00
|
|
|
## Users and bots
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- There are several types of users in a Zulip organization: organization
|
2020-10-23 02:43:28 +02:00
|
|
|
owners, organization administrators, members (normal users), guests,
|
|
|
|
and bots.
|
2018-10-19 21:18:55 +02:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Owners and administrators have the ability to deactivate and
|
2024-05-20 17:26:36 +02:00
|
|
|
reactivate other human and bot users, archive channels, add/remove
|
2020-06-10 22:42:20 +02:00
|
|
|
administrator privileges, as well as change configuration for the
|
|
|
|
organization.
|
2018-12-06 02:25:12 +01:00
|
|
|
|
|
|
|
Being an organization administrator does not generally provide the ability
|
2023-06-16 00:55:22 +02:00
|
|
|
to read other users' direct messages or messages sent to private
|
2024-05-20 17:26:36 +02:00
|
|
|
channels to which the administrator is not subscribed. There are two
|
2018-10-19 21:18:55 +02:00
|
|
|
exceptions:
|
|
|
|
|
2023-06-16 00:55:22 +02:00
|
|
|
- Organization owners may get access to direct messages via some types of
|
2020-06-08 23:04:39 +02:00
|
|
|
[data export](https://zulip.com/help/export-your-organization).
|
2018-10-19 21:18:55 +02:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Administrators can change the ownership of a bot. If a bot is subscribed
|
2024-05-20 17:26:36 +02:00
|
|
|
to a private channel, then an administrator can indirectly get access to
|
|
|
|
channel messages by taking control of the bot, though the access will be
|
2018-10-19 21:18:55 +02:00
|
|
|
limited to what the bot can do. (E.g. incoming webhook bots cannot read
|
|
|
|
messages.)
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Every Zulip user has an API key, available on the settings page.
|
2017-01-18 02:43:17 +01:00
|
|
|
This API key can be used to do essentially everything the user can
|
2021-08-20 21:53:28 +02:00
|
|
|
do; for that reason, users should keep their API key safe. Users
|
2017-01-18 02:43:17 +01:00
|
|
|
can rotate their own API key if it is accidentally compromised.
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- To properly remove a user's access to a Zulip team, it does not
|
2018-10-19 21:18:55 +02:00
|
|
|
suffice to change their password or deactivate their account in a
|
2022-01-20 14:02:17 +01:00
|
|
|
single sign-on (SSO) system, since neither of those prevents
|
|
|
|
authenticating with the user's API key or those of bots the user has
|
|
|
|
created. Instead, you should [deactivate the user's
|
|
|
|
account](https://zulip.com/help/deactivate-or-reactivate-a-user) via
|
|
|
|
Zulip's "Organization settings" interface.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- The Zulip mobile apps authenticate to the server by sending the
|
2017-01-18 02:43:17 +01:00
|
|
|
user's password and retrieving the user's API key; the apps then use
|
|
|
|
the API key to authenticate all future interactions with the site.
|
|
|
|
Thus, if a user's phone is lost, in addition to changing passwords,
|
|
|
|
you should rotate the user's Zulip API key.
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Guest users are like Members, but they do not have automatic access
|
2024-05-20 17:26:36 +02:00
|
|
|
to public channels.
|
2018-12-06 02:25:12 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Zulip supports several kinds of bots with different capabilities.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Incoming webhook bots can only send messages into Zulip.
|
|
|
|
- Outgoing webhook bots and Generic bots can essentially do anything a
|
2018-10-19 21:18:55 +02:00
|
|
|
non-administrator user can, with a few exceptions (e.g. a bot cannot
|
2020-08-11 02:20:10 +02:00
|
|
|
log in to the web application, register for mobile push
|
2018-10-19 21:18:55 +02:00
|
|
|
notifications, or create other bots).
|
2021-08-20 21:45:39 +02:00
|
|
|
- Bots with the `can_forge_sender` permission can send messages that appear to have been sent by
|
2018-10-19 21:18:55 +02:00
|
|
|
another user. They also have the ability to see the names of all
|
2024-05-20 17:26:36 +02:00
|
|
|
channels, including private channels. This is important for implementing
|
2018-10-19 21:18:55 +02:00
|
|
|
integrations like the Jabber, IRC, and Zephyr mirrors.
|
2017-10-24 02:36:56 +02:00
|
|
|
|
2020-12-20 14:21:42 +01:00
|
|
|
These bots cannot be created by Zulip users, including
|
2020-06-10 22:42:20 +02:00
|
|
|
organization owners. They can only be created on the command
|
2020-12-20 14:21:42 +01:00
|
|
|
line (via `manage.py change_user_role can_forge_sender`).
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-04-15 01:25:02 +02:00
|
|
|
## User-uploaded content and user-generated requests
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:53:28 +02:00
|
|
|
- Zulip supports user-uploaded files. Ideally they should be hosted
|
2017-01-18 02:43:17 +01:00
|
|
|
from a separate domain from the main Zulip server to protect against
|
2018-02-12 18:18:03 +01:00
|
|
|
various same-domain attacks (e.g. zulip-user-content.example.com).
|
|
|
|
|
|
|
|
We support two ways of hosting them: the basic `LOCAL_UPLOADS_DIR`
|
|
|
|
file storage backend, where they are stored in a directory on the
|
|
|
|
Zulip server's filesystem, and the S3 backend, where the files are
|
2021-08-20 21:53:28 +02:00
|
|
|
stored in Amazon S3. It would not be difficult to add additional
|
2018-02-12 18:18:03 +01:00
|
|
|
supported backends should there be a need; see
|
|
|
|
`zerver/lib/upload.py` for the full interface.
|
|
|
|
|
|
|
|
For both backends, the URLs used to access uploaded files are long,
|
|
|
|
random strings, providing one layer of security against unauthorized
|
|
|
|
users accessing files uploaded in Zulip (an authorized user would
|
|
|
|
need to share the URL with an unauthorized user in order for the
|
2018-10-19 21:18:55 +02:00
|
|
|
file to be accessed by the unauthorized user. Of course, any
|
2018-02-12 18:18:03 +01:00
|
|
|
such authorized user could have just downloaded and sent the file
|
2018-10-19 21:18:55 +02:00
|
|
|
instead of the URL, so this is arguably pretty good protection.)
|
2023-05-01 18:14:03 +02:00
|
|
|
|
|
|
|
However, to help protect against accidental sharing of URLs to
|
|
|
|
restricted files (e.g. by forwarding a missed-message email or leaks
|
|
|
|
involving the Referer header), every access to an uploaded file has
|
|
|
|
access control verified (confirming that the browser is logged into
|
|
|
|
a Zulip account that has received the uploaded file in question).
|
2018-02-12 18:18:03 +01:00
|
|
|
|
camo: Replace with go-camo implementation.
The upstream of the `camo` repository[1] has been unmaintained for
several years, and is now archived by the owner. Additionally, it has
a number of limitations:
- It is installed as a sysinit service, which does not run under
Docker
- It does not prevent access to internal IPs, like 127.0.0.1
- It does not respect standard `HTTP_proxy` environment variables,
making it unable to use Smokescreen to prevent the prior flaw
- It occasionally just crashes, and thus must have a cron job to
restart it.
Swap camo out for the drop-in replacement go-camo[2], which has the
same external API, requiring not changes to Django code, but is more
maintained. Additionally, it resolves all of the above complaints.
go-camo is not configured to use Smokescreen as a proxy, because its
own private-IP filtering prevents using a proxy which lies within that
IP space. It is also unclear if the addition of Smokescreen would
provide any additional protection over the existing IP address
restrictions in go-camo.
go-camo has a subset of the security headers that our nginx reverse
proxy sets, and which camo set; provide the missing headers with `-H`
to ensure that go-camo, if exposed from behind some other non-nginx
load-balancer, still provides the necessary security headers.
Fixes #18351 by moving to supervisor.
Fixes zulip/docker-zulip#298 also by moving to supervisor.
[1] https://github.com/atmos/camo
[2] https://github.com/cactus/go-camo
2021-11-18 01:44:51 +01:00
|
|
|
- Zulip supports using the [go-camo][go-camo] image proxy to proxy content like
|
2021-03-23 10:34:55 +01:00
|
|
|
inline image previews, that can be inserted into the Zulip message feed by
|
|
|
|
other users. This ensures that clients do not make requests to external
|
|
|
|
servers to fetch images, improving privacy.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- By default, Zulip will provide image previews inline in the body of
|
2021-08-20 21:53:28 +02:00
|
|
|
messages when a message contains a link to an image. You can
|
2017-01-18 02:43:17 +01:00
|
|
|
control this using the `INLINE_IMAGE_PREVIEW` setting.
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Zulip may make outgoing HTTP connections to other servers in a
|
2021-03-04 07:36:36 +01:00
|
|
|
number of cases:
|
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Outgoing webhook bots (creation of which can be restricted)
|
|
|
|
- Inline image previews in messages (enabled by default, but can be disabled)
|
|
|
|
- Inline webpage previews and embeds (must be configured to be enabled)
|
|
|
|
- Twitter message previews (must be configured to be enabled)
|
|
|
|
- BigBlueButton and Zoom API requests (must be configured to be enabled)
|
|
|
|
- Mobile push notifications (must be configured to be enabled)
|
2021-03-04 07:36:36 +01:00
|
|
|
|
2021-08-20 21:45:39 +02:00
|
|
|
- Notably, these first 3 features give end users (limited) control to cause
|
2021-11-17 22:17:56 +01:00
|
|
|
the Zulip server to make HTTP requests on their behalf. Because of this,
|
2022-01-20 14:02:17 +01:00
|
|
|
Zulip routes all outgoing HTTP requests [through
|
2021-05-12 20:22:03 +02:00
|
|
|
Smokescreen][smokescreen-setup] to ensure that Zulip cannot be
|
2021-08-20 22:54:08 +02:00
|
|
|
used to execute [SSRF attacks][ssrf] against other systems on an
|
2021-08-20 21:53:28 +02:00
|
|
|
internal corporate network. The default Smokescreen configuration
|
2021-04-15 01:25:02 +02:00
|
|
|
denies access to all non-public IP addresses, including 127.0.0.1.
|
|
|
|
|
2022-01-08 00:19:31 +01:00
|
|
|
The Camo image server does not, by default, route its traffic
|
|
|
|
through Smokescreen, since Camo includes logic to deny access to
|
|
|
|
private subnets; this can be [overridden][proxy.enable_for_camo].
|
|
|
|
|
camo: Replace with go-camo implementation.
The upstream of the `camo` repository[1] has been unmaintained for
several years, and is now archived by the owner. Additionally, it has
a number of limitations:
- It is installed as a sysinit service, which does not run under
Docker
- It does not prevent access to internal IPs, like 127.0.0.1
- It does not respect standard `HTTP_proxy` environment variables,
making it unable to use Smokescreen to prevent the prior flaw
- It occasionally just crashes, and thus must have a cron job to
restart it.
Swap camo out for the drop-in replacement go-camo[2], which has the
same external API, requiring not changes to Django code, but is more
maintained. Additionally, it resolves all of the above complaints.
go-camo is not configured to use Smokescreen as a proxy, because its
own private-IP filtering prevents using a proxy which lies within that
IP space. It is also unclear if the addition of Smokescreen would
provide any additional protection over the existing IP address
restrictions in go-camo.
go-camo has a subset of the security headers that our nginx reverse
proxy sets, and which camo set; provide the missing headers with `-H`
to ensure that go-camo, if exposed from behind some other non-nginx
load-balancer, still provides the necessary security headers.
Fixes #18351 by moving to supervisor.
Fixes zulip/docker-zulip#298 also by moving to supervisor.
[1] https://github.com/atmos/camo
[2] https://github.com/cactus/go-camo
2021-11-18 01:44:51 +01:00
|
|
|
[go-camo]: https://github.com/cactus/go-camo
|
2021-08-20 22:54:08 +02:00
|
|
|
[ssrf]: https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
|
2022-02-16 01:39:15 +01:00
|
|
|
[smokescreen-setup]: deployment.md#customizing-the-outgoing-http-proxy
|
2024-02-16 23:07:19 +01:00
|
|
|
[proxy.enable_for_camo]: system-configuration.md#enable_for_camo
|
2021-04-15 01:25:02 +02:00
|
|
|
|
2022-11-05 23:55:11 +01:00
|
|
|
## Rate limiting
|
|
|
|
|
|
|
|
Zulip has built-in rate limiting of login attempts, all access to the
|
|
|
|
API, as well as certain other types of actions that may be involved in
|
|
|
|
abuse. For example, the email confirmation flow, by its nature, needs
|
|
|
|
to allow sending an email to an email address that isn't associated
|
|
|
|
with an existing Zulip account. Limiting the ability of users to
|
|
|
|
trigger such emails helps prevent bad actors from damaging the spam
|
|
|
|
reputation of a Zulip server by sending confirmation emails to random
|
|
|
|
email addresses.
|
|
|
|
|
|
|
|
The default rate limiting rules for a Zulip server will change as we improve
|
|
|
|
the product. A server administrator can browse the current rules using
|
|
|
|
`/home/zulip/deployments/current/scripts/get-django-setting
|
|
|
|
RATE_LIMITING_RULES`; or with comments by reading
|
|
|
|
`DEFAULT_RATE_LIMITING_RULES` in `zproject/default_settings.py`.
|
|
|
|
|
|
|
|
Server administrators can tweak rate limiting in the following ways in
|
|
|
|
`/etc/zulip/settings.py`:
|
|
|
|
|
|
|
|
- The `RATE_LIMITING` setting can be set to `False` to completely
|
|
|
|
disable all rate-limiting.
|
|
|
|
- The `RATE_LIMITING_RULES` setting can be used to override specific
|
|
|
|
rules. See the comment in the file for more specific details on how
|
|
|
|
to do it. After changing the setting, we recommend using
|
|
|
|
`/home/zulip/deployments/current/scripts/get-django-setting
|
|
|
|
RATE_LIMITING_RULES` to verify your changes. You can then restart
|
|
|
|
the Zulip server with `scripts/restart-server` to have the new
|
|
|
|
configuration take effect.
|
|
|
|
- The `RATE_LIMIT_TOR_TOGETHER` setting can be set to `True` to group all
|
|
|
|
known exit nodes of [TOR](https://www.torproject.org/) together for purposes
|
|
|
|
of IP address limiting. Since traffic from a client using TOR is distributed
|
|
|
|
across its exit nodes, without enabling this setting, TOR can otherwise be
|
|
|
|
used to avoid IP-based rate limiting. The updated list of TOR exit nodes
|
|
|
|
is refetched once an hour.
|
2022-12-15 20:47:35 +01:00
|
|
|
- If a user runs into the rate limit for login attempts, a server
|
|
|
|
administrator can clear this state using the
|
|
|
|
`manage.py reset_authentication_attempt_count`
|
|
|
|
[management command][management-commands].
|
2022-11-05 23:55:11 +01:00
|
|
|
|
|
|
|
See also our [API documentation on rate limiting][rate-limit-api].
|
|
|
|
|
2022-12-15 20:47:35 +01:00
|
|
|
[management-commands]: ../production/management-commands.md
|
2022-11-05 23:55:11 +01:00
|
|
|
[rate-limit-api]: https://zulip.com/api/rest-error-handling#rate-limit-exceeded
|
|
|
|
|
2017-01-18 02:43:17 +01:00
|
|
|
## Final notes and security response
|
|
|
|
|
|
|
|
If you find some aspect of Zulip that seems inconsistent with this
|
2020-06-09 00:58:42 +02:00
|
|
|
security model, please report it to security@zulip.com so that we can
|
|
|
|
investigate and coordinate an appropriate security release if needed.
|
2017-01-18 02:43:17 +01:00
|
|
|
|
|
|
|
Zulip security announcements will be sent to
|
|
|
|
zulip-announce@googlegroups.com, so you should subscribe if you are
|
|
|
|
running Zulip in production.
|