Skip to content

Instantly share code, notes, and snippets.

@kenseehart
Last active June 26, 2023 21:19
Show Gist options
  • Save kenseehart/13e2fa2ed3e13cb260939dc795ab0348 to your computer and use it in GitHub Desktop.
Save kenseehart/13e2fa2ed3e13cb260939dc795ab0348 to your computer and use it in GitHub Desktop.
yase - Yet Another Safe Eval
'''
Okay, so the python community consensus is that eval is inherently unsafe (with untrusted input), and it is impossible to
construct a safe wrapper on eval that can't be hacked.
Just because it's impossible, doesn't mean it can't be done.
Please hack this, and comment. If you can hack this, I promise to keep my mouth shut during security related
conversations for not less than six months.
Possible solution when:
- You have a small subset of the language, such as a mathematical expressions, maybe some numpy stuff, etc...
- You don't want to spend time implementing the language (because you are lazy and want it all in under 15 lines of code)
Why this approach is different:
- Use ast, not re, because the ast is harder to fool, and do so without expressing any detailed knowledge of ast.
- Whitelist instead of blacklist (because blacklisting only works if you are comprehensive, not gonna happen)
This is intended as a recipe, not an off the shelf module. No doubt you will want to tweek the whitelists to your needs.
If you do, please keep in mind that its effectiveness may have something to do with neglecting to whitelist attribute access.
If you do open up attribute access, be sure to whitelist the attributes. If at any point you find yourself plugging holes of
any kind with a blacklist mentality, just admit you've lost. Whitelisting is fundamental.
This won't prevent DOS attacks with various kinds of extreme math. AFAIK, that can only effectively be dealt with at the
process level. I'm pretty sure there is no lexical approach to preventing extreme math in a sufficiently useful tool.
I will consider a hack successful if you can obtain access to os.system().
Released to public domain by Ken Seehart, 2017
github: kenseehart
'''
import ast
import math
sample = 'Foo(a[b] + 2 + 2.3 * a / a // a ** a % a ^ a & a | a and (a != b or c == d))'
node_whitelist = set(x.__class__.__name__ for x in ast.walk(ast.parse(sample)))
name_whitelist = set(x for x in dir(math) if x[0]!='_')
from math import *
def safe_eval(s):
nodes = set([x.__class__.__name__ for x in ast.walk(ast.parse(s))])
names = set([x.id for x in ast.walk(ast.parse(s)) if x.__class__.__name__ == 'Name'])
bad = (nodes - node_whitelist) | (names - name_whitelist )
if bad:
raise ValueError('illegal use of {} in expression'.format(list(bad)[0]))
return eval(s)
while 1:
s = raw_input('calc> ')
s = s.strip()
if not s:
continue
if s=='exit':
break
try:
print (safe_eval(s))
except ValueError as e:
print (e.message)
except SyntaxError as e:
print (e.msg)
@kenseehart
Copy link
Author

kenseehart commented Sep 24, 2017

Please hack this, and comment. If you can hack this, I promise to keep my mouth shut during security related
conversations for not less than six months. :)

This won't prevent DOS attacks with various kinds of extreme math. AFAIK, that can only effectively be dealt with at the
process level. I'm pretty sure there is no lexical approach to preventing extreme math in a sufficiently useful tool.
I will consider a hack successful if you can obtain access to os.system() or equivalent from the provided calc> prompt.

@pdemarti
Copy link

Good recipe, Ken, thank you. How would you evolve this snippet to whitelist certain operations on certain types only? Imagine I'd like to allow a subset of operations on pandas.DataFrame, Series, and, say, numpy arrays. The ast analysis doesn't know anything about types. So if I want to allow e.g. df.rename_axis('foo', axis=0), I suppose either I allow Call of Attribute rename_axis for any object, or for none. Is that correct?

@pdemarti
Copy link

Never mind. If anybody else lands here, see this article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment