pyre-check
pyre-check copied to clipboard
Cannot resolve type for pymysql
Hi, my code is fairly simple, using pymysql within the Flask framework, but I have been unable to detect SQL injection. By using pyre_dump() and reveal_type(), I have identified the following issues
source code
import pymysql
from flask import Request
def request_parse(req_data: Request):
data = None
if req_data.method == 'POST':
data = req_data.get_json()
elif req_data.method == 'GET':
data = req_data.args
return data
def update_case_by_Pymysql(request):
req = request_parse(request)
sql = req["sql"]
reveal_type(sql)
reveal_taint(sql)
conn = pymysql.connect(host='localhost',user='user',password='passwd',database='db', cursorclass=pymysql.cursors.DictCursor)
# pyre_dump()
cursor = conn.cursor()
reveal_type(cursor)
reveal_taint(cursor)
cursor.execute(sql)
issues
- with pyre_dump(), I got the exception result: Resolved type for base
pymysql
isunknown
, as below:
2024-04-24 09:56:02,141 [PID 6032] WARNING Checking if cursors
is an attribute, property or global variable. Resolved type for base pymysql
is unknown
2024-04-24 09:56:02,144 [PID 6032] WARNING Resolved callees for expression pymysql.cursors
:
2024-04-24 09:56:02,145 [PID 6032] WARNING { call = None;
2024-04-24 09:56:02,146 [PID 6032] WARNING attribute_access =
2024-04-24 09:56:02,146 [PID 6032] WARNING (Some { property_targets = []; global_targets = []; is_attribute = true });
2024-04-24 09:56:02,146 [PID 6032] WARNING identifier = None; string_format = None }
2024-04-24 09:56:02,147 [PID 6032] WARNING Checking if DictCursor
is an attribute, property or global variable. Resolved type for base pymysql.cursors
is unknown
2024-04-24 09:56:02,147 [PID 6032] WARNING Resolved callees for expression pymysql.cursors.DictCursor
:
2024-04-24 09:56:02,148 [PID 6032] WARNING { call = None;
2024-04-24 09:56:02,149 [PID 6032] WARNING attribute_access =
2024-04-24 09:56:02,150 [PID 6032] WARNING (Some { property_targets = []; global_targets = []; is_attribute = true });
2024-04-24 09:56:02,151 [PID 6032] WARNING identifier = None; string_format = None }
2024-04-24 09:56:02,151 [PID 6032] WARNING Resolving function call pymysql.connect($parameter$host = "localhost", $parameter$user = "user", $parameter$password = "passwd", $parameter$database = "db", $parameter$cursorclass = pymysql.cursors.DictCursor)
2024-04-24 09:56:02,151 [PID 6032] WARNING Checking if "localhost"
is a callable, resolved type is typing_extensions.Literal['localhost']
2024-04-24 09:56:02,152 [PID 6032] WARNING Checking if "user"
is a callable, resolved type is typing_extensions.Literal['user']
2024-04-24 09:56:02,152 [PID 6032] WARNING Checking if "passwd"
is a callable, resolved type is typing_extensions.Literal['passwd']
2024-04-24 09:56:02,153 [PID 6032] WARNING Checking if "db"
is a callable, resolved type is typing_extensions.Literal['db']
2024-04-24 09:56:02,154 [PID 6032] WARNING Checking if pymysql.cursors.DictCursor
is a callable, resolved type is typing.Type[pymysql.cursors.DictCursor]
2024-04-24 09:56:02,155 [PID 6032] WARNING Resolved callee from its resolved type:
2024-04-24 09:56:02,156 [PID 6032] WARNING { call_targets = []; 2024-04-24 09:56:02,157 [PID 6032] WARNING new_targets = 2024-04-24 09:56:02,158 [PID 6032] WARNING [{ target = 2024-04-24 09:56:02,159 [PID 6032] WARNING (Method 2024-04-24 09:56:02,160 [PID 6032] WARNING { class_name = "object"; method_name = "__new__"; kind = Normal }); 2024-04-24 09:56:02,161 [PID 6032] WARNING implicit_self = true; implicit_dunder_call = false; index = 0; 2024-04-24 09:56:02,161 [PID 6032] WARNING return_type = (Some {}); receiver_class = None } 2024-04-24 09:56:02,162 [PID 6032] WARNING ]; 2024-04-24 09:56:02,162 [PID 6032] WARNING init_targets = 2024-04-24 09:56:02,163 [PID 6032] WARNING [{ target = 2024-04-24 09:56:02,163 [PID 6032] WARNING (Method 2024-04-24 09:56:02,164 [PID 6032] WARNING { class_name = "pymysql.cursors.Cursor"; method_name = "__init__"; 2024-04-24 09:56:02,165 [PID 6032] WARNING kind = Normal }); 2024-04-24 09:56:02,165 [PID 6032] WARNING implicit_self = true; implicit_dunder_call = false; index = 0; 2024-04-24 09:56:02,167 [PID 6032] WARNING return_type = (Some {}); receiver_class = None } 2024-04-24 09:56:02,167 [PID 6032] WARNING ]; 2024-04-24 09:56:02,168 [PID 6032] WARNING higher_order_parameters = {}; unresolved = false }
2024-04-24 09:56:02,170 [PID 6032] WARNING Checking if pymysql.connect
is a callable, resolved type is typing.Any
- After removing the pyre_dump statement, the results obtained from reveal_type / reveal_taint are as follows. It can be seen that the pymysql sink cannot be captured:
2024-04-24 10:13:11,594 [PID 6426] WARNING djangosql.main:15:4-15:15: Revealed type for sql: typing.Any
2024-04-24 10:13:11,594 [PID 6426] WARNING djangosql.main:16:4-16:16: Revealed forward taint for sql
: {CallSite(callees=[djangosql.main.request_parse], location=djangosql.main:13:10-13:32, port=result) -> LocalTaint(Kinds: {UserControlled -> Frame(Breadcrumb: [ModelShaping(-), TitoBroadening(-), Tito(-), Broadening(-), ModelTitoShaping(-), ObscureUnknownCallee(-)], TraceLength: 1, LeafName: [LeafName(djangosql.main.request_parse, port=Leaf(req_data)), LeafName(werkzeug.wrappers.request.Request.get_data, port=Leaf(return)), LeafName(werkzeug.wrappers.request.Request.get_json, port=Leaf(return))], FirstField: ["args"])}, Breadcrumb: [], FirstIndex: ["sql"], CallInfoIntervals: [caller_interval: cursor
: {}
2024-04-24 10:13:11,595 [PID 6426] WARNING djangosql.main:20:4-20:24: Revealed backward taint for cursor
: {}
2024-04-24 10:13:11,595 [PID 6426] WARNING djangosql.main:16:4-16:21: Revealed backward taint for sql
: {}
2024-04-24 10:13:11,595 [PID 6426] WARNING flasksql.main:26:4-26:15: Revealed type for sql: typing.Any
2024-04-24 10:13:11,595 [PID 6426] WARNING flasksql.main:27:4-27:16: Revealed forward taint for sql
: {CallSite(callees=[flasksql.main.request_parse], location=flasksql.main:23:10-23:32, port=result) -> LocalTaint(Kinds: {UserControlled -> Frame(Breadcrumb: [ObscureUnknownCallee(-)], TraceLength: 1, LeafName: [LeafName(werkzeug.wrappers.request.Request.get_data, port=Leaf(return)), LeafName(werkzeug.wrappers.request.Request.get_json, port=Leaf(return))])}, Breadcrumb: [], FirstIndex: ["sql"], CallInfoIntervals: [caller_interval: db
: {}
2024-04-24 10:13:11,596 [PID 6426] WARNING flasksql.main:40:8-40:19: Revealed type for sql: typing.Any
2024-04-24 10:13:11,596 [PID 6426] WARNING flasksql.main:41:8-41:20: Revealed forward taint for sql
: {CallSite(callees=[flasksql.main.request_parse], location=flasksql.main:23:10-23:32, port=result) -> LocalTaint(Kinds: {UserControlled -> Frame(Breadcrumb: [ObscureUnknownCallee(-)], TraceLength: 1, LeafName: [LeafName(werkzeug.wrappers.request.Request.get_data, port=Leaf(return)), LeafName(werkzeug.wrappers.request.Request.get_json, port=Leaf(return))])}, Breadcrumb: [], FirstIndex: ["sql"], CallInfoIntervals: [caller_interval: sql
: {CallSite(callees=[djangosql.main.request_parse], location=djangosql.main:13:10-13:32, port=result) -> LocalTaint(Kinds: {UserControlled -> Frame(Breadcrumb: [ModelShaping(-), TitoBroadening(-), Tito(-), Broadening(-), ModelTitoShaping(-), ObscureUnknownCallee(-)], TraceLength: 1, LeafName: [LeafName(djangosql.main.request_parse, port=Leaf(req_data)), LeafName(werkzeug.wrappers.request.Request.get_data, port=Leaf(return)), LeafName(werkzeug.wrappers.request.Request.get_json, port=Leaf(return))], FirstField: ["args"])}, Breadcrumb: [], FirstIndex: ["sql"], CallInfoIntervals: [caller_interval: cursor
: {}
2024-04-24 10:13:11,599 [PID 6426] WARNING djangosql.main:20:4-20:24: Revealed backward taint for cursor
: {}
2024-04-24 10:13:11,599 [PID 6426] WARNING djangosql.main:16:4-16:21: Revealed backward taint for sql
: {}
2024-04-24 10:13:11,600 [PID 6426] WARNING flasksql.main:26:4-26:15: Revealed type for sql: typing.Any
2024-04-24 10:13:11,600 [PID 6426] WARNING flasksql.main:27:4-27:16: Revealed forward taint for sql
: {CallSite(callees=[flasksql.main.request_parse], location=flasksql.main:23:10-23:32, port=result) -> LocalTaint(Kinds: {UserControlled -> Frame(Breadcrumb: [ObscureUnknownCallee(-)], TraceLength: 1, LeafName: [LeafName(werkzeug.wrappers.request.Request.get_data, port=Leaf(return)), LeafName(werkzeug.wrappers.request.Request.get_json, port=Leaf(return))])}, Breadcrumb: [], FirstIndex: ["sql"], CallInfoIntervals: [caller_interval: db
: {}
2024-04-24 10:13:11,601 [PID 6426] WARNING flasksql.main:40:8-40:19: Revealed type for sql: typing.Any
2024-04-24 10:13:11,601 [PID 6426] WARNING flasksql.main:41:8-41:20: Revealed forward taint for sql
: {CallSite(callees=[flasksql.main.request_parse], location=flasksql.main:23:10-23:32, port=result) -> LocalTaint(Kinds: {UserControlled -> Frame(Breadcrumb: [ObscureUnknownCallee(-)], TraceLength: 1, LeafName: [LeafName(werkzeug.wrappers.request.Request.get_data, port=Leaf(return)), LeafName(werkzeug.wrappers.request.Request.get_json, port=Leaf(return))])}, Breadcrumb: [], FirstIndex: ["sql"], CallInfoIntervals: [caller_interval:
Additional information
- As far as I know, the pyre-check project already includes a sink file for pymysql. Could it be that this file has become outdated?
- When I replaced pymysql in the code with django.db, I was able to detect SQL injection.
I look forward to your prompt reply and would greatly appreciate it.
Hey! It seems the issue you're encountering with pyre-check
when using pymysql
may stem from outdated or incomplete type stubs in the library, which hinders the tool's ability to accurately recognize and handle types like pymysql.cursors.DictCursor
. This issue can prevent pyre-check
from effectively tracking taints and resolving types, thereby affecting its ability to detect vulnerabilities like SQL injection, which you observed works correctly with django.db
. To address this, you might consider updating the stubs or configurations in pyre-check
to better recognize pymysql
components, or consult the Pyre project documentation for guidance on enhancing type support.
I have already tried to update the type stubs of pymysql from git repository before I posted this issue. But it still didn't work. Maybe you can run the pyre to analyze my source code I mentioned above to see if you can figure out this problem.