Back to Blog
critical SEVERITY4 min read

SQL's Insert() and Update() Methods Used F-String Interpolation in u2share_batch_give_sugar

The SQL helper class in u2share_batch_give_sugar used Python f-strings to construct INSERT and UPDATE queries, creating SQL injection vulnerabilities even though values appeared to come from internal constants. The fix replaces all f-string query construction with sqlite3 parameterized queries using `?` placeholders, eliminating string interpolation entirely from the database path.

O
By Orbis AppSec
Published September 12, 2026Reviewed September 12, 2026

Answer Summary

The affected code is the `SQL` class and its `Insert()` and `Update()` methods in u2share_batch_give_sugar, where f-string interpolation was used for database operations. An attacker achieving control over the `value` parameter to `Insert()` or `ucoind`/`pid` parameters to `Update()` could execute arbitrary SQL commands, read the entire SQLite database, modify records, or potentially achieve code execution through SQLite extensions. The fix replaces f-string query construction with parameterized queries using sqlite3's `?` placeholders and tuple parameters. CWE-89 (SQL Injection).

Vulnerability at a Glance

cweCWE-89
fixReplaced f-string SQL construction with sqlite3 parameterized queries using `?` placeholders
riskDatabase compromise, data exfiltration, unauthorized data modification
languagePython
root causeF-string interpolation in SQL query construction instead of parameterized queries
vulnerabilitySQL Injection

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see fix commit
Ecosystem Python (sqlite3 standard library)
CVE / GHSA not assigned
CWE CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

The Vulnerability Explained

The SQL class in this codebase provided a thin wrapper around Python's sqlite3 module, but it undermined the library's built-in protections. Instead of using parameterized queries, the Insert() and Update() methods used Python f-strings to splice values directly into SQL command strings.

Here's the vulnerable Insert() method:

def Insert(self, column, value):
    try:
        sql = f'''INSERT INTO "main"."info" ({column}) VALUES ({value})'''
        logger.debug(sql)
        self.cursor.execute(sql)

And the vulnerable Update() method:

def Update(self, columnvalue, newcolumnvalue):
    try:
        sql = f'UPDATE "main"."info" SET {newcolumnvalue} WHERE {columnvalue}'
        logger.debug(sql)
        self.cursor.execute(sql)

The critical flaw: the execute() call receives a single string argument with values already interpolated, rather than using sqlite3's parameter substitution. Even if current callers passed "safe" values, the API surface itself was poisoned. Any future code path that accepted user input—directly or indirectly—would automatically inherit SQL injection capability without any additional vulnerability being introduced.

Exploitation Path

Consider the Update() method. The parameters columnvalue and newcolumnvalue were string fragments like "pid=123" that were spliced into the WHERE and SET clauses. An attacker controlling either parameter could inject arbitrary SQL:

newcolumnvalue = 'ucoind=100; DROP TABLE info; --'

This would produce:

UPDATE "main"."info" SET ucoind=100; DROP TABLE info; -- WHERE pid=123

The SQLite database—containing user identifiers, currency balances (ucoin, ucoind), and platform IDs (pid)—could be read, modified, or destroyed.

The Fix

The fix replaces f-string construction with sqlite3's parameterized query API. This is not a "sanitization" approach—it's a structural change that separates code from data entirely.

Before (Insert):

def Insert(self, column, value):
    sql = f'''INSERT INTO "main"."info" ({column}) VALUES ({value})'''
    self.cursor.execute(sql)

After (Insert):

def Insert(self, value):
    sql = 'INSERT INTO "main"."info" ("pid", "useractualid", "userid", "ucoin", "ucoind") VALUES (?, ?, ?, ?, ?)'
    self.cursor.execute(sql, value)

Before (Update):

def Update(self, columnvalue, newcolumnvalue):
    sql = f'UPDATE "main"."info" SET {newcolumnvalue} WHERE {columnvalue}'
    self.cursor.execute(sql)

After (Update):

def Update(self, pid, ucoind):
    sql = 'UPDATE "main"."info" SET "ucoind"=? WHERE "pid"=?'
    self.cursor.execute(sql, (ucoind, pid))

The changes are deliberate and API-breaking:

  1. Fixed schema: Insert() no longer accepts arbitrary columns. The five-column schema is hardcoded, preventing column-name injection.
  2. Tuple parameters: Values are passed as a second argument to execute(), not concatenated into the SQL string.
  3. Explicit parameters: Update() now takes pid and ucoind directly, eliminating the string-fragment pattern entirely.

Defense in Depth: XML Parser Hardening

The same commit includes an additional security improvement: the GetUID() function, which parses HTML from an external forum, now uses a hardened XML parser configuration:

xml_parser = etree.HTMLParser(resolve_entities=False, no_network=True)
html = etree.HTML(_html1, parser=xml_parser)

