1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-01 19:22:42 +01:00

Prevent inbound processing of the "void/placeholder" address and other reserved addresses

Summary:
Depends on D19952. Ref T13222. Never process mail targets if they match:

  - The "default" address which we send mail "From".
  - The "void" address which we use as a placholder "To" when we only have "CC" addresses.
  - Any address from a list of reserved/administrative names.

The first two prevent loops. The third one prevents abuse.

There's a reasonably well-annotated list of reservations and reasons here:

https://webmasters.stackexchange.com/questions/104811/is-there-any-list-of-email-addresses-reserved-because-of-security-concerns-for-a

Stuff like `support@` seems fine; stuff like `ssladmin@` might let you get SSL certs issued for a domain you don't control.

Also, forbid users from creating application emails with these reserved addresses.

Finally, build the default and void addresses somewhat more cleverly.

Test Plan: Added unit tests, tried to configured reserved addresses, hit the default/void cases manually with `bin/mail receive-test`.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: olexiy.myronenko

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D19953
This commit is contained in:
epriestley 2019-01-03 15:45:58 -08:00
parent e3aa043a02
commit a37b28ef79
6 changed files with 112 additions and 9 deletions

View file

@ -191,10 +191,7 @@ EODOC
$this->newOption('cluster.mailers', 'cluster.mailers', array()) $this->newOption('cluster.mailers', 'cluster.mailers', array())
->setHidden(true) ->setHidden(true)
->setDescription($mailers_description), ->setDescription($mailers_description),
$this->newOption( $this->newOption('metamta.default-address', 'string', null)
'metamta.default-address',
'string',
'noreply@phabricator.example.com')
->setDescription(pht('Default "From" address.')), ->setDescription(pht('Default "From" address.')),
$this->newOption( $this->newOption(
'metamta.one-mail-per-recipient', 'metamta.one-mail-per-recipient',

View file

@ -104,6 +104,16 @@ final class PhabricatorMetaMTAApplicationEmailEditor
pht('Invalid'), pht('Invalid'),
pht('Email address is not formatted properly.')); pht('Email address is not formatted properly.'));
} }
$address = new PhutilEmailAddress($email);
if (PhabricatorMailUtil::isReservedAddress($address)) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Reserved'),
pht(
'This email address is reserved. Choose a different '.
'address.'));
}
} }
$missing = $this->validateIsEmptyTextField( $missing = $this->validateIsEmptyTextField(

View file

@ -41,4 +41,30 @@ final class PhabricatorMailReceiverTestCase extends PhabricatorTestCase {
} }
} }
public function testReservedAddresses() {
$default_address = id(new PhabricatorMetaMTAMail())
->newDefaultEmailAddress();
$void_address = id(new PhabricatorMetaMTAMail())
->newVoidEmailAddress();
$map = array(
'alincoln@example.com' => false,
'sysadmin@example.com' => true,
'hostmaster@example.com' => true,
'"Walter Ebmaster" <webmaster@example.com>' => true,
(string)$default_address => true,
(string)$void_address => true,
);
foreach ($map as $raw_address => $expect) {
$address = new PhutilEmailAddress($raw_address);
$this->assertEqual(
$expect,
PhabricatorMailUtil::isReservedAddress($address),
pht('Reserved: %s', $raw_address));
}
}
} }

View file

@ -713,7 +713,7 @@ final class PhabricatorMetaMTAMail
$actors = $this->loadAllActors(); $actors = $this->loadAllActors();
$deliverable_actors = $this->filterDeliverableActors($actors); $deliverable_actors = $this->filterDeliverableActors($actors);
$default_from = PhabricatorEnv::getEnvConfig('metamta.default-address'); $default_from = (string)$this->newDefaultEmailAddress();
if (empty($params['from'])) { if (empty($params['from'])) {
$mailer->setFrom($default_from); $mailer->setFrom($default_from);
} }
@ -1463,18 +1463,33 @@ final class PhabricatorMetaMTAMail
} }
private function newMailDomain() { private function newMailDomain() {
$domain = PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain');
if (strlen($domain)) {
return $domain;
}
$install_uri = PhabricatorEnv::getURI('/'); $install_uri = PhabricatorEnv::getURI('/');
$install_uri = new PhutilURI($install_uri); $install_uri = new PhutilURI($install_uri);
return $install_uri->getDomain(); return $install_uri->getDomain();
} }
public function newVoidEmailAddress() { public function newDefaultEmailAddress() {
$raw_address = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (strlen($raw_address)) {
return new PhutilEmailAddress($raw_address);
}
$domain = $this->newMailDomain(); $domain = $this->newMailDomain();
$address = "void-recipient@{$domain}"; $address = "noreply@{$domain}";
return new PhutilEmailAddress($address); return new PhutilEmailAddress($address);
} }
public function newVoidEmailAddress() {
return $this->newDefaultEmailAddress();
}
/* -( Routing )------------------------------------------------------------ */ /* -( Routing )------------------------------------------------------------ */

View file

@ -161,10 +161,19 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
->setFilterMethod('isEnabled') ->setFilterMethod('isEnabled')
->execute(); ->execute();
$targets = $this->newTargetAddresses();
foreach ($targets as $key => $target) {
// Never accept any reserved address as a mail target. This prevents
// security issues around "hostmaster@" and bad behavior with
// "noreply@".
if (PhabricatorMailUtil::isReservedAddress($target)) {
unset($targets[$key]);
continue;
}
}
$any_accepted = false; $any_accepted = false;
$receiver_exception = null; $receiver_exception = null;
$targets = $this->newTargetAddresses();
foreach ($receivers as $receiver) { foreach ($receivers as $receiver) {
$receiver = id(clone $receiver) $receiver = id(clone $receiver)
->setViewer($viewer); ->setViewer($viewer);

View file

@ -62,4 +62,50 @@ final class PhabricatorMailUtil
return ($u->getAddress() === $v->getAddress()); return ($u->getAddress() === $v->getAddress());
} }
public static function isReservedAddress(PhutilEmailAddress $address) {
$address = self::normalizeAddress($address);
$local = $address->getLocalPart();
$reserved = array(
'admin',
'administrator',
'hostmaster',
'list',
'list-request',
'majordomo',
'postmaster',
'root',
'ssl-admin',
'ssladmin',
'ssladministrator',
'sslwebmaster',
'sysadmin',
'uucp',
'webmaster',
'noreply',
'no-reply',
);
$reserved = array_fuse($reserved);
if (isset($reserved[$local])) {
return true;
}
$default_address = id(new PhabricatorMetaMTAMail())
->newDefaultEmailAddress();
if (self::matchAddresses($address, $default_address)) {
return true;
}
$void_address = id(new PhabricatorMetaMTAMail())
->newVoidEmailAddress();
if (self::matchAddresses($address, $void_address)) {
return true;
}
return false;
}
} }