Every CVE Search/ Get API key

CVE-2026-54546

CloudTAK: Authenticated full-read SSRF in CloudTAK basemap import (PUT /api/basemap) — no IP-classification guard

5.0 MediumFix available

CloudTAK is a browser-based Common Operating Picture and situational awareness tool compatible with TAK. Prior to 13.22.1, the authenticated PUT /api/basemap endpoint passes an attacker-controlled URL through importBasemapURL() in api/routes/basemap.ts to fetch(url) without resolved-address classification or redirect revalidation. BasemapProtocol.isValidURL in api/lib/interface-basemap.ts checks only the HTTP or HTTPS scheme and is not applied on the vulnerable import path. Direct internal addresses, alternate IP encodings, and redirects to internal addresses can reach cloud metadata, loopback, private, and CGNAT HTTP services. The OptionalTileJSON response reflects fields including name, attribution, and tiles[0] to the caller, making the request forgery full-read rather than blind and enabling cloud credential theft and internal service disclosure. This issue is fixed in version 13.22.1.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
Low
Integrity
None
Availability
None

Exploit likelihood

0.37%Up 0.1 points since Sep 18, 2026
90%50%10%1%

What each source reports

CVE List Score used 5.0

CloudTAK: Authenticated full-read SSRF in CloudTAK basemap import (PUT /api/basemap) — no IP-classification guard

CloudTAK is a browser-based Common Operating Picture and situational awareness tool compatible with TAK. Prior to 13.22.1, the authenticated PUT /api/basemap endpoint passes an attacker-controlled URL through importBasemapURL() in api/routes/basemap.ts to fetch(url) without resolved-address classification or redirect revalidation. BasemapProtocol.isValidURL in api/lib/interface-basemap.ts checks only the HTTP or HTTPS scheme and is not applied on the vulnerable import path. Direct internal addresses, alternate IP encodings, and redirects to internal addresses can reach cloud metadata, loopback, private, and CGNAT HTTP services. The OptionalTileJSON response reflects fields including name, attribution, and tiles[0] to the caller, making the request forgery full-read rather than blind and enabling cloud credential theft and internal service disclosure. This issue is fixed in version 13.22.1.

Record
CVE-2026-54546
CVSS 3.1
5.0 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Weakness
CWE-918
Updated
2026-11-02
EUVDEUVD-2026-82331 5.0

CloudTAK is a browser-based Common Operating Picture and situational awareness tool compatible with TAK. Prior to 13.22.1, the authenticated PUT /api/basemap endpoint passes an attacker-controlled URL through importBasemapURL() in api/routes/basemap.ts to fetch(url) without resolved-address classification or redirect revalidation. BasemapProtocol.isValidURL in api/lib/interface-basemap.ts checks only the HTTP or HTTPS scheme and is not applied on the vulnerable import path. Direct internal addresses, alternate IP encodings, and redirects to internal addresses can reach cloud metadata, loopback, private, and CGNAT HTTP services. The OptionalTileJSON response reflects fields including name, attribution, and tiles[0] to the caller, making the request forgery full-read rather than blind and enabling cloud credential theft and internal service disclosure. This issue is fixed in version 13.22.1.

Record
EUVD-2026-82331
CVSS 3.1
5.0 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-vqrw-qphh-p34v 5.0

TAK-PS-Stats Web UI: Authenticated full-read SSRF in CloudTAK basemap import (PUT /api/basemap) — no IP-classification guard

Summary

PUT /api/basemap (the basemap import endpoint) fetches an attacker-supplied URL server-side with no SSRF protection whatsoever. Any authenticated user can submit a JSON body { "type": "...", "url": "<attacker url>" }; the server calls fetch(url) against that URL and then reflects the response body (name, attribution, tiles[0], zoom levels) back to the caller in the OptionalTileJSON response.