This prevents XML External Entity (XXE) attacks and blocks the parser from making network requests during document parsing—relevant when processing untrusted HTML from forum topics.

Key Takeaways

  • F-strings in SQL are always wrong: Python's f-string formatting has no place in database query construction. The sqlite3 module's parameter substitution (? placeholders with tuple arguments) is the only correct pattern.
  • API design matters for security: The original Insert(column, value) and Update(columnvalue, newcolumnvalue) signatures encouraged callers to build SQL fragments. The fixed signatures force callers to provide data values, not code.
  • "Currently safe" is not "safe": Even if audit showed no external input reached these methods, the vulnerable pattern created persistent risk. Code changes, refactoring, or new features could expose the injection surface without anyone recognizing the danger.
  • Log what you execute, not what you build: The logger.debug(sql) call in the original code logged the fully-interpolated string—potentially exposing sensitive data in logs. The fixed version logs the parameterized query template.
  • Harden parsers processing external data: The resolve_entities=False, no_network=True configuration for etree.HTMLParser demonstrates defense-in-depth for any code handling untrusted markup.

How Orbis AppSec Detected This

Source: The value parameter passed to Insert() and the ucoind/pid parameters passed to Update(), which could originate from any caller of the SQL class methods.

Sink: The cursor.execute() method invoked with a single string argument containing f-string-interpolated SQL, specifically the patterns self.cursor.execute(sql) where sql was constructed via f-string formatting.

Missing control: No use of sqlite3 parameterized queries; the code passed fully-constructed SQL strings rather than using ? placeholders with separate parameter tuples. No validation that column, value, columnvalue, or newcolumnvalue contained only expected characters.

CWE: CWE-89 — Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Fix: Replaced f-string SQL construction with parameterized queries using sqlite3's ? placeholders and tuple parameters, and hardened the etree.HTMLParser configuration to prevent XXE.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This fix demonstrates that SQL injection remains a critical risk even in modern Python code using standard libraries correctly—if those libraries are misused. The sqlite3 module provides robust protection against injection, but only when developers use parameterized queries rather than string formatting. The API redesign of Insert() and Update() shows how security can be built into method signatures, making dangerous patterns structurally impossible. For any codebase maintaining database wrapper classes, this pattern of hardcoded schemas with parameterized execution should be the mandatory standard.

Prevention and further reading

Frequently Asked Questions

Why was the `Insert()` method vulnerable if the `value` parameter appeared to be internally controlled?

The f-string pattern `f'''INSERT INTO "main"."info" ({column}) VALUES ({value})'''` created persistent injection risk. Future code changes, refactoring, or unexpected data flows could expose these parameters to external input without changing the vulnerable query construction pattern.

Does the fix change the method signatures of `Insert()` and `Update()`?

Yes. `Insert()` changed from accepting separate `column` and `value` parameters to accepting a single `value` tuple with five fixed positional elements. `Update()` changed from `columnvalue` and `newcolumnvalue` strings to explicit `pid` and `ucoind` parameters, making the API more restrictive and safer.

What additional security hardening was included in the same fix beyond SQL injection prevention?

The fix also added `xml_parser = etree.HTMLParser(resolve_entities=False, no_network=True)` and updated `GetUID()` to use this hardened parser, preventing XXE and external entity attacks in the HTML parsing path that processes forum topic data.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

high

TrackOptionsManager DDL: Template-Literal SQL Injection Closed

The `TrackOptionsManager` service built its `CREATE TABLE` and `ALTER TABLE ... ADD COLUMN alias` statements by interpolating a `DEFAULT_ALIAS` constant directly inside single quotes in a JavaScript template literal, and its private `_query()` helper had no parameter channel at all. The fix routes the default value through `mysql.escape()` and gives `_query(q, params = [])` a real bound-parameter argument that is forwarded to `db.query()`. This removes an injection primitive on a schema-bootstra

high

How SQL injection via template literals happens in Node.js SQLite and how to fix it

A SQL injection vulnerability in `src/lib/codex-state.mjs` allowed dynamic column names to reach SQL queries through JavaScript template literals. The fix implements defense-in-depth with strict identifier validation using `SAFE_IDENTIFIER` regex before query construction.

high

How Python SQLAlchemy Raw Query SQL Injection happens and how to fix it

A high-severity SQL injection vulnerability was fixed in the `skills/last30days/scripts/store.py` file where untrusted input was being concatenated directly into raw SQL queries. The fix replaces string concatenation with SQLAlchemy's TextualSQL prepared statements using named parameters, preventing attackers from manipulating database queries through malicious input.

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.

critical

How SQL Injection happens in PHP bulk email systems and how to fix it

A critical SQL injection vulnerability in `admin/utilities/bulkEmailSystem.php` allowed attackers to inject arbitrary SQL through unvalidated database names passed from user input. The fix implements strict input validation using regex pattern matching to ensure only safe database identifiers are processed, preventing exploitation of the bulk email functionality.

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.