0

I am looking for an alternative to python string evaluator eval()

from telethon import Button

c = "Click Here To Open Google | [Button.url('Google', 'google.com')]"
if "|" in c:
   filter, options= c.split("|")
filter = filter.strip()     
button = options.strip()

g = eval(button)
await event.reply(filter, buttons=g)

the usage of eval() here is dangerous as related to this, what can i use as an alternative ?

MissJuliaRobot
  • 77
  • 1
  • 10
  • 2
    Parse the string yourself? – juanpa.arrivillaga Nov 21 '20 at 16:30
  • 3
    depends on the context, this specific example is intentionally catered towards `eval`. The way to move away from `eval` is by not designing an API around relying on the user to input actual code and parsing user input yourself – Chase Nov 21 '20 at 16:31
  • A safe alternative is "ast.literal_eval" but it is very restricted compared to "eval". – Michael Butscher Nov 21 '20 at 16:32
  • 2
    Use JSON or a similar format instead of code and parse that so `Button.url('Google', 'google.com')` becomes `{'button': {'label': 'Google', 'url': 'google.com'}}` and then you `json.loads(text)` and you can handle it how you want. Obviously the more complicated stuff you want to put there the more complicated the format becomes... – Bakuriu Nov 21 '20 at 16:32
  • ast.literal_eval returns `ValueError: malformed node or string: <_ast.Call object at 0x7fd518872040>` – MissJuliaRobot Nov 21 '20 at 16:35
  • i don't understand why it says malformed string – MissJuliaRobot Nov 21 '20 at 16:37
  • 1
    `ast.literal_eval` only works on strings that represent valid Python literals, the whole point is not to be able to evaluate arbitrary code. As @Chase mentioned, the fundamental issue is your have designed your API to require the user to input *python code*. – juanpa.arrivillaga Nov 21 '20 at 16:37
  • Usage of serialized/deserialized format (like JSON, XML, some markup, etc) is better also because it decouples you from the internals of the app and you avoid abstractions leaks – RandomB Nov 21 '20 at 16:48

3 Answers3

2

A safer alternative is ast.literal_eval but that's very restricted compared to eval(), only works on strings that represent valid Python literals at least for your example.

Better suggestion to parse string yourself, first you can get the two parameters using regex:

import re

c = "Click Here To Open Google | [Button.url('Google', 'google.com')]"
if "|" in c:
   filter, options= c.split("|")
filter = filter.strip()     
button = options.strip()
params = re.findall(r'\'(.*)\'',button)

It returns list of ["Google', 'google.com"] in params.

Then you can use if to check if it contains malicious input, if then modify it or block user, otherwise unpack to the method using Button.url(*params)

Wasif
  • 14,755
  • 3
  • 14
  • 34
  • hi i think this is going fine just getting the output as `["Google', 'google.com"]` but it should be `['Google', 'google.com']` then i can assign `params[0]` and `params [1]` directly like `g = [Button.url(params[0], params[1])]`, then `await event.reply(filter, buttons=g)` will work fine – MissJuliaRobot Nov 21 '20 at 16:50
  • 1
    @MissJuliaRobot Change the regexp to `r'\'(.*?)\''`. Then you can do `g = [Button.url(*params)]`. – ekhumoro Nov 21 '20 at 17:07
  • You can use this regex to get the individal strings: `r"""\(['"](\S*)['"],\s?['"](\S*)['"]\)"""` – juanpa.arrivillaga Nov 21 '20 at 17:15
  • 1
    @MissJuliaRobot You don't need to check for malicious input, because you aren't using `eval` any more. The `params` list only contains strings, which you are passing to an API which takes some text and a url. – ekhumoro Nov 21 '20 at 17:24
1

Your API is broken. If you require the user to use python code then yes you do have to evaluate it. eval can be made harder to exploit by specifying which locals/globals the code evaluated can use, but it's extremely hard to make the code safe... moreover once you start blacklisting built-in functions and such it becomes extremely hard to code and the API because hard to understand since it's not really clear what you can or cannot do.

The correct way to handle this is to provide that information as structured data, not as executable code!

There are many existing formats such as JSON, yaml, xml etc.

I will use JSON as an example and since it is very common these days and the stdlib has an implementation:

import json

c = '''
{
    "filter": Click Here To Open Google",
    "button": {
        "label": "Google",
        "url": "google.com"
    }
}
'''
data = json.loads(c)
filter = data['filter']
if 'button' in data:
    options = data['button']
    g = Button.url(options['label'], options['url'])
elif 'checkbox' in data:
     # as an example
    options = data['checkbox']
    g = Checkbox.url(options['label'], options['url'], options['status'])

await event.reply(filter, buttons=[g])

Obviously depending on how much power you want to give to the user the data will become more and more complex and so more and more complicated to handle. You have to think what the user might want to do, and how that information can be specified as data and how to handle it. This is no easy or simple task. Unfortunately we cannot really help you with this since you provided a single example while this requires a complete knowledge of the use cases that should be supported.

eval might be useful for early prototypes or home mode scripts not meant for others or the internet.

Extra bonus of using data: you can now reimplement a part of your application in a different language easily. If you used python you'd have to implement a full python interpreter instead.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • this seems way hard as `c` is a string and should be a string always as c is returned from a sqlalchemy query here i am using `c` just as a example – MissJuliaRobot Nov 21 '20 at 17:09
  • @MissJuliaRobot if that string is returned from a sql query then this is *even more broken*. Again, the fundamental problem is your are using *strings representing python code* a ways to store *data*. So you have to 1) use eval 2) write a parser 3) change the design of your program (this is the best solution, the other two are hacks) – juanpa.arrivillaga Nov 21 '20 at 17:14
  • @MissJuliaRobot I don't care where that thing comes from. **You** stipulate that its contents are JSON. You can easily store JSON in a DB either as text or as its own data type. I don't see any problem there. Oh obviously, if you are taking this input from an other system that produces python code then, yes you are indeed stuck with `eval` and there is no safe way to evaluate it. The system is broken by design and you cannot patch it. The best you can do is run your program with its own user with little privileges to try to limit what can get broken. – Bakuriu Nov 22 '20 at 09:47
-2

use ast.literal_eval

So you will have

from telethon import Button
import ast

c = "Click Here To Open Google | [Button.url('Google', 'google.com')]"
if "|" in c:
   filter, options= c.split("|")
filter = filter.strip()
button = options.strip()

g = ast.literal_eval(button)
await event.reply(filter, buttons=g)
Med Sep
  • 346
  • 1
  • 6
  • **this will not work**. `[Button.url('Google', 'google.com')]` includes non-literal code. The whole *point* of `ast.literal_eval` is to not be able to evaluate that – juanpa.arrivillaga Nov 21 '20 at 16:42