Because there is no IP-address classification, internal-only services are reachable: cloud metadata (http://169.254.169.254/...), loopback (http://127.0.0.1/...), RFC1918 ranges, and CGNAT. The response body flows back to the attacker, making this a full-read SSRF (not blind): the attacker reads the internal HTTP response verbatim. This enables theft of cloud instance credentials, internal service enumeration, and reading of internal-only HTTP endpoints from the network position of the CloudTAK API server.

The only URL check in the basemap protocol layer (BasemapProtocol.isValidURL, api/lib/interface-basemap.ts) validates the scheme is http/https only and performs no host/IP filtering — and the import path does not even call it; it goes straight from new URL(rawURL) to fetch(url).

Three independent bypass classes were confirmed end-to-end against a real deployed build:

  1. Direct internal/loopback IP literals.
  2. Alternate IP encodings (e.g. decimal http://2130706433/ = 127.0.0.1).
  3. Redirect following — fetch uses the default redirect: 'follow', so even a public initial host that 302-redirects to an internal address is followed with no re-validation.

Vulnerable code (file:line)

api/routes/basemap.ts — importBasemapURL() and the PUT /basemap handler (line numbers at commit 90d43bdd2fb7d9bd57cbce28c709d9e5207a475a):

https://github.com/dfpc-coe/CloudTAK/blob/64bc1886ff56b62f53d765d69ac90ff9fc23b54b/api/routes/basemap.ts#L68-L136

// api/routes/basemap.ts
async function importBasemapURL(
    config: Config,
    rawURL: string,
    auth?: Static<typeof BasemapImportAuth>,
): Promise<Static<typeof OptionalTileJSON>> {
    const imported: Static<typeof OptionalTileJSON> = { type: Basemap_Type.RASTER };

    let url: URL;
    try {
        url = new URL(rawURL);            // (1) only well-formedness is checked
    } catch (err) {
        throw new Err(400, err instanceof Error ? err : new Error(String(err)), 'Invalid URL');
    }

    if (isEsriLayerURL(String(url))) { /* ... ESRI proxy path ... */ }

    const tjres = await fetch(url);        // (2) SSRF sink — no host/IP guard, default redirect:follow
    if (!tjres.ok) throw new Err(400, null, 'Unable to fetch TileJSON from source URL');

    const tjbody = await tjres.json() as Record<string, any>;
    if (tjbody.name) imported.name = tjbody.name;               // (3) internal response reflected to caller
    if (tjbody.attribution) imported.attribution = tjbody.attribution;
    if (Array.isArray(tjbody.tiles) && tjbody.tiles.length) {
        imported.url = tjbody.tiles[0]...;
    }
    return imported;
}

The handler at api/routes/basemap.ts L139–L233 mounts this as PUT /api/basemap and gates it only with Auth.is_auth(config, req) (L157), i.e. any authenticated user with application/json body { type, url } reaches importBasemapURL((req.body).url, (req.body).auth).

BasemapProtocol.isValidURL at api/lib/interface-basemap.ts L136 — the only "validation" in the protocol layer — checks the scheme only and performs no IP filtering:

https://github.com/dfpc-coe/CloudTAK/blob/64bc1886ff56b62f53d765d69ac90ff9fc23b54b/api/lib/interface-basemap.ts#L136-L148

How input reaches the sink

  1. Attacker authenticates as any CloudTAK user (any role) and obtains a bearer token.
  2. Attacker sends PUT /api/basemap with Content-Type: application/json and body { "type": "...", "url": "<attacker url>" }.
  3. Handler api/routes/basemap.ts L155–L233 calls `Auth.is_auth(config,…
Record
GHSA-vqrw-qphh-p34v
CVSS 3.1
5.0 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Weakness
CWE-918
Updated
2026-11-02
NVD 5.0

CloudTAK is a browser-based Common Operating Picture and situational awareness tool compatible with TAK. Prior to 13.22.1, the authenticated PUT /api/basemap endpoint passes an attacker-controlled URL through importBasemapURL() in api/routes/basemap.ts to fetch(url) without resolved-address classification or redirect revalidation. BasemapProtocol.isValidURL in api/lib/interface-basemap.ts checks only the HTTP or HTTPS scheme and is not applied on the vulnerable import path. Direct internal addresses, alternate IP encodings, and redirects to internal addresses can reach cloud metadata, loopback, private, and CGNAT HTTP services. The OptionalTileJSON response reflects fields including name, attribution, and tiles[0] to the caller, making the request forgery full-read rather than blind and enabling cloud credential theft and internal service disclosure. This issue is fixed in version 13.22.1.

Record
CVE-2026-54546
CVSS 3.1
5.0 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Weakness
CWE-918
Updated
2026-11-02
OSV 5.0

CloudTAK: Authenticated full-read SSRF in CloudTAK basemap import (PUT /api/basemap) — no IP-classification guard

CloudTAK is a browser-based Common Operating Picture and situational awareness tool compatible with TAK. Prior to 13.22.1, the authenticated PUT /api/basemap endpoint passes an attacker-controlled URL through importBasemapURL() in api/routes/basemap.ts to fetch(url) without resolved-address classification or redirect revalidation. BasemapProtocol.isValidURL in api/lib/interface-basemap.ts checks only the HTTP or HTTPS scheme and is not applied on the vulnerable import path. Direct internal addresses, alternate IP encodings, and redirects to internal addresses can reach cloud metadata, loopback, private, and CGNAT HTTP services. The OptionalTileJSON response reflects fields including name, attribution, and tiles[0] to the caller, making the request forgery full-read rather than blind and enabling cloud credential theft and internal service disclosure. This issue is fixed in version 13.22.1.

Record
CVE-2026-54546
CVSS 3.1
5.0 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Weakness
CWE-918
Updated
2026-11-02
OSVGHSA-vqrw-qphh-p34v 5.0

TAK-PS-Stats Web UI: Authenticated full-read SSRF in CloudTAK basemap import (PUT /api/basemap) — no IP-classification guard

Summary

PUT /api/basemap (the basemap import endpoint) fetches an attacker-supplied URL server-side with no SSRF protection whatsoever. Any authenticated user can submit a JSON body { "type": "...", "url": "<attacker url>" }; the server calls fetch(url) against that URL and then reflects the response body (name, attribution, tiles[0], zoom levels) back to the caller in the OptionalTileJSON response.

Because there is no IP-address classification, internal-only services are reachable: cloud metadata (http://169.254.169.254/...), loopback (http://127.0.0.1/...), RFC1918 ranges, and CGNAT. The response body flows back to the attacker, making this a full-read SSRF (not blind): the attacker reads the internal HTTP response verbatim. This enables theft of cloud instance credentials, internal service enumeration, and reading of internal-only HTTP endpoints from the network position of the CloudTAK API server.

The only URL check in the basemap protocol layer (BasemapProtocol.isValidURL, api/lib/interface-basemap.ts) validates the scheme is http/https only and performs no host/IP filtering — and the import path does not even call it; it goes straight from new URL(rawURL) to fetch(url).

Three independent bypass classes were confirmed end-to-end against a real deployed build:

  1. Direct internal/loopback IP literals.
  2. Alternate IP encodings (e.g. decimal http://2130706433/ = 127.0.0.1).
  3. Redirect following — fetch uses the default redirect: 'follow', so even a public initial host that 302-redirects to an internal address is followed with no re-validation.

Vulnerable code (file:line)

api/routes/basemap.ts — importBasemapURL() and the PUT /basemap handler (line numbers at commit 90d43bdd2fb7d9bd57cbce28c709d9e5207a475a):

https://github.com/dfpc-coe/CloudTAK/blob/64bc1886ff56b62f53d765d69ac90ff9fc23b54b/api/routes/basemap.ts#L68-L136

// api/routes/basemap.ts
async function importBasemapURL(
    config: Config,
    rawURL: string,
    auth?: Static<typeof BasemapImportAuth>,
): Promise<Static<typeof OptionalTileJSON>> {
    const imported: Static<typeof OptionalTileJSON> = { type: Basemap_Type.RASTER };

    let url: URL;
    try {
        url = new URL(rawURL);            // (1) only well-formedness is checked
    } catch (err) {
        throw new Err(400, err instanceof Error ? err : new Error(String(err)), 'Invalid URL');
    }

    if (isEsriLayerURL(String(url))) { /* ... ESRI proxy path ... */ }

    const tjres = await fetch(url);        // (2) SSRF sink — no host/IP guard, default redirect:follow
    if (!tjres.ok) throw new Err(400, null, 'Unable to fetch TileJSON from source URL');

    const tjbody = await tjres.json() as Record<string, any>;
    if (tjbody.name) imported.name = tjbody.name;               // (3) internal response reflected to caller
    if (tjbody.attribution) imported.attribution = tjbody.attribution;
    if (Array.isArray(tjbody.tiles) && tjbody.tiles.length) {
        imported.url = tjbody.tiles[0]...;
    }
    return imported;
}

The handler at api/routes/basemap.ts L139–L233 mounts this as PUT /api/basemap and gates it only with Auth.is_auth(config, req) (L157), i.e. any authenticated user with application/json body { type, url } reaches importBasemapURL((req.body).url, (req.body).auth).

BasemapProtocol.isValidURL at api/lib/interface-basemap.ts L136 — the only "validation" in the protocol layer — checks the scheme only and performs no IP filtering:

https://github.com/dfpc-coe/CloudTAK/blob/64bc1886ff56b62f53d765d69ac90ff9fc23b54b/api/lib/interface-basemap.ts#L136-L148

How input reaches the sink

  1. Attacker authenticates as any CloudTAK user (any role) and obtains a bearer token.
  2. Attacker sends PUT /api/basemap with Content-Type: application/json and body { "type": "...", "url": "<attacker url>" }.
  3. Handler api/routes/basemap.ts L155–L233 calls `Auth.is_auth(config,…
Record
GHSA-vqrw-qphh-p34v
CVSS 3.1
5.0 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Weakness
CWE-918
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
@tak-ps/cloudtak
npm
before 13.22.1
13.22.1

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-54546 is a medium vulnerability (CVSS 5.0) in Dfpc-coe Cloudtak, published July 17, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.37%, the 31st percentile. Fixed in @tak-ps/cloudtak 13.22.1.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open