Anti tamper: avoid submitting any suffix

This commit is contained in:
Son NK 2020-05-02 12:15:03 +02:00
parent 5e174b08f4
commit db92003e5f
4 changed files with 32 additions and 16 deletions

View file

@ -123,6 +123,7 @@ DB_URI = os.environ["DB_URI"]
# Flask secret # Flask secret
FLASK_SECRET = os.environ["FLASK_SECRET"] FLASK_SECRET = os.environ["FLASK_SECRET"]
MAILBOX_SECRET = FLASK_SECRET + "mailbox" MAILBOX_SECRET = FLASK_SECRET + "mailbox"
CUSTOM_ALIAS_SECRET = FLASK_SECRET + "custom_alias"
# AWS # AWS
AWS_REGION = "eu-west-3" AWS_REGION = "eu-west-3"

View file

@ -42,7 +42,7 @@
<div class="col-sm-6 p-1"> <div class="col-sm-6 p-1">
<select class="form-control" name="suffix"> <select class="form-control" name="suffix">
{% for suffix in suffixes %} {% for suffix in suffixes %}
<option value="{{ suffix[1] }}"> <option value="{{ suffix[2] }}">
{% if suffix[0] %} {% if suffix[0] %}
{{ suffix[1] }} (your domain) {{ suffix[1] }} (your domain)
{% else %} {% else %}

View file

@ -1,7 +1,12 @@
from flask import render_template, redirect, url_for, flash, request from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user from flask_login import login_required, current_user
from itsdangerous import TimestampSigner, SignatureExpired
from app.config import DISABLE_ALIAS_SUFFIX, ALIAS_DOMAINS from app.config import (
DISABLE_ALIAS_SUFFIX,
ALIAS_DOMAINS,
CUSTOM_ALIAS_SECRET,
)
from app.dashboard.base import dashboard_bp from app.dashboard.base import dashboard_bp
from app.email_utils import email_belongs_to_alias_domains, get_email_domain_part from app.email_utils import email_belongs_to_alias_domains, get_email_domain_part
from app.extensions import db from app.extensions import db
@ -9,6 +14,8 @@ from app.log import LOG
from app.models import Alias, CustomDomain, DeletedAlias, Mailbox from app.models import Alias, CustomDomain, DeletedAlias, Mailbox
from app.utils import convert_to_id, random_word, word_exist from app.utils import convert_to_id, random_word, word_exist
signer = TimestampSigner(CUSTOM_ALIAS_SECRET)
@dashboard_bp.route("/custom_alias", methods=["GET", "POST"]) @dashboard_bp.route("/custom_alias", methods=["GET", "POST"])
@login_required @login_required
@ -24,27 +31,24 @@ def custom_alias():
return redirect(url_for("dashboard.index")) return redirect(url_for("dashboard.index"))
user_custom_domains = [cd.domain for cd in current_user.verified_custom_domains()] user_custom_domains = [cd.domain for cd in current_user.verified_custom_domains()]
# List of (is_custom_domain, alias-suffix) # List of (is_custom_domain, alias-suffix, time-signed alias-suffix)
suffixes = [] suffixes = []
# put custom domain first # put custom domain first
for alias_domain in user_custom_domains: for alias_domain in user_custom_domains:
suffixes.append((True, "@" + alias_domain)) suffix = "@" + alias_domain
suffixes.append((True, suffix, signer.sign(suffix).decode()))
# then default domain # then default domain
for domain in ALIAS_DOMAINS: for domain in ALIAS_DOMAINS:
suffixes.append( suffix = ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain
( suffixes.append((False, suffix, signer.sign(suffix).decode()))
False,
("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain,
)
)
mailboxes = [mb.email for mb in current_user.mailboxes()] mailboxes = [mb.email for mb in current_user.mailboxes()]
if request.method == "POST": if request.method == "POST":
alias_prefix = request.form.get("prefix") alias_prefix = request.form.get("prefix")
alias_suffix = request.form.get("suffix") signed_suffix = request.form.get("suffix")
mailbox_email = request.form.get("mailbox") mailbox_email = request.form.get("mailbox")
alias_note = request.form.get("note") alias_note = request.form.get("note")
@ -55,6 +59,18 @@ def custom_alias():
flash("Something went wrong, please retry", "warning") flash("Something went wrong, please retry", "warning")
return redirect(url_for("dashboard.custom_alias")) return redirect(url_for("dashboard.custom_alias"))
# hypothesis: user will click on the button in the 300 secs
try:
alias_suffix = signer.unsign(signed_suffix, max_age=300).decode()
except SignatureExpired:
LOG.error("Alias creation time expired")
flash("Alias creation time is expired, please retry", "warning")
return redirect(url_for("dashboard.custom_alias"))
except Exception:
LOG.error("Alias suffix is tampered, user %s", current_user)
flash("Unknown error, refresh the page", "error")
return redirect(url_for("dashboard.custom_alias"))
if verify_prefix_suffix( if verify_prefix_suffix(
current_user, alias_prefix, alias_suffix, user_custom_domains current_user, alias_prefix, alias_suffix, user_custom_domains
): ):

View file

@ -1,6 +1,7 @@
from flask import url_for from flask import url_for
from app.config import EMAIL_DOMAIN from app.config import EMAIL_DOMAIN
from app.dashboard.views.custom_alias import signer
from app.extensions import db from app.extensions import db
from app.models import Mailbox from app.models import Mailbox
from app.utils import random_word from app.utils import random_word
@ -12,14 +13,12 @@ def test_add_alias_success(flask_client):
db.session.commit() db.session.commit()
word = random_word() word = random_word()
suffix = f".{word}@{EMAIL_DOMAIN}"
suffix = signer.sign(suffix).decode()
r = flask_client.post( r = flask_client.post(
url_for("dashboard.custom_alias"), url_for("dashboard.custom_alias"),
data={ data={"prefix": "prefix", "suffix": suffix, "mailbox": user.email,},
"prefix": "prefix",
"suffix": f".{word}@{EMAIL_DOMAIN}",
"mailbox": user.email,
},
follow_redirects=True, follow_redirects=True,
) )