Anti tamper: avoid submitting any suffix
This commit is contained in:
parent
5e174b08f4
commit
db92003e5f
|
@ -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"
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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
|
||||||
):
|
):
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue