mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	We recently changed /developer-community to /development-community. Now that this change is in production, we can also migrate the external links in our ReadTheDocs documentation.
		
			
				
	
	
		
			282 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# Security model
 | 
						|
 | 
						|
This section attempts to document the Zulip security model. It likely
 | 
						|
does not cover every issue; if there are details you're curious about,
 | 
						|
please feel free to ask questions in [#production
 | 
						|
help](https://chat.zulip.org/#narrow/stream/31-production-help) on the
 | 
						|
[Zulip community server](https://zulip.com/development-community/) (or if you
 | 
						|
think you've found a security bug, please report it to
 | 
						|
security@zulip.com so we can do a responsible security
 | 
						|
announcement).
 | 
						|
 | 
						|
## Secure your Zulip server like your email server
 | 
						|
 | 
						|
- It's reasonable to think about security for a Zulip server like you
 | 
						|
  do security for a team email server -- only trusted individuals
 | 
						|
  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
 | 
						|
  history, etc.). It would be difficult or impossible to avoid this,
 | 
						|
  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.
 | 
						|
 | 
						|
## Encryption and authentication
 | 
						|
 | 
						|
- Traffic between clients (web, desktop and mobile) and the Zulip
 | 
						|
  server is encrypted using HTTPS. By default, all Zulip services
 | 
						|
  talk to each other either via a localhost connection or using an
 | 
						|
  encrypted SSL connection.
 | 
						|
 | 
						|
- Zulip requires CSRF tokens in all interactions with the web API to
 | 
						|
  prevent CSRF attacks.
 | 
						|
 | 
						|
- The preferred way to log in to Zulip is using an SSO solution like
 | 
						|
  Google auth, LDAP, or similar, but Zulip also supports password
 | 
						|
  authentication. See
 | 
						|
  [the authentication methods documentation](../production/authentication-methods.md)
 | 
						|
  for details on Zulip's available authentication methods.
 | 
						|
 | 
						|
### Passwords
 | 
						|
 | 
						|
Zulip stores user passwords using the standard Argon2 and PBKDF2
 | 
						|
algorithms. Argon2 is used for all new and changed passwords as of
 | 
						|
Zulip Server 1.6.0, but legacy PBKDF2 passwords that were last changed
 | 
						|
before the 1.6.0 upgrade are still supported.
 | 
						|
 | 
						|
When the user is choosing a password, Zulip checks the password's
 | 
						|
strength using the popular [zxcvbn][zxcvbn] library. Weak passwords
 | 
						|
are rejected, and strong passwords encouraged. The minimum password
 | 
						|
strength allowed is controlled by two settings in
 | 
						|
`/etc/zulip/settings.py`:
 | 
						|
 | 
						|
- `PASSWORD_MIN_LENGTH`: The minimum acceptable length, in characters.
 | 
						|
  Shorter passwords are rejected even if they pass the `zxcvbn` test
 | 
						|
  controlled by `PASSWORD_MIN_GUESSES`.
 | 
						|
 | 
						|
- `PASSWORD_MIN_GUESSES`: The minimum acceptable strength of the
 | 
						|
  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.
 | 
						|
 | 
						|
  By default, `PASSWORD_MIN_GUESSES` is 10000. This provides
 | 
						|
  significant protection against online attacks, while limiting the
 | 
						|
  burden imposed on users choosing a password. See
 | 
						|
  [password strength](../production/password-strength.md) for an extended
 | 
						|
  discussion on how we chose this value.
 | 
						|
 | 
						|
  Estimating the guessability of a password is a complex problem and
 | 
						|
  impossible to efficiently do perfectly. For background or when
 | 
						|
  considering an alternate value for this setting, the article
 | 
						|
  ["Passwords and the Evolution of Imperfect Authentication"][bhos15]
 | 
						|
  is recommended. The [2016 zxcvbn paper][zxcvbn-paper] adds useful
 | 
						|
  information about the performance of zxcvbn, and [a large 2012 study
 | 
						|
  of Yahoo users][bon12] is informative about the strength of the
 | 
						|
  passwords users choose.
 | 
						|
 | 
						|
<!---
 | 
						|
  If the BHOS15 link ever goes dead: it's reference 30 of the zxcvbn
 | 
						|
  paper, aka https://dl.acm.org/citation.cfm?id=2699390 , in the
 | 
						|
  _Communications of the ACM_ aka CACM.  (But the ACM has it paywalled.)
 | 
						|
  .
 | 
						|
  Hooray for USENIX and IEEE: the other papers' canonical links are
 | 
						|
  not paywalled.  The Yahoo study is reference 5 in BHOS15.
 | 
						|
