Python: Add query to handle SQLAlchemy TextClause Injection

instead of doing this via taint-steps. See description in code/tests.
This commit is contained in:
Rasmus Wriedt Larsen
2021-09-02 10:13:12 +02:00
parent 81dbe36e99
commit c34d6d1162
15 changed files with 391 additions and 127 deletions

View File

@@ -0,0 +1,52 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
The <code>TextClause</code> class in the <code>SQLAlchemy</code> PyPI package represents
a textual SQL string directly. If user-input is added to it without sufficient
sanitization, a user may be able to run malicious database queries, since the
<code>TextClause</code> is inserted directly into the final SQL.
</overview>
<recommendation>
<p>
Don't allow user-input to be added to a <code>TextClause</code>, instead construct your
full query with constructs from the ORM, or use query parameters for user-input.
</p>
</recommendation>
<example>
<p>
In the following snippet, a user is fetched from the database using three
different queries.
</p>
<p>
In the first case, the final query string is built by directly including a user-supplied
input. The parameter may include quote characters, so this code is vulnerable to a SQL
injection attack.
</p>
<p>
In the second case, the query is built using ORM models, but part of it is using a
<code>TextClause</code> directly including a user-supplied input. Since the
<code>TextClause</code> is inserted directly into the final SQL, this code is vulnerable
to a SQL injection attack.
</p>
<p>
In the third case, the query is built fully using the ORM models, so in the end, the
user-supplied input will passed passed to the database using query parameters. The
database connector library will take care of escaping and inserting quotes as needed.
</p>
<sample src="examples/sqlalchemy_textclause_injection.py" />
</example>
<references>
<li><a href="https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text.params.text">Official documentation of the text parameter</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name SQLAlchemy TextClause built from user-controlled sources
* @description Building a TextClause query from user-controlled sources is vulnerable to insertion of
* malicious SQL code by the user.
* @kind path-problem
* @problem.severity error
* @security-severity 8.8
* @precision high
* @id py/sqlalchemy-textclause-injection
* @tags security
* external/cwe/cwe-089
* external/owasp/owasp-a1
*/
import python
import semmle.python.security.dataflow.SQLAlchemyTextClause
import DataFlow::PathGraph
from SQLAlchemyTextClause::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"This SQLAlchemy TextClause depends on $@, which could lead to SQL injection.", source.getNode(),
"a user-provided value"

View File

@@ -0,0 +1,34 @@
from flask import Flask, request
import sqlalchemy
import sqlalchemy.orm
app = Flask(__name__)
engine = sqlalchemy.create_engine(...)
Base = sqlalchemy.orm.declarative_base()
class User(Base):
__tablename__ = "users"
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
username = sqlalchemy.Column(sqlalchemy.String)
@app.route("/users/<username>")
def show_user(username):
session = sqlalchemy.orm.Session(engine)
# BAD, normal SQL injection
stmt = sqlalchemy.text("SELECT * FROM users WHERE username = '{}'".format(username))
results = session.execute(stmt).fetchall()
# BAD, allows SQL injection
username_formatted_for_sql = sqlalchemy.text("'{}'".format(username))
stmt = sqlalchemy.select(User).where(User.username == username_formatted_for_sql)
results = session.execute(stmt).scalars().all()
# GOOD, does not allow for SQL injection
stmt = sqlalchemy.select(User).where(User.username == username)
results = session.execute(stmt).scalars().all()
...