SendEmail: Protect users against vulnerable logmailers (#939)
glog is used on a variety of systems, and we must assume that some of them still use vulnerable mailers that have bugs or "interesting features" such as https://nvd.nist.gov/vuln/detail/CVE-2004-2771. Let's protect users against accidental shell injection by validating the email addresses against a slightly stricter version of the regex used by HTML5 to validate addresses[1]. This should prevent triggering any unexpected behavior in these tools. Also add some basic unit tests for the SendEmail method. [1] https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
This commit is contained in:
parent
6fbc93a7e4
commit
64827572c0
@ -470,6 +470,9 @@ DECLARE_bool(stop_logging_if_full_disk);
|
|||||||
// Use UTC time for logging
|
// Use UTC time for logging
|
||||||
DECLARE_bool(log_utc_time);
|
DECLARE_bool(log_utc_time);
|
||||||
|
|
||||||
|
// Mailer used to send logging email
|
||||||
|
DECLARE_string(logmailer);
|
||||||
|
|
||||||
// Log messages below the GOOGLE_STRIP_LOG level will be compiled away for
|
// Log messages below the GOOGLE_STRIP_LOG level will be compiled away for
|
||||||
// security reasons. See LOG(severtiy) below.
|
// security reasons. See LOG(severtiy) below.
|
||||||
|
|
||||||
|
|||||||
@ -560,17 +560,20 @@ struct FlagSaver {
|
|||||||
: v_(FLAGS_v),
|
: v_(FLAGS_v),
|
||||||
stderrthreshold_(FLAGS_stderrthreshold),
|
stderrthreshold_(FLAGS_stderrthreshold),
|
||||||
logtostderr_(FLAGS_logtostderr),
|
logtostderr_(FLAGS_logtostderr),
|
||||||
alsologtostderr_(FLAGS_alsologtostderr) {}
|
alsologtostderr_(FLAGS_alsologtostderr),
|
||||||
|
logmailer_(FLAGS_logmailer) {}
|
||||||
~FlagSaver() {
|
~FlagSaver() {
|
||||||
FLAGS_v = v_;
|
FLAGS_v = v_;
|
||||||
FLAGS_stderrthreshold = stderrthreshold_;
|
FLAGS_stderrthreshold = stderrthreshold_;
|
||||||
FLAGS_logtostderr = logtostderr_;
|
FLAGS_logtostderr = logtostderr_;
|
||||||
FLAGS_alsologtostderr = alsologtostderr_;
|
FLAGS_alsologtostderr = alsologtostderr_;
|
||||||
|
FLAGS_logmailer = logmailer_;
|
||||||
}
|
}
|
||||||
int v_;
|
int v_;
|
||||||
int stderrthreshold_;
|
int stderrthreshold_;
|
||||||
bool logtostderr_;
|
bool logtostderr_;
|
||||||
bool alsologtostderr_;
|
bool alsologtostderr_;
|
||||||
|
std::string logmailer_;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cerrno> // for errno
|
#include <cerrno> // for errno
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <cctype> // for std::isspace
|
||||||
#ifdef GLOG_OS_WINDOWS
|
#ifdef GLOG_OS_WINDOWS
|
||||||
#include "windows/dirent.h"
|
#include "windows/dirent.h"
|
||||||
#else
|
#else
|
||||||
@ -2205,6 +2207,13 @@ static string ShellEscape(const string& src) {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim whitespace from both ends of the provided string.
|
||||||
|
static inline void trim(std::string &s) {
|
||||||
|
const auto toRemove = [](char ch) { return std::isspace(ch) == 0; };
|
||||||
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), toRemove));
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(), toRemove).base(), s.end());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// use_logging controls whether the logging functions LOG/VLOG are used
|
// use_logging controls whether the logging functions LOG/VLOG are used
|
||||||
@ -2214,6 +2223,45 @@ static bool SendEmailInternal(const char*dest, const char *subject,
|
|||||||
const char*body, bool use_logging) {
|
const char*body, bool use_logging) {
|
||||||
#ifndef GLOG_OS_EMSCRIPTEN
|
#ifndef GLOG_OS_EMSCRIPTEN
|
||||||
if (dest && *dest) {
|
if (dest && *dest) {
|
||||||
|
// Split the comma-separated list of email addresses, validate each one and
|
||||||
|
// build a sanitized new comma-separated string without whitespace.
|
||||||
|
std::istringstream ss(dest);
|
||||||
|
std::ostringstream sanitized_dests;
|
||||||
|
std::string s;
|
||||||
|
while (std::getline(ss, s, ',')) {
|
||||||
|
trim(s);
|
||||||
|
if (s.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// We validate the provided email addresses using the same regular
|
||||||
|
// expression that HTML5 uses[1], except that we require the address to
|
||||||
|
// start with an alpha-numeric character. This is because we don't want to
|
||||||
|
// allow email addresses that start with a special character, such as a
|
||||||
|
// pipe or dash, which could be misunderstood as a command-line flag by
|
||||||
|
// certain versions of `mail` that are vulnerable to command injection.[2]
|
||||||
|
// [1] https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
|
||||||
|
// [2] e.g. https://nvd.nist.gov/vuln/detail/CVE-2004-2771
|
||||||
|
if (!std::regex_match(
|
||||||
|
s,
|
||||||
|
std::regex("^[a-zA-Z0-9]"
|
||||||
|
"[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]*@[a-zA-Z0-9]"
|
||||||
|
"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]"
|
||||||
|
"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"))) {
|
||||||
|
if (use_logging) {
|
||||||
|
VLOG(1) << "Invalid destination email address:" << s;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Invalid destination email address: %s\n",
|
||||||
|
s.c_str());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!sanitized_dests.str().empty()) {
|
||||||
|
sanitized_dests << ",";
|
||||||
|
}
|
||||||
|
sanitized_dests << s;
|
||||||
|
}
|
||||||
|
dest = sanitized_dests.str().c_str();
|
||||||
|
|
||||||
if ( use_logging ) {
|
if ( use_logging ) {
|
||||||
VLOG(1) << "Trying to send TITLE:" << subject
|
VLOG(1) << "Trying to send TITLE:" << subject
|
||||||
<< " BODY:" << body << " to " << dest;
|
<< " BODY:" << body << " to " << dest;
|
||||||
|
|||||||
@ -1489,3 +1489,31 @@ TEST(LogMsgTime, gmtoff) {
|
|||||||
const long utc_max_offset = 50400;
|
const long utc_max_offset = 50400;
|
||||||
EXPECT_TRUE( (nGmtOff >= utc_min_offset) && (nGmtOff <= utc_max_offset) );
|
EXPECT_TRUE( (nGmtOff >= utc_min_offset) && (nGmtOff <= utc_max_offset) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(EmailLogging, ValidAddress) {
|
||||||
|
FlagSaver saver;
|
||||||
|
FLAGS_logmailer = "/usr/bin/true";
|
||||||
|
|
||||||
|
EXPECT_TRUE(SendEmail("example@example.com", "Example subject", "Example body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EmailLogging, MultipleAddresses) {
|
||||||
|
FlagSaver saver;
|
||||||
|
FLAGS_logmailer = "/usr/bin/true";
|
||||||
|
|
||||||
|
EXPECT_TRUE(SendEmail("example@example.com,foo@bar.com", "Example subject", "Example body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EmailLogging, InvalidAddress) {
|
||||||
|
FlagSaver saver;
|
||||||
|
FLAGS_logmailer = "/usr/bin/true";
|
||||||
|
|
||||||
|
EXPECT_FALSE(SendEmail("hello world@foo", "Example subject", "Example body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EmailLogging, MaliciousAddress) {
|
||||||
|
FlagSaver saver;
|
||||||
|
FLAGS_logmailer = "/usr/bin/true";
|
||||||
|
|
||||||
|
EXPECT_FALSE(SendEmail("!/bin/true@example.com", "Example subject", "Example body"));
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user