Low Severity CVE-2026-113615
Published: Nov 13, 2026

[GitHub] cve-2026-113615-poc

Sqli
Type
N/A
CVSS Score
5
Views
Anonymous
Author

Description

CVE-2026-113615 - GS JWT <= 1.0.0 unauthenticated SQLi to administrator JWT (PoC + exploit by thiscitze)

# CVE-2026-113615 — PoC + exploit: GS JWT unauthenticated SQLi to admin JWT

**by [thiscitze](https://github.com/thiscitze)**

| | |
|---|---|
| Product | GS JWT auth and OTP varification (`gs-jwt-auth-and-otp-varification`) |
| Affected | `<= 1.0.0` |
| Type | Unauthenticated SQL injection, privilege escalation |
| Impact | Administrator JWT, then full site takeover |
| CVSS | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-113615 |

## Summary

The plugin exposes `POST /wp-json/gs-jwt/v1/verify-otp` without
authentication. The `otp` and `mobile` parameters are concatenated into a raw
SQL query. A `UNION SELECT` makes the handler return a row for any user id,
and the handler then signs a JWT for that user. The plugin accepts that JWT
for the whole REST API through `determine_current_user`, so the token carries
the user's privileges.

## Root cause

```php
// gs-jwt.php, gs_wp_jwt_otp_verify_endpoint_handler()
$OTP = sanitize_text_field($request['otp']);
$mobile = sanitize_text_field($request['mobile']);

$q = "
SELECT t1.user_id, t1.meta_value as mobile FROM {$wpdb->prefix}usermeta as t1
INNER JOIN {$wpdb->prefix}usermeta as t2 ON t1.user_id = t2.user_id
WHERE
t1.meta_key = '_gs_jwt_fs_otp' AND t1.meta_value = '{$OTP}' AND
t2.meta_key = 'billing_phone' AND t2.meta_value = '{$mobile}'
";
$if_exist_otp = $wpdb->get_results($q);

if (count($if_exist_otp) != 0) {
$user_id = $if_exist_otp[0]->user_id;
...
$user_data = gs_wp_jwt_encode_token($user_id);
}
```

`sanitize_text_field()` does not escape quotes. The expiry check reads
`_gs_jwt_fs_otp_exp` from the target user; a user without that meta yields an
empty string, `new DateTime('')` is "now", and the ten minute window passes.

## How it works

1. **Probe** — `GET /wp-json/` (or `?rest_route=/`) must return a JSON index.
Redirects are followed and `http` is upgraded when the server answers 308.
2. **Forge** — `POST /wp-json/gs-jwt/v1/verify-otp` with:

Community Rating
0

Login to rate this exploit

Quick Actions