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.

··Feeds·Contact·Privacy·© 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
GitHub

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
bishopfox/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
4 days agoNot yet reviewed
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

root@kitploit:~
# 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

Examples

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

root@kitploit:~
$ ./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:

root@kitploit:~
$ ./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:

root@kitploit:~
$ ./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:

root@kitploit:~
$ ./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:

root@kitploit:~
$ ./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:

The verdict is read from Remoting/Send because only the duplex-streaming and unary handlers propagate the interceptor's status; the server- and client-streaming handlers overwrite it on both builds.

It reports patch state, not a version

The check observes the behaviour of the patched code path and does not rely on a version string. It does not report which build a patched server is running.

ALPN is required

The ARM listener resets TLS connections that do not negotiate ALPN h2, so the tool sets it explicitly. A scanner that connects over TLS without ALPN receives no data from the port.

Interpreting non-vulnerable results

VULNERABLE and PATCHED are each based on an observed server response. INCONCLUSIVE and ERROR mean the probes did not classify the target, so its patch state is unknown. For this reason INCONCLUSIVE is reported separately from PATCHED.

Verdicts

Every verdict carries a short reason tag. --brief prints it as the third column and --json carries it as reason.

For all three INCONCLUSIVE reasons the patch state is unknown. The reason tag indicates what to address before running the check again.

Exit codes

CodeMeaning

Exit code 0 covers PATCHED, UNAFFECTED, and all three INCONCLUSIVE reasons. To distinguish a patched target from one that could not be classified, read the verdict (the first column of --brief or the verdict field of --json) rather than the exit code.

Limitations

  • Collector role: a collector answers both probes with 14 on either build and is reported as INCONCLUSIVE. This case was tested against a mock that reproduces the response, not against a deployed collector.
  • Header parsing: the tool scans for grpc-status as a literal HPACK field and does not implement Huffman or dynamic-table decoding. gRPC's C core, which ARM uses, sends these trailers uncompressed. If a future build Huffman-codes them, the tool reports INCONCLUSIVE with partial-response.
  • Scope: PATCHED applies to this CVE only and does not indicate the status of other ARM vulnerabilities.
  • Exploitation and compromise: VULNERABLE indicates that the fix is not present on the path the probe reached. The tool does not execute code on the target and does not detect prior compromise; see the NetworkConnectionEntry indicator above.
  • Reachability: results reflect what the server exposes to the network location the tool runs from. On a default install TCP 55555 listens on all interfaces, including IPv6.

Remediation

Upgrade to ARM 2026.2.1.7 or later, per the ARM 2026.2.1 release notes. Builds 2026.2.0.42 and earlier are affected.

Additional notes:

  • Restrict TCP 55555 to ARM's own collectors and clients. This limits exposure on systems that cannot be patched immediately.
  • The fix removes the fallback authentication path; it does not rotate the key. Because the key was the same on all installations, treat any period before patching as one in which the listener's authentication could be bypassed by anyone able to reach it.
  • A version inventory shows what should be installed; this check shows whether the fix is in effect on the listener.

License

This code is distributed under an MIT license.

Legal Disclaimer

Usage of this tool for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state, and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program.

See Also

  • SolarWinds ARM 2026.2.1 release notes
  • NVD — CVE-2026-28326
Download Tool
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)
Remoting/SendGetCertificateBuildVerdict
14 UNAVAILABLE0 OK<= 2026.2.0.42VULNERABLE
16 UNAUTHENTICATEDany>= 2026.2.1.7PATCHED
14 UNAVAILABLE14 UNAVAILABLEeitherINCONCLUSIVE
VerdictReason tagMeaning
VULNERABLEunavailable-on-server-roleRemoting/Send returned 14 on a listener answering GetCertificate as the server role. The fix is not present.
PATCHEDunauthenticated-gateRemoting/Send returned 16, the status introduced by the fix. Applies to this CVE only.
INCONCLUSIVEexternal-requests-refusedBoth probes returned 14. A collector role and a restricted server behave identically here on both builds. Scan the ARM server itself.
INCONCLUSIVEpartial-responseOnly one probe returned a status, so the pair cannot be compared. Retry.
INCONCLUSIVEunrecognized-statusThe endpoint answered with a status combination outside the recognized set.
UNAFFECTEDnot-armNo ARM gRPC services answered here.
ERRORunreachableThe TCP connection, TLS handshake, or HTTP/2 exchange failed.
0No target was VULNERABLE
1At least one target is VULNERABLE
2Usage error (bad arguments, invalid target, or unreadable targets file)
130Interrupted (Ctrl-C)