Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-28326-check — Non-destructive patch-state checker for SolarWinds ARM CVE-2026-28326 that probes the gRPC listener on TCP 55555 to report VULNERABLE, PATCHED, or INCONCLUSIVE. | Kitploit
Tools/GitHubGitHub/bishopfox/cve-2026-28326-check
Defensive ToolsVulnerability ScannersVulnerability AnalysisInformation GatheringNetwork SecurityPenetration TestingRemote Access Tool
GitHubbishopfox/cve-2026-28326-check

CVE-2026-28326-check

Non-destructive patch-state checker for SolarWinds ARM CVE-2026-28326 that probes the gRPC listener on TCP 55555 to report VULNERABLE, PATCHED, or INCONCLUSIVE.

View Repository
2925 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

SolarWinds Access Rights Manager Unauthenticated RCE — Vulnerability Detection Script

An unauthenticated patch-state check for CVE-2026-28326, the unauthenticated remote code execution vulnerability in SolarWinds Access Rights Manager (ARM), fixed in ARM 2026.2.1 on 2026-09-17. CWE-798, CVSS 8.8 High (CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). Reported to SolarWinds by Kai Huang of Armadin. The AV:A in that vector assumes TCP 55555 is reachable only from an adjacent network, which is the posture ARM's own firewall tooling is built for; where the port has been opened more broadly the score understates the exposure for that environment.

ARM's gRPC remoting listener on TCP 55555 authenticates peers with a client certificate. Unpatched builds also accept a fallback credential derived from a hardcoded key that is identical on every installation, which bypasses that authentication and exposes an unsafe deserialization path. The result is code execution as the ARM service account, which runs as LocalSystem by default.

The script reports whether the fix is present on each target. It reads the status codes returned by the authentication interceptor and does not use the key, authenticate, or send data to the deserialization path. A result other than VULNERABLE does not by itself indicate that a target is patched; see Interpreting non-vulnerable results.

Usage

# single target (port defaults to 55555)
./cve_2026_28326_check.py arm.example.com

# explicit port
./cve_2026_28326_check.py arm.example.com:55555

# several targets
./cve_2026_28326_check.py arm-a.example.com arm-b.example.com

# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_28326_check.py -f targets.txt --brief

# machine-readable output for pipelines
./cve_2026_28326_check.py -f targets.txt --json > results.json

Python 3.8+, standard library only — no third-party packages.

Run the tool against the ARM server rather than a collector. Both roles listen on 55555, but a collector refuses external requests the same way before and after the fix, so the tool reports it as INCONCLUSIVE.

Options

FlagDescription
TARGETOne or more HOST[:PORT] targets; the port defaults to 55555
-f, --targets-file FILERead targets from a file (one per line; # comments)
-p, --port PORTDefault port when a target omits one (default: 55555)
--timeout SECSPer-probe timeout (default: 12)
--workers NConcurrent targets (default: 16); output stays in input order
-b, --briefSingle aligned line per target, for scanning many hosts
--jsonEmit structured JSON, including both probes per target
--no-colorDisable coloured output (also honours NO_COLOR and non-TTY)

Examples

Vulnerable server (default output; the [!] marker and VULNERABLE are shown in red on a TTY):

$ ./cve_2026_28326_check.py arm.example.com
[!] arm.example.com:55555: VULNERABLE  [unavailable-on-server-role]
      Remoting/Send returned 14 UNAVAILABLE on a listener answering as the server role, the pre-patch rejection path (<= 2026.2.0.42)

Patched server. The fixed build returns a grpc-message, which the tool prints alongside the verdict:

$ ./cve_2026_28326_check.py arm-dr.example.com
[+] arm-dr.example.com:55555: PATCHED  [unauthenticated-gate]
      Remoting/Send returned 16 UNAUTHENTICATED, the localhost and per-process-token gate introduced by the fix (>= 2026.2.1.7)
      grpc-message: Client certificate not found. Please register your application first.

Listener that cannot be classified. A collector, or a server that refuses external requests, answers both probes the same way on either build:

$ ./cve_2026_28326_check.py collector.example.com
[?] collector.example.com:55555: INCONCLUSIVE  [external-requests-refused]
      the listener refuses external requests, which a collector role and a restricted server both do identically on either build; scan the ARM server itself

Multiple targets with --brief:

$ ./cve_2026_28326_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE    arm.example.com:55555            unavailable-on-server-role
PATCHED       arm-dr.example.com:55555         unauthenticated-gate
INCONCLUSIVE  collector.example.com:55555      external-requests-refused
UNAFFECTED    db.example.com:55555             not-arm
ERROR         offline.example.com:55555        unreachable
exit: 1

JSON output with --json. Both probe results are included so the verdict can be checked against the raw responses:

$ ./cve_2026_28326_check.py arm.example.com --json
[
  {
    "target": "arm.example.com:55555",
    "verdict": "VULNERABLE",
    "reason": "unavailable-on-server-role",
    "detail": "Remoting/Send returned 14 UNAVAILABLE on a listener answering as the server role, the pre-patch rejection path (<= 2026.2.0.42)",
    "grpc_message": "",
    "probes": [
      {
        "path": "/SolarWinds_ARM_GRPC_ConnectionAuthrization.ConnectionAuthorization/GetCertificate",
        "status": 0,
        "message": "",
        "alpn": "h2",
        "note": ""
      },
      {
        "path": "/SolarWinds_ARM_Remoting.Remoting/Send",
        "status": 14,
        "message": "",
        "alpn": "h2",
        "note": ""
      }
    ]
  }
]

Is it Safe to Run?

The check is intended for use against production systems:

  • It does not use the hardcoded key or present any credential. It observes how the server rejects an anonymous peer, which depends on the installed build.
  • It does not send data to the deserialization path. The vulnerability is triggered by a serialized object in a Remoting/Send message body. Both probes send a gRPC message with a zero-length body, and the interceptor rejects the call before any message body is dispatched.
  • It does not change server state. No client certificate, credential, or session is presented. The GetCertificate probe sends an empty enrolment code, which the server rejects before it looks up or removes any pending codes, so the probe does not consume an enrolment code for a client that is mid-registration.
  • It opens two TCP connections per target, one per probe, and closes each after reading the status.

Request and log footprint

#CallPurpose
1ConnectionAuthorization/GetCertificateConfirms the endpoint is ARM and is in the server role
2Remoting/SendThe patch-state probe

On an unpatched server a rejected anonymous request logs at debug level (Unable to validate remote request token) and an informational line naming the peer. Patched builds log a rejection naming the peer and the registration hint quoted in the grpc-message above. Neither build records a session.

To look for exploitation rather than exposure, review ARM's NetworkConnectionEntry records. An entry with IsAuthenticated=false and a populated ClientCertThumbPrint is consistent with the authentication bypass and does not occur in normal traffic.

How it Works

The fix changed which status the authentication interceptor returns to a peer presenting no client certificate. Pre-patch, every rejection path in the client-certificate check set Unavailable. Post-patch, the localhost-plus-token check sets Unauthenticated with a distinctive message, and nothing else on that path returns 16. Two anonymous calls distinguish the builds:

Remoting/SendGetCertificateBuildVerdict
14 UNAVAILABLE0 OK<= 2026.2.0.42VULNERABLE
16 UNAUTHENTICATEDany>= 2026.2.1.7PATCHED
14 UNAVAILABLE14 UNAVAILABLEeitherINCONCLUSIVE
Download Tool