Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport 2.10.0 changes #7230

Merged
merged 18 commits into from
Sep 19, 2024
Merged

Backport 2.10.0 changes #7230

merged 18 commits into from
Sep 19, 2024

Conversation

legoktm
Copy link
Member

@legoktm legoktm commented Sep 18, 2024

Status

Ready for review

Description of Changes

This is a backport of all the changes released in 2.10.0 that were developed privately (it is a branch merge because we need to merge everything, and not cherry-picks like we normally do for the changelog). See https://securedrop.org/news/securedrop-2_10_0-released/ for more details.

Testing

How should the reviewer test this PR?

  • visual review
  • CI passes

Deployment

Any special considerations for deployment? n/a

legoktm and others added 18 commits August 20, 2024 00:34
Redis can require basic password authentication to further limit who can
access it. We need to set a password in `/etc/redis/redis.conf` and then
ensure all clients pass it when authenticating.

For most of our code, this is straightforward since we can set the
password in config.py and access it through `SecureDropConfig`.

However the `rqworker` has its own config file that we haven't needed
until now, so we also create a `rq_config.py` with just the redis
password.

The ansible playbook now generates a 32-byte password for redis, sets it
in config.py, rq_config.py, and /etc/redis/redis.conf, and then restarts
the redis-server systemd service.

The postinst generates a 32-byte password in the same manner, writes it
to the same set of files, and then restarts the redis-server service.

This ended up requiring the most changes, since I removed the inline
Python code from the Makefile into a separate `dev-config` script, which
now also handles creation of rq_config.py. We set the password in
/etc/redis/redis.conf and then start the redis-server service. It's also
written to `/tmp/redispasswd` for easy fetching in tests. testinfra
tests verify that that unathenticated access fails and that the
configured `rq_config.py` password works.

This addresses SEC-01-008 WP3: "Read+Write Access to Redis via UnAuth
Telnet (Low)".

Fixes <freedomofpress/securedrop-security#97>.
Collateral changes here address cases where tests have:

1. done app.get("/logout") without checking either the response code or
   the result of the actual logout operation; or

2. assumed there will be exactly one button, belonging to exactly one
   form, on any given page.

Co-authored-by: Kunal Mehta <[email protected]>
We require that all users have a strong diceware password by generating
it ourselves for them to use. However, we accept any diceware-looking
sent in the POST request, meaning that a user can deliberately set a
weak or fixed password. (It would be difficult for an attacker to do so
since they'd need to both bypass the onion service authentication and
CSRF.)

To fix this, upon generating a new password for users, we save a hash of
it in the user's session. When they save the password, we validate it
against the hash and if matches, allow it to be saved. This ends up
being mostly straightforward (outside of tests), requiring modifications
to utils.set_diceware_password() and the creation of
utils.set_pending_password() and utils.verify_pending_password().

Tests are a bit more complicated because we rely on being able to send
arbitrary POST strings to the correct endpoint, so it needs to manually
be set in the session. New tests for this functionality are added as
well.

This addresses "SEC-01-002 WP4: Possible Account Takeover via Weak
Password Policy (Low)"

Fixes <freedomofpress/securedrop-security#94>.
Validate user provided same password back to server
See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#controlling_third-party_cookies_with_samesite>
for an explanation of how SameSite="Strict" works.

We just need to set the correct Flask config value and it automatically
takes care of the rest. The journalist session code has some conditional
code to support older versions of Flask that we no longer care about.

Tests verify that `SameSite=Strict` is set on the session cookie in both
the JI and SI. Note that the `http.cookie_jar` module doesn't support
the SameSite option directly (<python/cpython#88629>)
so we need to use the `get_nonstandard_attr()` method to access the value.

Refs <freedomofpress/securedrop-security#53>.
fix(`/logout`): require `POST` for CSRF protection
Set SameSite="Strict" on all cookies for more CSRF protection
The `/admin/2fa` endpoint has an IDOR vulnerability in which changing
the value of the `uid` parameter reveals that user's TOTP secret. The
similar `/account/2fa` endpoint allows a user to see their own TOTP
secret, which we'd like to avoid as well.

Address that by requiring the TOTP secret to be checked to be passed as
a form parameter. This allows to keep the existing workflow while
removing the abilty to get arbitrary users' TOTP secrets.

Practically the `/2fa` endpoint is split into `/verify-2fa-totp` and
`/verify-2fa-hotp` endpoints that now each have their own template and
function. Both contain documentation that explains what is provided by
the user and what is looked up in the database.

Tests needed an update because various endpoints no longer redirect.
Note that no functional test changes were needed because there are no
user-facing behavioral changes.

This addresses "SEC-01-001 WP4: Arbitrary 2FA Enrollment via IDOR (medium)".

Fixes <freedomofpress/securedrop-security#92>.
Don't allow admins to look up arbitrary users' TOTP secrets via the web
In a future 2.10.1 upgrade, the global chown over /var/www/securedrop
would've blown away rq_config.py's root:www-data ownership, breaking
read access for www-data. Add an exclusion, just like the existing one
for config.py.
Ensure rq_config.py permissions are restored on next upgrade
@legoktm legoktm requested a review from a team as a code owner September 18, 2024 20:46
@rocodes rocodes self-assigned this Sep 19, 2024
Copy link
Contributor

@rocodes rocodes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@legoktm legoktm added this pull request to the merge queue Sep 19, 2024
Merged via the queue into develop with commit 139591a Sep 19, 2024
44 of 45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

5 participants