Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chained function calls separated into multiple assignments #171

Merged
merged 1 commit into from
Sep 2, 2018

Conversation

bcaller
Copy link
Collaborator

@bcaller bcaller commented Aug 30, 2018

Take the example from examples/vulnerable_code/sql/sqli.py:

result = session.query(User).filter("username={}".format(TAINT))

The filter function is marked as a sink. However, previously this did not get marked as a vulnerability. The call label used to be session.query, ignoring the filter function.

Now, when the file is read, it is transformed into 2 lines:

__chain_tmp_1 = session.query(User)
result = __chain_tmp_1.filter("username={}".format(TAINT))

before converting to a CFG. The vulnerability is now found.

We don't find everything here: just ordinary assignments and return statements. We can't just transform all Call nodes here since Call nodes can appear in many different scenarios e.g. comprehensions, bare function calls. Because of this, it may be worth thinking about how to do this via the CFG everywhere function calls are chained.

Take the example from examples/vulnerable_code/sql/sqli.py:

`result = session.query(User).filter("username={}".format(TAINT))`

The `filter` function is marked as a sink. However, previously this did
not get marked as a vulnerability.
The call label used to be `session.query`, ignoring the filter function.

Now, when the file is read, it is transformed into 2 lines:

```
__chain_tmp_1 = session.query(User)
result = __chain_tmp_1.filter("username={}".format(TAINT))
```

And the vulnerability is found.

We don't find everything here: just ordinary assignments and return
statements. We can't just transform all Call nodes here since Call nodes
can appear in many different scenarios e.g. comprehensions, bare
function calls.
Copy link
Collaborator

@KevinHock KevinHock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, sorry it took a few days to review, just moved :)

class ChainedFunctionTransformer():
def visit_chain(self, node, depth=1):
if (
isinstance(node.value, ast.Call) and
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love how you did
node.value
node.value.func
node.value.func.value
in that order, it's super clean.

inner_calls = self.visit_chain(unvisited_inner_call, depth + 1)
for inner_call_node in inner_calls:
ast.copy_location(inner_call_node, node)
outer_call = self.generic_visit(type(node)(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation nit: This is awesomely smart but more indentation may make it clearer

outer_call = self.generic_visit(
    type(node)(
        value=ast.Call(
            func=ast.Attribute(
                value=ast.Name(id=temp_var_id, ctx=ast.Load()),
                attr=call_node.func.attr,
                ctx=ast.Load(),
            ),
            args=call_node.args,
            keywords=call_node.keywords,
        ),
        **{field: value for field, value in ast.iter_fields(node) if field != 'value'}  # e.g. targets
    )
)

Nit: w/r/t generic_visit, since it isn't defined in this class, maybe inheriting from ast.NodeTransformer in both classes would be clearer.

self.assertIsInstance(transformed.body[0], ast.FunctionDef)

self.assertEqual(ast.dump(transformed), ast.dump(sync_tree))

def test_chained_function(self):
chained_tree = ast.parse("\n".join([
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great use of join :D

@KevinHock KevinHock merged commit f56e761 into python-security:master Sep 2, 2018
@bcaller bcaller deleted the chains branch September 7, 2018 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants