mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-11-22 02:17:26 +00:00
prevent accidental domain control validation hijacking by limiting use of admin@ etc. addresses in users/aliases
This commit is contained in:
parent
cb656f9ef4
commit
072aeca1be
@ -7,6 +7,7 @@ In Development
|
|||||||
Mail:
|
Mail:
|
||||||
|
|
||||||
* POP3S is now enabled (port 995).
|
* POP3S is now enabled (port 995).
|
||||||
|
* In order to guard against misconfiguration that can lead to domain control validation hijacking, email addresses that begin with admin, administrator, postmaster, hostmaster, and webmaster can no longer be used for (new) mail user accounts, and aliases for these addresses may direct mail only to the box's administrator(s).
|
||||||
|
|
||||||
System:
|
System:
|
||||||
|
|
||||||
|
@ -92,6 +92,13 @@ def prettify_idn_email_address(email):
|
|||||||
# Failed to decode IDNA. Should never happen.
|
# Failed to decode IDNA. Should never happen.
|
||||||
return email
|
return email
|
||||||
|
|
||||||
|
def is_dcv_address(email):
|
||||||
|
email = email.lower()
|
||||||
|
for localpart in ("admin", "administrator", "postmaster", "hostmaster", "webmaster"):
|
||||||
|
if email.startswith(localpart+"@") or email.startswith(localpart+"+"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def open_database(env, with_connection=False):
|
def open_database(env, with_connection=False):
|
||||||
conn = sqlite3.connect(env["STORAGE_ROOT"] + "/mail/users.sqlite")
|
conn = sqlite3.connect(env["STORAGE_ROOT"] + "/mail/users.sqlite")
|
||||||
if not with_connection:
|
if not with_connection:
|
||||||
@ -271,6 +278,10 @@ def add_mail_user(email, pw, privs, env):
|
|||||||
return ("Invalid email address.", 400)
|
return ("Invalid email address.", 400)
|
||||||
elif not validate_email(email, mode='user'):
|
elif not validate_email(email, mode='user'):
|
||||||
return ("User account email addresses may only use the ASCII letters A-Z, the digits 0-9, underscore (_), hyphen (-), and period (.).", 400)
|
return ("User account email addresses may only use the ASCII letters A-Z, the digits 0-9, underscore (_), hyphen (-), and period (.).", 400)
|
||||||
|
elif is_dcv_address(email):
|
||||||
|
# Make domain control validation hijacking a little harder to mess up by preventing the usual
|
||||||
|
# addresses used for DCV from being user accounts.
|
||||||
|
return ("You may not make a user account for that address because it is frequently used for domain control validation. Use an alias instead if necessary.", 400)
|
||||||
|
|
||||||
# validate password
|
# validate password
|
||||||
validate_password(pw)
|
validate_password(pw)
|
||||||
@ -369,12 +380,13 @@ def remove_mail_user(email, env):
|
|||||||
def parse_privs(value):
|
def parse_privs(value):
|
||||||
return [p for p in value.split("\n") if p.strip() != ""]
|
return [p for p in value.split("\n") if p.strip() != ""]
|
||||||
|
|
||||||
def get_mail_user_privileges(email, env):
|
def get_mail_user_privileges(email, env, empty_on_error=False):
|
||||||
# get privs
|
# get privs
|
||||||
c = open_database(env)
|
c = open_database(env)
|
||||||
c.execute('SELECT privileges FROM users WHERE email=?', (email,))
|
c.execute('SELECT privileges FROM users WHERE email=?', (email,))
|
||||||
rows = c.fetchall()
|
rows = c.fetchall()
|
||||||
if len(rows) != 1:
|
if len(rows) != 1:
|
||||||
|
if empty_on_error: return []
|
||||||
return ("That's not a user (%s)." % email, 400)
|
return ("That's not a user (%s)." % email, 400)
|
||||||
return parse_privs(rows[0][0])
|
return parse_privs(rows[0][0])
|
||||||
|
|
||||||
@ -415,11 +427,15 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru
|
|||||||
source = sanitize_idn_email_address(source)
|
source = sanitize_idn_email_address(source)
|
||||||
|
|
||||||
# validate source
|
# validate source
|
||||||
if source.strip() == "":
|
source = source.strip()
|
||||||
|
if source == "":
|
||||||
return ("No incoming email address provided.", 400)
|
return ("No incoming email address provided.", 400)
|
||||||
if not validate_email(source, mode='alias'):
|
if not validate_email(source, mode='alias'):
|
||||||
return ("Invalid incoming email address (%s)." % source, 400)
|
return ("Invalid incoming email address (%s)." % source, 400)
|
||||||
|
|
||||||
|
# extra checks for email addresses used in domain control validation
|
||||||
|
is_dcv_source = is_dcv_address(source)
|
||||||
|
|
||||||
# validate destination
|
# validate destination
|
||||||
dests = []
|
dests = []
|
||||||
destination = destination.strip()
|
destination = destination.strip()
|
||||||
@ -427,9 +443,10 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru
|
|||||||
# Postfix allows a single @domain.tld as the destination, which means
|
# Postfix allows a single @domain.tld as the destination, which means
|
||||||
# the local part on the address is preserved in the rewrite. We must
|
# the local part on the address is preserved in the rewrite. We must
|
||||||
# try to convert Unicode to IDNA first before validating that it's a
|
# try to convert Unicode to IDNA first before validating that it's a
|
||||||
# legitimate alias address.
|
# legitimate alias address. Don't allow this sort of rewriting for
|
||||||
|
# DCV source addresses.
|
||||||
d1 = sanitize_idn_email_address(destination)
|
d1 = sanitize_idn_email_address(destination)
|
||||||
if validate_email(d1, mode='alias'):
|
if validate_email(d1, mode='alias') and not is_dcv_source:
|
||||||
dests.append(d1)
|
dests.append(d1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -442,6 +459,11 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru
|
|||||||
email = sanitize_idn_email_address(email) # Unicode => IDNA
|
email = sanitize_idn_email_address(email) # Unicode => IDNA
|
||||||
if not validate_email(email):
|
if not validate_email(email):
|
||||||
return ("Invalid destination email address (%s)." % email, 400)
|
return ("Invalid destination email address (%s)." % email, 400)
|
||||||
|
if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(email, env, empty_on_error=True):
|
||||||
|
# Make domain control validation hijacking a little harder to mess up by
|
||||||
|
# requiring aliases for email addresses typically used in DCV to forward
|
||||||
|
# only to accounts that are administrators on this system.
|
||||||
|
return ("This alias can only have administrators of this system as destinations because the address is frequently used for domain control validation.", 400)
|
||||||
dests.append(email)
|
dests.append(email)
|
||||||
if len(destination) == 0:
|
if len(destination) == 0:
|
||||||
return ("No destination email address(es) provided.", 400)
|
return ("No destination email address(es) provided.", 400)
|
||||||
|
Loading…
Reference in New Issue
Block a user