keycloak/keycloak

NullPointerException in CertificateInfoHelper when "keyAlias" parameter is missing in multipart request. SAST

Open

#49,246 创建于 2026年5月22日

在 GitHub 查看
 (1 评论) (1 反应) (0 负责人)Java (34,398 star) (8,346 fork)batch import
area/admin/apihelp wantedkind/bugpriority/lowstatus/auto-bumpstatus/auto-expireteam/core-protocolsteam/core-shared

描述

Before reporting an issue

  • I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them.

Area

admin/api

Describe the bug

When sending a multipart request with the required keyAlias parameter missing, the uploadForm.getFirst("keyAlias") call returns null, which results in a NullPointerException when invoking .asString(). The criticality is minor, because authorization is required to reach the endpoint.

Log:

2026-05-22 12:42:02,183 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NullPointerException: Cannot invoke "org.keycloak.http.FormPartValue.asString()" because the return value of "jakarta.ws.rs.core.MultivaluedMap.getFirst(Object)" is null
        at org.keycloak.services.util.CertificateInfoHelper.getCertificateFromRequest(CertificateInfoHelper.java:242)
        at org.keycloak.services.resources.admin.ClientAttributeCertificateResource.uploadJksCertificate(ClientAttributeCertificateResource.java:166)
        at org.keycloak.services.resources.admin.ClientAttributeCertificateResource$quarkusrestinvoker$uploadJksCertificate_8dff09afc57f1e71a1d400026a52fbdbba3d2e83.invoke(Unknown Source)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:190)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$15.runWith(VertxCoreRecorder.java:677)
        at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2651)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2630)
        at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1622)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1589)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)

Version

26.6.2

Regression

  • The issue is a regression

Expected behavior

The server should validate the multipart form parameters and return an HTTP 400 Bad Request with a meaningful error message (e.g., "keyAlias cannot be null or empty") if the required keyAlias parameter is missing from the request payload.

Actual behavior

See the description

How to Reproduce?

  1. Start Keycloak in Docker:
docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.6.2 start-dev
  1. Generate a test JKS via keytool:
keytool -genkeypair -alias my-test-alias -keyalg RSA -keysize 2048 -validity 365 -keystore test-keystore.jks -storepass password123 -keypass password123 -dname "CN=Test, O=Keycloak, C=EN"
  1. Send a multipart POST request to /admin/realms/master/clients/{client-uuid}/certificates/jwt.credential/upload-certificate . To do this, I used AI to create the following script:
#!/bin/bash

# === CONFIGURATION SETTINGS ===
KC_URL="http://localhost:8080"
REALM="master"
USERNAME="admin"
PASSWORD="admin"
CLIENT_NAME="admin-cli" # Automatically searching for UUID using this name
KEYSTORE_FILE="test-keystore.jks"
# ==============================

echo "[*] Requesting a fresh access token..."

# 1. Automatically fetch the token
TKN=$(curl -s -X POST "${KC_URL}/realms/${REALM}/protocol/openid-connect/token" \
  -d "client_id=admin-cli" \
  -d "username=${USERNAME}" \
  -d "password=${PASSWORD}" \
  -d "grant_type=password" | grep -o '"access_token":"[^"]*' | grep -o '[^"]*$')

if [ -z "$TKN" ]; then
    echo "[-] Error: Failed to obtain access token."
    exit 1
fi
echo "[+] Access token successfully obtained!"

echo "[*] Automatically searching for UUID of client '${CLIENT_NAME}'..."

# 2. Query the API to find the internal client ID
CLIENT_UUID=$(curl -s -H "Authorization: Bearer ${TKN}" \
  "${KC_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_NAME}" | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

if [ -z "$CLIENT_UUID" ]; then
    echo "[-] Error: Could not find UUID for client ${CLIENT_NAME}."
    exit 1
fi
echo "[+] Found! Internal Client UUID: ${CLIENT_UUID}"

echo "[*] Sending the vulnerability trigger to the endpoint..."

# 3. Execute the exploit request using the dynamically resolved UUID
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST \
  "${KC_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/certificates/jwt.credential/upload-certificate" \
  -H "Authorization: Bearer ${TKN}" \
  -F "keystoreFormat=JKS" \
  -F "storePassword=password123" \
  -F "keyPassword=password123" \
  -F "file=@${KEYSTORE_FILE}")

BODY=$(echo "$RESPONSE" | sed '$d')
STATUS=$(echo "$RESPONSE" | tail -n1 | cut -d':' -f2)

echo -e "\n=== RESPONSE RESULT ==="
echo "HTTP Status: $STATUS"
echo "Response Body: $BODY"

if [ "$STATUS" -eq 500 ]; then
    echo -e "\n[⚡] SUCCESS: Bug reproduced! The server crashed with 500 Internal Server Error (NullPointerException)."
elif [ "$STATUS" -eq 400 ]; then
    echo -e "\n[✓] INFO: The server returned HTTP 400. The vulnerability might already be patched."
else
    echo -e "\n[-] Something went wrong. Verify the endpoint or attribute type (jwt.credential)."
fi

The output should be:

$ bash nperepro.sh 
[*] Requesting a fresh access token...
[+] Access token successfully obtained!
[*] Automatically searching for UUID of client 'admin-cli'...
[+] Found! Internal Client UUID: b0e4871a-eca9-43d4-8f3d-38b320475c62
[*] Sending the vulnerability trigger to the endpoint...

=== RESPONSE RESULT ===
HTTP Status: 500
Response Body: {"error":"unknown_error","error_description":"For more on this error consult the server log."}

[⚡] SUCCESS: Bug reproduced! The server crashed with 500 Internal Server Error (NullPointerException).

Anything else?

Found by Linux Verification Center with SVACE

贡献者指南