-->
 | 
						|
 | 
						|
[zxcvbn]: https://github.com/dropbox/zxcvbn
 | 
						|
[bhos15]: http://www.cl.cam.ac.uk/~fms27/papers/2015-BonneauHerOorSta-passwords.pdf
 | 
						|
[zxcvbn-paper]: https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_wheeler.pdf
 | 
						|
[bon12]: http://ieeexplore.ieee.org/document/6234435/
 | 
						|
 | 
						|
## Messages and history
 | 
						|
 | 
						|
- Zulip message content is rendered using a specialized Markdown
 | 
						|
  parser which escapes content to protect against cross-site scripting
 | 
						|
  attacks.
 | 
						|
 | 
						|
- Zulip supports both public streams and private streams.
 | 
						|
 | 
						|
  - Any non-guest user can join any public stream in the organization,
 | 
						|
    and can view the complete message history of any public stream
 | 
						|
    without joining the stream. Guests can only access streams that
 | 
						|
    another user adds them to.
 | 
						|
 | 
						|
  - Organization owners and administrators can see and modify most
 | 
						|
    aspects of a private stream, including the membership and
 | 
						|
    estimated traffic. Owners and administrators generally cannot see
 | 
						|
    messages sent to private streams or do things that would
 | 
						|
    indirectly give them access to those messages, like adding members
 | 
						|
    or changing the stream privacy settings.
 | 
						|
 | 
						|
  - Non-admins cannot easily see which private streams exist, or interact
 | 
						|
    with them in any way until they are added. Given a stream name, they can
 | 
						|
    figure out whether a stream with that name exists, but cannot see any
 | 
						|
    other details about the stream.
 | 
						|
 | 
						|
  - See [Stream permissions](https://zulip.com/help/stream-permissions) for more details.
 | 
						|
 | 
						|
- Zulip supports editing the content and topics of messages that have
 | 
						|
  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
 | 
						|
  to the original. In all configurations:
 | 
						|
 | 
						|
  - Message content can only ever be modified by the original author.
 | 
						|
 | 
						|
  - Any message visible to an organization owner or administrator can
 | 
						|
    be deleted at any time by that administrator.
 | 
						|
 | 
						|
  - See
 | 
						|
    [Configuring message editing and deletion](https://zulip.com/help/configure-message-editing-and-deletion)
 | 
						|
    for more details.
 | 
						|
 | 
						|
## Users and bots
 | 
						|
 | 
						|
- There are several types of users in a Zulip organization: organization
 | 
						|
  owners, organization administrators, members (normal users), guests,
 | 
						|
  and bots.
 | 
						|
 | 
						|
- Owners and administrators have the ability to deactivate and
 | 
						|
  reactivate other human and bot users, archive streams, add/remove
 | 
						|
  administrator privileges, as well as change configuration for the
 | 
						|
  organization.
 | 
						|
 | 
						|
  Being an organization administrator does not generally provide the ability
 | 
						|
  to read other users' private messages or messages sent to private
 | 
						|
  streams to which the administrator is not subscribed. There are two
 | 
						|
  exceptions:
 | 
						|
 | 
						|
  - Organization owners may get access to private messages via some types of
 | 
						|
    [data export](https://zulip.com/help/export-your-organization).
 | 
						|
 | 
						|
  - Administrators can change the ownership of a bot. If a bot is subscribed
 | 
						|
    to a private stream, then an administrator can indirectly get access to
 | 
						|
    stream messages by taking control of the bot, though the access will be
 | 
						|
    limited to what the bot can do. (E.g. incoming webhook bots cannot read
 | 
						|
    messages.)
 | 
						|
 | 
						|
- Every Zulip user has an API key, available on the settings page.
 | 
						|
  This API key can be used to do essentially everything the user can
 | 
						|
  do; for that reason, users should keep their API key safe. Users
 | 
						|
  can rotate their own API key if it is accidentally compromised.
 | 
						|
 | 
						|
- To properly remove a user's access to a Zulip team, it does not
 | 
						|
  suffice to change their password or deactivate their account in a
 | 
						|
  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.
 | 
						|
 | 
						|
- The Zulip mobile apps authenticate to the server by sending the
 | 
						|
  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.
 | 
						|
 | 
						|
- Guest users are like Members, but they do not have automatic access
 | 
						|
  to public streams.
 | 
						|
 | 
						|
- Zulip supports several kinds of bots with different capabilities.
 | 
						|
 | 
						|
  - Incoming webhook bots can only send messages into Zulip.
 | 
						|
  - Outgoing webhook bots and Generic bots can essentially do anything a
 | 
						|
    non-administrator user can, with a few exceptions (e.g. a bot cannot
 | 
						|
    log in to the web application, register for mobile push
 | 
						|
    notifications, or create other bots).
 | 
						|
  - Bots with the `can_forge_sender` permission can send messages that appear to have been sent by
 | 
						|
    another user. They also have the ability to see the names of all
 | 
						|
    streams, including private streams. This is important for implementing
 | 
						|
    integrations like the Jabber, IRC, and Zephyr mirrors.
 | 
						|
 | 
						|
    These bots cannot be created by Zulip users, including
 | 
						|
    organization owners. They can only be created on the command
 | 
						|
    line (via `manage.py change_user_role can_forge_sender`).
 | 
						|
 | 
						|
## User-uploaded content and user-generated requests
 | 
						|
 | 
						|
- Zulip supports user-uploaded files. Ideally they should be hosted
 | 
						|
  from a separate domain from the main Zulip server to protect against
 | 
						|
  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
 | 
						|
  stored in Amazon S3. It would not be difficult to add additional
 | 
						|
  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
 | 
						|
  file to be accessed by the unauthorized user. Of course, any
 | 
						|
  such authorized user could have just downloaded and sent the file
 | 
						|
  instead of the URL, so this is arguably pretty good protection.)
 | 
						|
  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), we
 | 
						|
  provide additional layers of protection in both backends as well.
 | 
						|
 | 
						|
  In the Zulip S3 backend, the random URLs to access files that are
 | 
						|
  presented to users don't actually host the content. Instead, the S3
 | 
						|
  backend verifies that the user has a valid Zulip session in the
 | 
						|
  relevant organization (and that has access to a Zulip message linking to
 | 
						|
  the file), and if so, then redirects the browser to a temporary S3
 | 
						|
  URL for the file that expires a short time later. In this way,
 | 
						|
  possessing a URL to a secret file in Zulip does not provide
 | 
						|
  unauthorized users with access to that file.
 | 
						|
 | 
						|
  We have a similar protection for the `LOCAL_UPLOADS_DIR` backend.
 | 
						|
  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).
 | 
						|
 | 
						|
