Beware of Google App Engine SDK

Wednesday, April 9, 2008

An easily exploited vulnerability in Google App Engine's SDK can put your development servers at risk. While this bug is trivial to fix, engineers at Google have declined to address the vulnerability, so be cautious when using the SDK to develop your web service.

The Vulnerability

The vulnerability exists in the mail API of the Google App Engine SDK, specifically in google/appengine/api/mail_stub.py:

def _SendSendmail(self, mime_message,
               popen=subprocess.Popen,
               sendmail_command='sendmail'):

 try:
   tos = [mime_message[to] for to in ['To', 'Cc', 'Bcc'] if mime_message[to]]
  sendmail_command = '%s %s' % (sendmail_command, ' '.join(tos))

   try:
   child = popen(sendmail_command,
               shell=True,
               stdin=subprocess.PIPE,
               stdout=subprocess.PIPE)

The code constructs the command line arguments that will be passed to sendmail and executed by the shell interpreter (eg. "/bin/sh -c 'sendmail_command'"). Unfortunately, since the To/Cc/Bcc entries are not sanitized before the command line arguments are constructed, an attacker could specify a malicious input that causes the shell interpreter to execute any command the attacker desires.

For example, imagine a web app that has some sort of registration where the user types in his email address and a confirmation link/code is sent to that email address to complete the registration. If the attacker specifies "jon@oberheide.org | rm -rf /" as his email address, the resulting command executed by the shell interpreter would be "sendmail jon@oberheide.org | rm -rf /", which of course would have a rather undesirable effect.

Google's Response

I discovered and reported this vulnerability mere hours after the Google App Engine SDK was made available. In fact, it was only the 4th issue that had been posted in their bug tracker. Unfortunately, the response was less than satisfactory, given that the bug could have been patched in the time it took to write the response.

Thanks for the heads up! The downloadable SDK and the python API stubs are only
intended for local development, not for actually serving apps live. Gooogle App
Engine itself doesn't use the stubs for deployed apps.

So while the issue only affects the SDK and not the actual GAE infrastructure, Google is distributing the SDK and encouraging developers to use it. If you develop and distribute software to end users, it is your responsibility to fix vulnerabilities that may put your users at risk. Anything else is just irresponsible.

Addressing the Vulnerability

If your web app uses the Sendmail functionality of the GAE SDK and is deployed in a scenario where untrusted input may be supplied for the To/Cc/Bcc fields, I strongly recommend you patch the SDK or switch to another method of sending mail from your web app.

The patch to address the vulnerability is trivial and simply specifies shell=False when using popen. Using shell=False invokes execve(2) instead of the shell interpreter, eliminating the potential for command injection from the attacker. Since we're using execve(2), we need the full path to the sendmail executable, so be sure to pass the correct path for sendmail on your system.

Index: google/appengine/api/mail_stub.py
===================================================================
--- google/appengine/api/mail_stub.py  (revision 28)
+++ google/appengine/api/mail_stub.py  (working copy)
@@ -156,7 +156,7 @@

  def _SendSendmail(self, mime_message,
                    popen=subprocess.Popen,
-                   sendmail_command='sendmail'):
+                   sendmail_command='/usr/sbin/sendmail'):
    """Send MIME message via sendmail, if exists on computer.

    Attempts to send email via sendmail. Any IO failure, including
@@ -168,11 +168,11 @@
    """
    try:
      tos = [mime_message[to] for to in ['To', 'Cc', 'Bcc'] if mime_message[to]]
-     sendmail_command = '%s %s' % (sendmail_command, ' '.join(tos))
+     sendmail_command = [sendmail_command] + tos

      try:
        child = popen(sendmail_command,
-                     shell=True,
+                     shell=False,
                      stdin=subprocess.PIPE,
                      stdout=subprocess.PIPE)
      except (IOError, OSError), e:
@@ -191,7 +191,7 @@
  def _Send(self, request, response, log=logging.info,
            smtp_lib=smtplib.SMTP,
            popen=subprocess.Popen,
-           sendmail_command='sendmail'):
+           sendmail_command='/usr/sbin/sendmail'):
    """Implementation of MailServer::Send().

    Logs email message. Contents of attachments are not shown, only

Copyright © 2021 - Jon Oberheide