Skip to content
33f46c954fd3ed16b32eeddd598f90cf

33f46c954fd3ed16b32eeddd598f90cf

gist.github.com September 21, 2026

Save 2H-K/33f46c954fd3ed16b32eeddd598f90cf to your computer and use it in GitHub Desktop.

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

Specific Issue: Unsafe Query Construction via String Concatenation in ORM API

Affected Component: drogon::orm::Mapper::orderBy() and drogon_ctl restful controller template

Impact: Remote attackers can extract arbitrary database data through boolean blind injection via the sort query parameter. Exploitation requires no authentication when the generated RESTful controller is deployed without an authentication filter — which is the default behavior of the drogon_ctl scaffold.

Default Status: The vulnerable data flow is reachable through the standard ORM/REST workflow exposed by the framework. When developers enable restful_api_controllers in model.json (the documented workflow for scaffolding a REST API) and generate the controller via drogon_ctl , the generated get() endpoint receives the untrusted sort query parameter and passes it directly to the unsafe orderBy() API. The generated controller attaches no authentication filter by default ( filters: [] in model.json ), so the endpoint is exposed unauthenticated unless the developer explicitly configures one.

The vulnerable code pattern ( formattedString(" order by %s", colName) without validation) has existed since the restful controller generator was first introduced. Exploitation was confirmed on v1.9.13 with the restful_api_controllers scaffold enabled and no authentication filter configured (the default for generated controllers), against PostgreSQL 16.

Introduced In: v1.0.0-beta8 (when restful_controller_base_cc.csp template was added)

Fixed In: None (as of 2026-05-10)

Tested Version: v1.9.13 (compiled from source against PostgreSQL 16.13)

Note: Source inspection shows identical code across all versions, but "same code pattern" does not guarantee identical exploitability in every version — upstream changes to calling context, default configurations, or type constraints may affect behavior. Exploitation was confirmed only on v1.9.13.

Note: Source inspection shows identical code across all versions, but "same code pattern" does not guarantee identical exploitability in every version — upstream changes to calling context, default configurations, or type constraints may affect behavior. Exploitation was confirmed only on v1.9.13.

3. Technical Analysis

3.1 Source-to-Sink Data Flow

Note: Line numbers refer to the Drogon framework source files ( drogon/drogon_ctl/templates/restful_controller_base_cc.csp for the template and drogon/orm_lib/inc/drogon/orm/Mapper.h for ORM). Actual generated controller files may have slightly different line numbers due to project‑specific expansions.

Sink — drogon/orm_lib/inc/drogon/orm/Mapper.h:1980-2002 :

Source — drogon/drogon_ctl/templates/restful_controller_base_cc.csp:243-264 :

The orderBy() method uses utils::formattedString() (a sprintf -like function) to interpolate colName directly into the SQL ORDER BY clause. There is no:

Column name validation — any string is accepted

Parameterized binding — the value is concatenated into the SQL text, not passed as a parameter

Escaping — special characters are not sanitized

A defense function isValidSqlIdentifier() already exists in BaseBuilder.h:116-131 that restricts characters to [a-zA-Z0-9_.] , but no validation step was observed along the analyzed execution path for orderBy() . It is only used in JOIN methods wrapped in assert() (removed in release builds via NDEBUG ).

drogon_ctl create model generates REST controllers with [[filters]] = empty string when model.json 's restful_api_controllers.filters is absent or an empty array ( create_model.cc:1498-1513 ). The default generated model.json ships "filters": [] ( model_json.csp:90 ), and restful_api_controllers.enabled defaults to false — controllers are only produced once a developer explicitly opts into the REST scaffold, which is the intended usage for building a REST API.

Framework ships zero built-in authentication filters — no LoginFilter , AuthFilter exists ( lib/src/ provides only GlobalFilters , IntranetIpFilter , LocalHostFilter )

The sort parameter is parsed from req->parameters() (HTTP query string) — fully attacker-controlled

masquerading does not protect the orderBy path (only protects filter/WHERE and create/update)

Unauthenticated exposure confirmed by generated code: the controller generated by drogon_ctl registers get() via METHOD_ADD([[className]]::get,"",Get,Options[[filters]]) ( restful_controller_custom_h.csp:66 ). With the default empty filters , no filter constraint is attached, so the sort parameter is reachable unauthenticated. Note this applies once the developer has enabled restful_api_controllers ; the controller is not generated by default.

4.1 Environment Setup