- Zulip supports using the [go-camo][go-camo] image proxy to proxy content like
 | 
						|
  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.
 | 
						|
 | 
						|
- By default, Zulip will provide image previews inline in the body of
 | 
						|
  messages when a message contains a link to an image. You can
 | 
						|
  control this using the `INLINE_IMAGE_PREVIEW` setting.
 | 
						|
 | 
						|
- Zulip may make outgoing HTTP connections to other servers in a
 | 
						|
  number of cases:
 | 
						|
 | 
						|
  - 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)
 | 
						|
 | 
						|
- Notably, these first 3 features give end users (limited) control to cause
 | 
						|
  the Zulip server to make HTTP requests on their behalf. Because of this,
 | 
						|
  Zulip routes all outgoing outgoing HTTP requests [through
 | 
						|
  Smokescreen][smokescreen-setup] to ensure that Zulip cannot be
 | 
						|
  used to execute [SSRF attacks][ssrf] against other systems on an
 | 
						|
  internal corporate network. The default Smokescreen configuration
 | 
						|
  denies access to all non-public IP addresses, including 127.0.0.1.
 | 
						|
 | 
						|
[go-camo]: https://github.com/cactus/go-camo
 | 
						|
[ssrf]: https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
 | 
						|
[smokescreen-setup]: ../production/deployment.html#customizing-the-outgoing-http-proxy
 | 
						|
 | 
						|
## Final notes and security response
 | 
						|
 | 
						|
If you find some aspect of Zulip that seems inconsistent with this
 | 
						|
security model, please report it to security@zulip.com so that we can
 | 
						|
investigate and coordinate an appropriate security release if needed.
 | 
						|
 | 
						|
Zulip security announcements will be sent to
 | 
						|
zulip-announce@googlegroups.com, so you should subscribe if you are
 | 
						|
running Zulip in production.
 |