[exim-announce] Exim Security: 4.77 hardening of match_* con…

Top Page

Reply to this message
Author: Phil Pennock
Date:  
To: exim-announce
Subject: [exim-announce] Exim Security: 4.77 hardening of match_* conditions
Folks,

The forthcoming Exim 4.77 release (now in Release Candidate state) will
have a backwards-incompatible change by default, in configuration
parsing of four expansion conditions: "match_address", "match_domain",
"match_ip" & "match_local_part".

Exim's treatment of these options has matched the documentation, but
does not appear to match the expectations of many administrators, who as
a result may have created configurations which have a security flaw,
leading to problems such as SQL injection.

Exim's configuration language generally provides a lot of power, but
also requires the administrator to use functions like ${quote_mysql:...}
when constructing an SQL query. We let you shoot yourself in the foot.
We also provide an ${expand:...} operator, to let you re-expand strings;
hopefully it is obvious that re-expanding data extracted from an email's
headers is a security problem. This is much like the "eval"
functionality of many scripting languages.

In the case of the match_* operators, the problem is more subtle and too
many folks did not understand the documented behaviour and so
inadvertently created similar situations, using expansion conditions
more powerful than they realised.

The four expansion conditions "match_address", "match_domain",
"match_ip" & "match_local_part" all take two arguments. The first is
something to look for, the second is a list of data to match against.
In common with Exim typed list handling, these lists can contain more
than just literal string data. In particular, they can contain
arbitrary database lookups with arbitrary SQL execution.

At this point, it should be clear that using a value derived from an
email header or an SMTP exchange as the specification of the list is
somewhat unwise. And yet, experience has shown that, time and again, we
are seeing people use conditions such as:
match_address{$something}{$another_address}
where $another_address is extracted from untrustworthy sources.

Going forward, by default those four expansion conditions will be
unusually different from other conditions, in that the contents of the
second string WILL NOT BE SUBJECT TO STRING EXPANSION.

Thus this works:
${if match_domain{example.org}{+local_domains}}
but this does not, because the '$' is left as a literal '$':
${if match_domain{example.org}{$sender_domain}}

Note that the named domainlist "local_domains" here can still contain
arbitrary data lookups, as before. Those can still refer to variables
derived from an email, as before. It's just that the specification of
what the list is that Exim should compare against is no longer expanded
and thus can not directly refer to data from an email.

This may break some existing configurations, but if so then we believe
that your current configuration may have unpleasant security
implications. Because we recognise that there may be safe usage of
variables _not_ derived directly from the email being handled there is a
new build-time option to keep the old behaviour, "EXPAND_LISTMATCH_RHS".
Please let us know if you need to use this built-time option, so that we
can consider providing safer alternatives. If we do not hear of cases
where this was needed, then we may remove the build-time option in a
future release.

Two new expansion conditions have been added to Exim to help with
this change. They are "inlist" and "inlisti". The check:
${if inlist{needle}{foo:needle:bar}}
is equivalent to the existing:
${if forany{foo:needle:bar}{eq{$item}{needle}}}
but is a little easier to understand and matches the existing
match_<foo> layout. The "inlisti" condition is similar, but uses "eqi"
instead if "eq" for case-insensitive comparison.

The second parameter here is subject to string expansion, but the
contents are not subject to lookup expansion, including named list
handling.

So it's:
${if inlist{example.org}{${complicated_expansion_giving_list}}}
${if inlist{example.org}{<, example.org, example.com}}
${if match_domain{example.org}{+my_domain_list}}

It is likely that current incorrect usages of the match_<foo> conditions
can be migrated to use of either "eqi" for direct string equality
checks, or "inlisti" for looking in ${expanded_data}.

Regards,
- -Phil Pennock, pp The Exim Maintainers