fix(row-permission): escape SQL values in row permission filter to prevent injection (CWE-89)#1186
Merged
XiaJunjie2020 merged 2 commits intoJun 11, 2026
Conversation
…(CWE-89) All user-controlled values interpolated into SQL WHERE clause fragments in transTreeItem() and getSysVariableValue() are now passed through _escape_sql_value() which doubles single quotes (standard SQL escaping) and escapes backslashes. The logic operator in transTreeToWhere() is also validated against a whitelist. Affected injection vectors: - item["enum_value"] list (enum filter) - item["value"] string (normal filter) - user variable values (variable filter) - current_user name/account/email (system variable filter) - tree["logic"] operator (tree structure)
Contributor
Author
|
Thanks for the merge. Glad it landed cleanly. P.S. Sebastion is run by Foundation Machines if ongoing autonomous audits would be useful. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SQL injection (CWE-89) in
backend/apps/datasource/crud/row_permission.py. User-controlled values are interpolated directly into SQL WHERE clauses without escaping, allowing an authenticated non-admin user to inject arbitrary SQL via row permission filter values.Vulnerability details
The functions
transTreeItemandgetSysVariableValueinrow_permission.pybuild SQL WHERE fragments by concatenating user-controlled strings into quoted SQL literals using f-strings and.join(). None of these interpolation points escape single quotes or other SQL-significant characters.Data flow:
transFilterTree()is called fromget_row_permission_filters()inpermission.pypreview()indatasource.py) and LLM query paths for non-admin users who have row permissions configuredtransTreeItem()viaitem['enum_value'],item['value'], and user variable values — orgetSysVariableValue()viacurrent_user.name/current_user.accountf"'{values[0]}'"Affected interpolation points (before this fix):
enum_valuelist joined unsanitized into IN clausevalues[0]in LIKE clauses via f-stringvalues[0]in equality/comparison via f-stringvalue.split(",")in IN clausescurrent_user.name/current_user.accountingetSysVariableValue()tree['logic']operator — no validation, concatenated directly into the SQL fragmentExploitability
The most realistic attack scenario involves SSO: when SSO is configured, a non-admin user controls their own display name via the identity provider. If an admin configures a row permission rule using the
nameoraccountsystem variable, the user's SSO name is interpolated unsanitized into the SQL query.Preconditions:
id != 1)nameoraccountThere is an existing
check_sql_read()guard indb.pythat prevents write operations (INSERT/UPDATE/DELETE), but it does not prevent data exfiltration via UNION SELECT or boolean-based blind injection.Proof of Concept
An SSO user sets their display name at the identity provider to:
When the user queries any datasource table that has a row permission rule configured with the
namesystem variable,getSysVariableValue()retrievescurrent_user.nameand builds:The injected UNION SELECT passes
check_sql_read()(it is still a SELECT) and returns data fromsys_user, including password hashes for all users.For the
enum_valueand directvaluepaths, a user with admin access to row permission configuration could set filter values containing injection payloads — though this vector is largely redundant since admin already has full access.The
logicfield can also be injected: if a crafted filter tree supplieslogic: "AND 1=1) UNION SELECT ...", it would be concatenated directly into the SQL fragment without validation.Relevant code path (current main branch):
Fix description
This PR adds two defenses:
1.
_escape_sql_value()functionA new helper that escapes string values before interpolation into SQL literals:
'→'') — standard SQL escaping\→\\) — prevents bypass on drivers that interpret\'as an escape sequenceApplied at every interpolation point in
transTreeItem()andgetSysVariableValue(), covering all value paths (enum, IN, LIKE, equality, system variables).2.
_VALID_LOGIC_OPSwhitelistThe
logicfield from filter trees is validated against{"AND", "OR"}before use. Invalid operators cause the filter to returnNone(safe no-op), preventing injection via this field.Parameterized queries would be the ideal fix, but the architecture builds SQL fragments across multiple functions and concatenates them — refactoring to parameterized queries would require significant structural changes across
row_permission.py,permission.py, anddatasource.py. The escaping approach is a pragmatic defense-in-depth measure that addresses the immediate injection risk.Testing
29 unit tests added in
tests/test_cwe89_escape_fix.py, all passing:TestEscapeSqlValue(16 tests): Validates escaping of normal strings, single quotes, double quotes, backslashes, combined payloads, UNION injection payloads, stacked queries, numeric coercion, and already-escaped inputTestValidLogicOps(8 tests): Validates the whitelist accepts only AND/OR, rejects injection payloads, empty strings, and other operators; verifies case-insensitive matchingTestSqlFragmentSafety(5 tests): End-to-end tests simulating how escaped values are assembled into IN, LIKE, equality, and N-prefixed SQL Server clauses, verifying that injection payloads remain inside string literals (even quote count)Adversarial review
Before submitting, we attempted to disprove this finding. We verified that
TokenMiddlewareenforces authentication on all non-whitelisted routes (the affected data preview endpoint is not whitelisted), so this requires an authenticated session. We checked whether thetransFilterTerm()function could serve as an injection vector — it cannot, as it mapsitem['term']through a fixed if/elif chain that returns only hardcoded SQL operators. We also considered whether most attack vectors are redundant because they require admin access to configure — they are, but the SSO user name path throughgetSysVariableValue()is controlled by the non-admin user themselves and represents a genuine escalation path. The fix still hardens all paths as defense-in-depth.Submitted by Sebastion — autonomous open-source security research from Foundation Machines. Free for public repos via the Sebastion AI GitHub App.