System Environment: Ubuntu virtual machine or physical machine

Step 1: Install Required Dependencies

Install the essential build tools, mandatory libraries, and database development libraries according to the Drogon installation guide .

Note: The PostgreSQL client development library must be installed before compiling Drogon, otherwise the ORM will report NO DATABASE FOUND .

Note: The PostgreSQL client development library must be installed before compiling Drogon, otherwise the ORM will report NO DATABASE FOUND .

Step 2: Clone the Drogon Source Repository

Step 3: Enter the Repository

Step 4: Initialize Submodules and Build from Source

Step 5: Verify drogon_ctl is Available

Step 6: Create a New Drogon Project

If you prefer not to install Drogon globally and want source-level debugging, use the path to drogon_ctl:

Then, modify the generated CMakeLists.txt to include Drogon source directly:

Ensure Trantor submodule is initialized: git submodule update --init in the Drogon source directory.

Create a New Drogon Project (Global Install):

Step 7: Prepare the Database Schema

Place the provided schema.sql file (see attachments) in your custom working directory.

Step 8: Start PostgreSQL via Docker

Step 9: Verify Database Tables Were Created

Enter the container and inspect the tables:

Step 10: Configure the Project

Navigate to your Drogon project directory. If config.json does not exist, copy it from the Drogon source tree:

Edit config.json and models/model.json to configure the database connection string and the project listening port. For the debugging phase, it is recommended to remove the following HTTPS listener block:

Additionally, remove the entire redis_clients field, as it is unrelated to the vulnerability.

Step 11: Create main.cc

Create main.cc with the following content:

This is the standard entry point for a Drogon application. The controllers are automatically generated by drogon_ctl, and you do not need to modify them.

Step 12: Generate Controllers and Build

Generate the ORM models and RESTful controllers using drogon_ctl (use the path to your local drogon_ctl if not installed globally):

This command will automatically create:

ORM model classes (Users.h/cc, etc.) in the models/ directory

RESTful controller classes (RestfulUsersCtrl.h/cc, RestfulUsersCtrlBase.h/cc, etc.) in the controllers/ directory

Step 13: Run the Application and Verify

Open your browser and visit (the port configured in config.json , default is 80 ) to confirm the application is running normally.

4.2 Manual Verification

Assuming I configured port 8200 as the startup port

Boolean TRUE — sorts by id (numeric order: 1,2,3,4,5,6,7,8):

Boolean FALSE — sorts by username (alphabetical order: admin,lisi,sunqi,wangwu,wujiu,zhangsan,zhaoliu,zhouba):

Data extraction — guess admin password_hash first character = 'a' (correct):

Data extraction — guess = 'z' (wrong):

4.3 Automated Exploit

Full automated exploit script available in attachments: exploit_orderby_sqli.py

Full attack results (27,883 HTTP requests, 642 seconds):

4.4 Injection Constraints

Confidentiality: HIGH

Full database compromise via boolean blind injection through ORDER BY clause

Extracted: user credentials (password hashes), salary data, financial records, business secrets (product cost prices, supplier names), database metadata (version, internal IP, user privileges)

ORDER BY clause injection typically cannot modify data in the observed attack paths (SQL verb is primarily SELECT)

Stacked queries blocked by PostgreSQL prepared statement mechanism

Malformed payloads cause syntax errors caught by framework (returns HTTP 500)

No denial of service vector

6. Proposed Mitigation

Short-term — Add column name validation in Mapper.h:1980 :

Migrate ORDER BY construction to use parameterized binding where possible

Add authentication filter support to drogon_ctl create controller CLI

Apply isValidSqlIdentifier() validation to all identifier inputs across the ORM layer

Source Repository:

Sink (orderBy injection): drogon/orm_lib/inc/drogon/orm/Mapper.h:1986 — formattedString(" order by %s", colName.c_str()) If you need more surrounding lines for context:

Source (template): drogon/drogon_ctl/templates/restful_controller_base_cc.csp:243-264 — mapper.orderBy(field, SortOrder::ASC) (lines 254, 259, 263)

Unused Defense Function: drogon/orm_lib/inc/drogon/orm/BaseBuilder.h:116 — isValidSqlIdentifier() (exists but was not observed to be called in the analyzed orderBy execution path)

PoC Repository: (private, access granted to reviewers)

CWE-89:

Extracted Entities

Attack Types (1)

Companies (1)

CWE Weaknesses (1)

Domains (1)

Platforms (2)