From 42c3a88de7c34343ef19bbc51927faa64b3baa9c Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Fri, 3 Jul 2026 23:05:07 +0200 Subject: [PATCH 1/8] Improve sponsor ad hover layout --- theme/pagetoc.css | 66 +++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/theme/pagetoc.css b/theme/pagetoc.css index 29a4e9340..b628f2d99 100644 --- a/theme/pagetoc.css +++ b/theme/pagetoc.css @@ -127,6 +127,7 @@ padding: 5px; display: none; /*changed via JS once ad is loaded*/ flex-direction: column; + align-items: center; text-decoration: none !important; overflow: hidden; backdrop-filter: blur(6px); @@ -141,27 +142,31 @@ object-fit: contain; align-self: center; flex: 0 0 auto; - transition-property: all; - transition-timing-function: cubic-bezier(.4,0,.2,1); - transition-duration: .3s; + transition: + height .36s cubic-bezier(.22,1,.36,1), + width .36s cubic-bezier(.22,1,.36,1), + max-height .36s cubic-bezier(.22,1,.36,1), + min-height .36s cubic-bezier(.22,1,.36,1), + padding .36s cubic-bezier(.22,1,.36,1), + transform .36s cubic-bezier(.22,1,.36,1); } .sidesponsor:hover img{ - width: 42px; + width: 56px; padding: 4px; grid-area: sponsor-image; align-self: center; - transition-duration: 0s; + justify-self: center; } .sidesponsor:hover { display: grid !important; - grid-template-columns: auto minmax(0, 1fr); - grid-template-rows: max-content minmax(0, 1fr) auto; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: max-content max-content minmax(0, 1fr) auto; grid-template-areas: - "sponsor-image sponsor-title" - "sponsor-description sponsor-description" - "sponsor-cta sponsor-cta"; - column-gap: 8px; - row-gap: 10px; + "sponsor-image" + "sponsor-title" + "sponsor-description" + "sponsor-cta"; + row-gap: 3px; align-items: center; align-content: start; overflow: hidden; @@ -177,6 +182,7 @@ color: var(--sponsor-fg); overflow-wrap: anywhere; text-align: center; + align-self: center; flex: 0 0 auto; } .sidesponsor .sponsor-title.sponsor-title--long { @@ -184,13 +190,15 @@ } .sidesponsor:hover .sponsor-title{ grid-area: sponsor-title; - margin-left: 0; + margin-left: 8px; + margin-right: 8px; margin-top: 0; margin-bottom: 0; - font-size: 1.55rem; - line-height: 1.15; + font-size: 1.45rem; + line-height: 1.1; overflow-wrap: anywhere; align-self: center; + justify-self: center; } .sidesponsor .sponsor-description{ display: block; @@ -203,23 +211,31 @@ opacity: 0; visibility: hidden; overflow: hidden; + transform: translateY(5px); transition: - opacity .18s ease .14s, - visibility 0s linear .32s; + max-height .44s cubic-bezier(.22,1,.36,1), + opacity .34s ease .08s, + transform .44s cubic-bezier(.22,1,.36,1), + visibility 0s linear .44s; } .sidesponsor:hover .sponsor-description{ grid-area: sponsor-description; + align-self: start; min-height: 0; max-height: 100%; - margin-top: 0; + margin-top: 3px; margin-bottom: 0; font-size: calc(1em - 1px); opacity: 1; visibility: visible; + transform: translateY(0); overflow: auto; overscroll-behavior: contain; - animation: sponsorTextReveal .12s ease .18s forwards; - transition: none; + transition: + max-height .44s cubic-bezier(.22,1,.36,1), + opacity .36s ease .08s, + transform .44s cubic-bezier(.22,1,.36,1), + visibility 0s linear 0s; } .sidesponsor .sponsor-cta{ margin-top: auto; @@ -235,20 +251,20 @@ } .sidesponsor:hover .sponsor-cta{ grid-area: sponsor-cta; - margin-top: 22px; - margin-bottom: 8px; - padding: 9px 14px; + margin-top: 4px; + margin-bottom: 2px; + padding: 11px 18px; box-sizing: border-box; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 1.2rem; + font-size: 1.3rem; font-weight: 800; line-height: 1.1; visibility: hidden; opacity: 0; - animation: sponsorTextReveal .12s ease .18s forwards; + animation: sponsorTextReveal .28s ease .12s forwards; } @keyframes sponsorTextReveal { 0% { From fdd3d95668e08effcf844d588cacfd566a0a810f Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Sat, 4 Jul 2026 00:45:00 +0200 Subject: [PATCH 2/8] Add local cloud Kubernetes updates --- .../gcp-containers-gke-and-composer-enum.md | 19 ++++- .../README.md | 4 +- .../exposing-services-in-kubernetes.md | 67 ++++++++++++--- .../kubernetes-enumeration.md | 85 ++++++++++++++++--- .../kubernetes-securitycontext-s.md | 22 ++++- .../kubernetes-network-attacks.md | 6 +- .../kubernetes-pivoting-to-clouds.md | 57 ++++++++++++- ...bernetes-role-based-access-control-rbac.md | 34 +++++++- ...bernetes-validatingwebhookconfiguration.md | 83 +++++++++++++----- .../pentesting-kubernetes-services/README.md | 37 +++++++- 10 files changed, 358 insertions(+), 56 deletions(-) diff --git a/src/pentesting-cloud/gcp-security/gcp-services/gcp-containers-gke-and-composer-enum.md b/src/pentesting-cloud/gcp-security/gcp-services/gcp-containers-gke-and-composer-enum.md index a2fcc18a0..6d00aefa2 100644 --- a/src/pentesting-cloud/gcp-security/gcp-services/gcp-containers-gke-and-composer-enum.md +++ b/src/pentesting-cloud/gcp-security/gcp-services/gcp-containers-gke-and-composer-enum.md @@ -74,6 +74,24 @@ You can read more about `gcloud` for containers [here](https://cloud.google.com/ This is a simple script to enumerate kubernetes in GCP: [https://gitlab.com/gitlab-com/gl-security/security-operations/gl-redteam/gcp_k8s_enum](https://gitlab.com/gitlab-com/gl-security/security-operations/gl-redteam/gcp_k8s_enum) +### Current GKE identity and metadata checks + +When reviewing modern GKE clusters, separate Google Cloud IAM permissions, Kubernetes RBAC, pod workload identity, and node credentials. A Google principal can often retrieve cluster endpoint data with `container.clusters.get`, but the resulting Kubernetes requests still need to pass GKE/Kubernetes authorization and any network restrictions such as private endpoints or authorized networks. + +Workload Identity Federation for GKE is the preferred way for pods to access Google Cloud APIs. Check whether the cluster has a workload pool and whether Kubernetes service accounts are mapped directly as IAM principals or are allowed to impersonate IAM service accounts: + +```bash +gcloud container clusters describe --region \ + --format='value(workloadIdentityConfig.workloadPool)' + +kubectl get serviceaccounts -A -o yaml | grep -n 'iam.gke.io' -B 5 -A 8 +kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName' +``` + +If a service account has the annotation `iam.gke.io/gcp-service-account`, review the IAM service account policy for `roles/iam.workloadIdentityUser` grants to Kubernetes service account principals. Also check IAM allow policies for direct workload identity principals or broad principal sets. + +Metadata access depends on cluster mode, node pool configuration, and workload settings. Do not assume every pod can steal the node service account. In Workload Identity-enabled environments, ordinary pods should use the GKE metadata server to obtain the workload identity intended for their Kubernetes service account. Node compromise, `hostNetwork` pods in some Standard configurations, and legacy node metadata exposure can still change the blast radius, so verify the actual node pool metadata mode, node service account, OAuth scopes, and pod placement. + ### TLS Boostrap Privilege Escalation Initially this privilege escalation technique allowed to **privesc inside the GKE cluster** effectively allowing an attacker to **fully compromise it**. @@ -104,4 +122,3 @@ Even if the API **doesn't allow to modify resources**, it could be possible to f {{#include ../../../banners/hacktricks-training.md}} - diff --git a/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md b/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md index 6ed01d2cd..4094cdf26 100644 --- a/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md +++ b/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md @@ -357,7 +357,8 @@ curl -v -H "Authorization: Bearer " https://:/api/v1 ### Creating and Reading Secrets -There is a special kind of a Kubernetes secret of type **kubernetes.io/service-account-token** which stores serviceaccount tokens. +There is a special kind of Kubernetes Secret of type **kubernetes.io/service-account-token** which stores service account tokens. Modern Kubernetes versions do **not** automatically create one long-lived Secret for every ServiceAccount; projected, bound TokenRequest tokens are the normal workload path. However, manually created service account token Secrets are still supported, and upgraded or legacy clusters may still contain long-lived token Secrets. Current clusters can also mark unused auto-generated legacy token Secrets invalid and eventually clean them up, leaving labels such as `kubernetes.io/legacy-token-invalid-since` and `kubernetes.io/legacy-token-last-used`. + If you have permissions to create and read secrets, and you also know the serviceaccount's name, you can create a secret as follows and then steal the victim serviceaccount's token from it: ```yaml @@ -870,4 +871,3 @@ https://github.com/aquasecurity/kube-bench {{#include ../../../banners/hacktricks-training.md}} - diff --git a/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md b/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md index eb4d59e7e..da2c54fe3 100644 --- a/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md +++ b/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md @@ -116,6 +116,8 @@ kubectl get services --all-namespaces -o=custom-columns='NAMESPACE:.metadata.nam Traffic that ingresses into the cluster with the **external IP** (as **destination IP**), on the Service port, will be **routed to one of the Service endpoints**. `externalIPs` are not managed by Kubernetes and are the responsibility of the cluster administrator. +`externalIPs` is a sensitive route-control field because a user who can set it might claim traffic for an IP address the Service owner should not control if the surrounding network routes that IP to the cluster. Kubernetes announced the deprecation and planned removal of Service `externalIPs` in v1.36, so prefer controller-owned exposure mechanisms such as LoadBalancer integrations or Gateway API where possible, and restrict/admit this field carefully while it still exists. + In the Service spec, `externalIPs` can be specified along with any of the `ServiceTypes`. In the example below, "`my-service`" can be accessed by clients on "`80.11.12.10:80`" (`externalIP:port`) ```yaml @@ -160,6 +162,21 @@ List all ExternalNames: kubectl get services --all-namespaces | grep ExternalName ``` +### EndpointSlices + +EndpointSlices show the concrete backend addresses and ports that a Service currently routes to. They are especially useful when a Service has no selector, when labels do not explain the traffic path, or when only some backends are ready. + +List EndpointSlices associated with Services: + +```bash +kubectl get endpointslices --all-namespaces +kubectl get endpointslice -n -l kubernetes.io/service-name= -o yaml +kubectl get endpointslice -n -l kubernetes.io/service-name= \ + -o custom-columns='NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready,PORTS:.ports[*].port' +``` + +When reviewing exposure, compare the Service selector with the EndpointSlice `targetRef`, endpoint addresses, readiness conditions, and ports. A selectorless Service can be paired with manually managed EndpointSlices and route traffic to non-Pod or unexpected destinations. + ### Ingress Unlike all the above examples, **Ingress is NOT a type of service**. Instead, it sits **in front of multiple services and act as a “smart router”** or entrypoint into your cluster. @@ -171,28 +188,37 @@ The default GKE ingress controller will spin up a [HTTP(S) Load Balancer](https: The YAML for a Ingress object on GKE with a [L7 HTTP Load Balancer](https://cloud.google.com/compute/docs/load-balancing/http/) might look like this: ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress spec: - backend: - serviceName: other - servicePort: 8080 + defaultBackend: + service: + name: other + port: + number: 8080 rules: - host: foo.mydomain.com http: paths: - - backend: - serviceName: foo - servicePort: 8080 + - path: / + pathType: Prefix + backend: + service: + name: foo + port: + number: 8080 - host: mydomain.com http: paths: - - path: /bar/* + - path: /bar + pathType: Prefix backend: - serviceName: bar - servicePort: 8080 + service: + name: bar + port: + number: 8080 ``` List all the ingresses: @@ -207,12 +233,29 @@ Although in this case it's better to get the info of each one by one to read it kubectl get ingresses --all-namespaces -o=yaml ``` +### Gateway API + +Gateway API is the newer Kubernetes API for exposing Services. It separates infrastructure-owned Gateway objects from application-owned Route objects such as HTTPRoute. This is useful for delegation, but it also means exposure can be split across namespaces. + +List Gateway API exposure objects: + +```bash +kubectl get gatewayclasses +kubectl get gateways --all-namespaces +kubectl get httproutes --all-namespaces +kubectl get gateway -n -o yaml +kubectl get httproute -n -o yaml +``` + +Check Gateway listeners, allowed route namespaces, Route `parentRefs`, hostnames, filters, backend references, and status conditions such as whether the route was accepted. A Route that is accepted by a shared Gateway can expose a backend even when no legacy Ingress object exists. + ### References - [https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0) - [https://kubernetes.io/docs/concepts/services-networking/service/](https://kubernetes.io/docs/concepts/services-networking/service/) +- [https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/](https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/) +- [https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/) +- [https://gateway-api.sigs.k8s.io/](https://gateway-api.sigs.k8s.io/) {{#include ../../banners/hacktricks-training.md}} - - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md index bb400387f..abe51fb6b 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md @@ -192,6 +192,23 @@ k api-resources --namespaced=false #Resources NOT specific to a namespace {{#endtab }} {{#endtabs }} +### Object metadata worth checking + +When you can read an object, export the full YAML or JSON instead of relying only on table output or `describe`. The most useful security context is often in generic object fields that exist across many resource types: + +```bash +kubectl get pod -n -o yaml +kubectl get deploy -n -o json | jq '.metadata, .spec, .status' +kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName,PHASE:.status.phase' +``` + +- `metadata.uid`, `name`, `namespace`, `apiVersion` and `kind` identify the exact object and avoid confusion between objects with the same name in different namespaces or API groups. +- `metadata.labels` and selectors connect Services, Deployments, ReplicaSets, Pods, NetworkPolicies and automation. Following selectors is often the fastest way to identify the real backend pods for a Service. +- `metadata.annotations` can leak operational context such as ingress behavior, cloud load balancer settings, GitOps or Helm metadata, policy exemptions, and service mesh configuration. They should not contain secrets, but real clusters often expose useful clues there. +- `metadata.ownerReferences` shows controller lineage. If a Pod is owned by a ReplicaSet owned by a Deployment, changing or deleting only the Pod usually does not fix the source. +- `metadata.finalizers` and `metadata.deletionTimestamp` explain resources stuck in deletion and can reveal cleanup controllers or persistence/disruption tricks. +- `status`, Events, and conditions can reveal node placement, pod IPs, image IDs, failure messages, scheduling issues, admission denials, and controller progress. They are useful clues, but audit logs are still required to prove who performed an action. + ### Get Current Privileges {{#tabs }} @@ -330,7 +347,7 @@ kurl -k -v https://$APISERVER/api/v1/namespaces/{namespace}/serviceaccounts ### Get Deployments -The deployments specify the **components** that need to be **run**. +Deployments specify the desired state for stateless application workloads. They create ReplicaSets, and those ReplicaSets create Pods. {{#tabs }} {{#tab name="kubectl" }} @@ -345,7 +362,30 @@ k get deployments -n custnamespace {{#tab name="API" }} ```bash -kurl -v https://$APISERVER/api/v1/namespaces//deployments/ +kurl -v https://$APISERVER/apis/apps/v1/namespaces//deployments/ +``` + +{{#endtab }} +{{#endtabs }} + +### Get StatefulSets + +StatefulSets manage Pods that need stable names, ordered rollout behavior, and often per-replica persistent volumes. + +{{#tabs }} +{{#tab name="kubectl" }} + +```bash +k get statefulsets +k get statefulsets -n custnamespace +``` + +{{#endtab }} + +{{#tab name="API" }} + +```bash +kurl -v https://$APISERVER/apis/apps/v1/namespaces//statefulsets/ ``` {{#endtab }} @@ -391,7 +431,7 @@ k get services -n custnamespace {{#tab name="API" }} ```bash -kurl -v https://$APISERVER/api/v1/namespaces/default/services/ +kurl -v https://$APISERVER/api/v1/namespaces//services/ ``` {{#endtab }} @@ -421,7 +461,7 @@ kurl -v https://$APISERVER/api/v1/nodes/ ### Get DaemonSets -**DaeamonSets** allows to ensure that a **specific pod is running in all the nodes** of the cluster (or in the ones selected). If you delete the DaemonSet the pods managed by it will be also removed. +**DaemonSets** ensure that a **specific Pod is running on all selected nodes** of the cluster. If you delete the DaemonSet, the Pods managed by it will also be removed. {{#tabs }} {{#tab name="kubectl" }} @@ -435,21 +475,22 @@ k get daemonsets {{#tab name="API" }} ```bash -kurl -v https://$APISERVER/apis/extensions/v1beta1/namespaces/default/daemonsets +kurl -v https://$APISERVER/apis/apps/v1/namespaces//daemonsets ``` {{#endtab }} {{#endtabs }} -### Get cronjob +### Get Jobs -Cron jobs allows to schedule using crontab like syntax the launch of a pod that will perform some action. +Jobs create Pods that run until completion. They are commonly used for migrations, backups, batch work, and one-off administrative tasks. {{#tabs }} {{#tab name="kubectl" }} ```bash -k get cronjobs +k get jobs +k get jobs -n custnamespace ``` {{#endtab }} @@ -457,7 +498,30 @@ k get cronjobs {{#tab name="API" }} ```bash -kurl -v https://$APISERVER/apis/batch/v1beta1/namespaces//cronjobs +kurl -v https://$APISERVER/apis/batch/v1/namespaces//jobs +``` + +{{#endtab }} +{{#endtabs }} + +### Get CronJobs + +CronJobs use a crontab-like schedule to create Jobs that launch Pods for task-style execution. + +{{#tabs }} +{{#tab name="kubectl" }} + +```bash +k get cronjobs +k get cronjobs -n custnamespace +``` + +{{#endtab }} + +{{#tab name="API" }} + +```bash +kurl -v https://$APISERVER/apis/batch/v1/namespaces//cronjobs ``` {{#endtab }} @@ -849,6 +913,3 @@ https://www.cyberark.com/resources/threat-research-blog/kubernetes-pentest-metho {{#endref}} {{#include ../../banners/hacktricks-training.md}} - - - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md b/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md index ef6e7b58c..33203cf40 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md @@ -55,12 +55,32 @@ Note that the attributes set in **both SecurityContext and PodSecurityContext**, |

seccompProfile
SeccompProfile

| The **seccomp options** to use by this container. | |

windowsOptions
WindowsSecurityContextOptions

| The **Windows specific settings** applied to all containers. | +## Practical workload review checklist + +When reviewing a Pod or workload template, inspect both `spec.securityContext` and every container-level `securityContext` under `containers`, `initContainers`, and `ephemeralContainers`. Container-level fields can override the pod-level defaults, so a safe-looking pod default does not guarantee that every container is safe. + +High-risk combinations to prioritize: + +- `privileged: true`, especially with `hostPID`, `hostIPC`, `hostNetwork`, `hostPath`, host ports, or runtime socket mounts. +- Added capabilities such as `SYS_ADMIN`, `NET_ADMIN`, `SYS_PTRACE`, `SYS_MODULE`, `DAC_READ_SEARCH`, or `DAC_OVERRIDE`. +- `allowPrivilegeEscalation: true` or unset in containers that can execute attacker-controlled code. +- `seccompProfile: Unconfined`, `procMount: Unmasked`, or missing runtime profiles on sensitive workloads. +- Writable root filesystems or broad writable volume mounts in workloads that process untrusted input. +- Missing CPU, memory, or ephemeral-storage requests and limits in multi-tenant namespaces. + +For most application workloads, a good baseline is to run as a non-root UID, set `runAsNonRoot: true`, set `allowPrivilegeEscalation: false`, drop all capabilities and add back only the minimum required ones, use `seccompProfile: RuntimeDefault`, prefer a read-only root filesystem, and avoid host namespaces, hostPath mounts, and privileged mode. + +At cluster level, use [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) namespace labels to enforce the Kubernetes [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) where possible. Use `restricted` for namespaces that can support it, at least `baseline` for ordinary application namespaces, and keep privileged exceptions narrow, documented, and isolated to trusted platform namespaces or node pools. + ## References - [https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritycontext-v1-core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritycontext-v1-core) - [https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core) +- [https://kubernetes.io/docs/tasks/configure-pod-container/security-context/](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) +- [https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/](https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/) +- [https://kubernetes.io/docs/concepts/security/pod-security-standards/](https://kubernetes.io/docs/concepts/security/pod-security-standards/) +- [https://kubernetes.io/docs/concepts/security/pod-security-admission/](https://kubernetes.io/docs/concepts/security/pod-security-admission/) {{#include ../../../banners/hacktricks-training.md}} - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-network-attacks.md b/src/pentesting-cloud/kubernetes-security/kubernetes-network-attacks.md index 14a0fea49..84185e540 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-network-attacks.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-network-attacks.md @@ -160,6 +160,9 @@ Therefore, the pod will send the **DNS requests to the address 10.96.0.10** whic > > Moreover, if the **DNS server** is in the **same node as the attacker**, the attacker can **intercept all the DNS request** of any pod in the cluster (between the DNS server and the bridge) and modify the responses. +> [!NOTE] +> Validate the active CNI and DNS path before assuming this works in a real cluster. Some CNIs route or isolate same-node traffic differently, and clusters using NodeLocal DNSCache may send pod DNS queries to a node-local address before forwarding to CoreDNS. In those environments, DNS spoofing depends on pod placement, packet capabilities, resolver configuration, node-local cache behavior, and whether applications verify peers with TLS or another identity mechanism. + ## ARP Spoofing in pods in the same Node Our goal is to **steal at least the communication from the ubuntu-victim to the mysql**. @@ -281,6 +284,8 @@ google.com. 1 IN A 1.1.1.1 A user with write permissions over the configmap `coredns` in the kube-system namespace can modify the DNS responses of the cluster. +Also review NodeLocal DNSCache if it is deployed. It usually runs as a hostNetwork DaemonSet and has its own ConfigMap, logs, cache, and forwarding path. A CoreDNS change may not be the only place where DNS behavior can be affected or observed. + Check more information about this attack in: {{#ref}} @@ -330,4 +335,3 @@ It will install agents in the selected pods and gather their traffic information {{#include ../../banners/hacktricks-training.md}} - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md b/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md index 7cd64362e..4dbf5060f 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md @@ -298,6 +298,8 @@ To access the node metadata endpoint you need to: (Note that the metadata endpoint is at 169.254.169.254 as always). +In newer EKS environments, verify the node and cluster mode before assuming pods can reach the node instance profile. Amazon Linux 2023 EKS optimized AMIs default the IMDS hop limit to 1, and EKS Auto Mode enables `disablePodIMDS` by default, so ordinary pods should not receive node-role credentials unless the operator changed those settings or the pod has another node-level path such as `hostNetwork` or node compromise. The recommended pattern is to block pod access to node IMDS and use IRSA or EKS Pod Identity for workload AWS permissions. + To **escape to the node** you can use the following command to run a pod with `hostNetwork` enabled: ```bash @@ -336,13 +338,64 @@ kubectl --context=node1 create token -n ns1 sa-priv \ --bound-object-uid=7f7e741a-12f5-4148-91b4-4bc94f75998d ``` +## Azure / AKS + +In AKS, keep three identity paths separated during assessment: + +- **Azure to Kubernetes**: Azure principals can retrieve user or admin kubeconfigs through Azure Resource Manager if their Azure RBAC role allows it. Local admin kubeconfigs from `az aks get-credentials --admin` are certificate-based credentials and can bypass normal Microsoft Entra user/group governance unless local accounts are disabled. +- **Microsoft Entra to Kubernetes**: Entra-integrated clusters authenticate users, groups, or service principals through `kubelogin`/exec kubeconfigs. The final Kubernetes action can be authorized by native Kubernetes RBAC or by Azure RBAC for Kubernetes Authorization. +- **Kubernetes to Azure**: Pods should normally use Microsoft Entra Workload ID, which exchanges projected Kubernetes service account tokens with Entra through the AKS OIDC issuer and federated identity credentials. + +Useful AKS identity checks from Azure: + +```bash +az aks show -g -n \ + --query '{disableLocalAccounts:disableLocalAccounts,enableAzureRBAC:enableAzureRBAC,oidcIssuerProfile:oidcIssuerProfile,securityProfile:securityProfile,identity:identity,identityProfile:identityProfile,nodeResourceGroup:nodeResourceGroup}' \ + -o yaml + +AKS_ID=$(az aks show -g -n --query id -o tsv) +az role assignment list --scope "$AKS_ID" --include-inherited -o table +az role assignment list --scope "$AKS_ID/namespaces/" -o table +``` + +From Kubernetes, search for AKS Workload ID signals: + +```bash +kubectl get serviceaccounts -A -o yaml | grep -n 'azure.workload.identity' -B 6 -A 8 +kubectl get pods -A -o yaml | grep -n 'azure.workload.identity/use' -B 8 -A 8 +``` + +The relevant Workload ID fields are usually: + +```yaml +metadata: + annotations: + azure.workload.identity/client-id: "" + azure.workload.identity/tenant-id: "" +--- +metadata: + labels: + azure.workload.identity/use: "true" +``` + +If the cluster still uses the deprecated Microsoft Entra pod-managed identity model, look for the old CRDs and NMI/MIC components instead of the Workload ID annotations: + +```bash +kubectl get crd | grep -i azureidentity +kubectl get azureidentity,azureidentitybinding,azureassignedidentity -A -o yaml 2>/dev/null +kubectl get ds -A | grep -Ei 'nmi|mic|aad-pod-identity' +``` + +AKS nodes are Azure VM scale set instances, so node or host-level access can expose Azure Instance Metadata Service at `169.254.169.254`. Do not assume an ordinary pod should receive node managed identity credentials: verify workload identity settings, legacy pod identity/NMI behavior, hostNetwork usage, network controls, and node access first. If a node identity has broad Azure permissions, node compromise can become an Azure pivot even when application Workload ID is correctly scoped. + ## References - [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) - [https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c) - [https://blogs.halodoc.io/iam-roles-for-service-accounts-2/](https://blogs.halodoc.io/iam-roles-for-service-accounts-2/) +- [https://learn.microsoft.com/en-us/azure/aks/concepts-identity](https://learn.microsoft.com/en-us/azure/aks/concepts-identity) +- [https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) +- [https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization](https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization) {{#include ../../banners/hacktricks-training.md}} - - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-role-based-access-control-rbac.md b/src/pentesting-cloud/kubernetes-security/kubernetes-role-based-access-control-rbac.md index 91b261ecf..93e9348b1 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-role-based-access-control-rbac.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-role-based-access-control-rbac.md @@ -95,7 +95,7 @@ kubectl get pods --all-namespaces [**From the docs:**](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding) A **role binding grants the permissions defined in a role to a user or set of users**. It holds a list of subjects (users, groups, or service accounts), and a reference to the role being granted. A **RoleBinding** grants permissions within a specific **namespace** whereas a **ClusterRoleBinding** grants that access **cluster-wide**. ```yaml:RoleBinding -piVersion: rbac.authorization.k8s.io/v1 +apiVersion: rbac.authorization.k8s.io/v1 # This role binding allows "jane" to read pods in the "default" namespace. # You need to already have a Role named "pod-reader" in that namespace. kind: RoleBinding @@ -132,6 +132,36 @@ roleRef: **Permissions are additive** so if you have a clusterRole with “list” and “delete” secrets you can add it with a Role with “get”. So be aware and test always your roles and permissions and **specify what is ALLOWED, because everything is DENIED by default.** +### Details worth checking + +RBAC uses resource names as they appear in API URLs, not the YAML `kind`. A Pod is `pods`, a Deployment is `deployments`, and subresources are written with a slash such as `pods/log`, `pods/exec`, `pods/portforward`, `pods/ephemeralcontainers`, `deployments/scale`, `serviceaccounts/token`, `nodes/proxy` or `services/proxy`. A permission on `pods` does not automatically grant access to `pods/exec` or `pods/log`. + +`resourceNames` can restrict some requests to specific object names: + +```yaml +rules: + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["app-config"] + verbs: ["get", "update"] +``` + +This does not restrict top-level `create` or `deletecollection` by name. For `list` and `watch`, the client must include a matching `metadata.name` field selector, otherwise the request is not authorized by that rule: + +```bash +kubectl get configmaps -n default --field-selector=metadata.name=app-config +``` + +Use exact access reviews for high-impact checks: + +```bash +kubectl auth can-i create pods/exec -n default +kubectl auth can-i create serviceaccounts/token -n default +kubectl auth can-i impersonate users +kubectl auth can-i bind clusterroles.rbac.authorization.k8s.io +kubectl auth can-i escalate clusterroles.rbac.authorization.k8s.io +``` + ## **Enumerating RBAC** ```bash @@ -164,5 +194,3 @@ abusing-roles-clusterroles-in-kubernetes/ {{#include ../../banners/hacktricks-training.md}} - - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-validatingwebhookconfiguration.md b/src/pentesting-cloud/kubernetes-security/kubernetes-validatingwebhookconfiguration.md index 70d3426d0..dc2640be2 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-validatingwebhookconfiguration.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-validatingwebhookconfiguration.md @@ -6,11 +6,20 @@ ## Definition -ValidatingWebhookConfiguration is a Kubernetes resource that defines a validating webhook, which is a server-side component that validates incoming Kubernetes API requests against a set of predefined rules and constraints. +`ValidatingWebhookConfiguration` is a Kubernetes resource that registers one or more validating admission webhooks. These webhooks receive AdmissionReview requests from the API server after authentication and authorization, but before the object is persisted. + +Validating webhooks can reject a request. Mutating webhooks, configured with `MutatingWebhookConfiguration`, can change the object first. Security reviews should usually inspect both resources because a malicious or weak mutating webhook can rewrite workloads, while a validating webhook or policy engine can block or allow them. ## Purpose -The purpose of a ValidatingWebhookConfiguration is to define a validating webhook that will enforce a set of predefined rules and constraints on incoming Kubernetes API requests. The webhook will validate the requests against the rules and constraints defined in the configuration, and will return an error if the request does not conform to the rules. +The purpose of a `ValidatingWebhookConfiguration` is to define when the API server should call a validating webhook and how it should handle the webhook result. The important security question is not only "is a policy installed?", but also: + +- Which API groups, resources, operations, and scopes does it match? +- Which namespaces or objects are excluded by selectors? +- Does `matchConditions` skip any request classes? +- Does `failurePolicy` fail open with `Ignore` or fail closed with `Fail`? +- Is the webhook service reachable, trusted by the configured `caBundle`, and run by a highly privileged service account? +- Does the policy engine also expose exception resources, excluded users, or excluded groups? **Example** @@ -21,22 +30,29 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: example-validation-webhook - namespace: default -webhook: - name: example-validation-webhook - clientConfig: - url: https://example.com/webhook - serviceAccountName: example-service-account - rules: - - apiGroups: - - "" - apiVersions: - - "*" - operations: - - CREATE - - UPDATE - resources: - - pods +webhooks: + - name: pods.example.local + admissionReviewVersions: ["v1"] + sideEffects: None + failurePolicy: Fail + timeoutSeconds: 5 + clientConfig: + service: + namespace: webhook-system + name: example-validation-webhook + path: /validate + caBundle: + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["pods"] + scope: "Namespaced" + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: ["kube-system"] ``` The main difference between a ValidatingWebhookConfiguration and policies : @@ -49,9 +65,23 @@ The main difference between a ValidatingWebhookConfiguration and policies : ## Enumeration ``` -$ kubectl get ValidatingWebhookConfiguration +$ kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations +$ kubectl get validatingwebhookconfiguration -o yaml +$ kubectl get mutatingwebhookconfiguration -o yaml +$ kubectl get svc,deploy,pod -A | grep -i webhook ``` +Fields to inspect: + +- `rules`: Check covered API groups, versions, resources, subresources, operations, and scope. +- `namespaceSelector` / `objectSelector`: Look for namespaces or labels that exclude resources from policy. +- `matchConditions`: CEL expressions can intentionally or accidentally skip requests. +- `failurePolicy`: `Ignore` lets requests continue if the webhook fails; `Fail` blocks them. +- `sideEffects`: Webhooks with side effects may not support dry-run testing. +- `timeoutSeconds`: Very short timeouts combined with `Ignore` can become fail-open behavior. +- `clientConfig`: Review whether the webhook points to an in-cluster Service or external URL, and inspect the backing workload and service account. +- `reinvocationPolicy`: Mutating webhooks may be reinvoked when later mutation changes the object. + ### Abusing Kyverno and Gatekeeper VWC As we can see all operators installed have at least one ValidatingWebHookConfiguration(VWC). @@ -87,12 +117,23 @@ namespaceSelector: - MYAPP ``` -Here, `kubernetes.io/metadata.name` label refers to the namespace name. Namespaces with names in the `values` list will be excluded from the policy : +Here, `kubernetes.io/metadata.name` refers to the namespace name label. Namespaces with names in the `values` list will be excluded from the policy: Check namespaces existence. Sometimes, due to automation or misconfiguration, some namespaces might have not been created. If you have permission to create namespace, you could create a namespace with a name in the `values` list and policies won't apply your new namespace. The goal of this attack is to exploit **misconfiguration** inside VWC in order to bypass operators restrictions and then elevate your privileges with other techniques +Other common bypass or abuse patterns: + +- An `objectSelector` that allows users to add an opt-out label to their own objects. +- `failurePolicy: Ignore` on security-critical validation, especially when the webhook Service has no endpoints or unreliable networking. +- Policy engine exceptions for users, groups, service accounts, namespaces, or roles that are broader than intended. +- Missing coverage for workload controller templates, `pods/ephemeralcontainers`, `pods/exec`, custom resources, or update operations. +- Write access to `validatingwebhookconfigurations`, `mutatingwebhookconfigurations`, Gatekeeper constraints, Kyverno policies, or exception resources. +- A malicious mutating webhook that injects containers, changes images, mounts secrets, adds tolerations, or changes service account selection before validation. + +Remember that admission only protects requests that pass through the API server admission chain. Static Pods, node-local runtime socket access, direct kubelet abuse, and direct etcd access are different trust paths and need separate hardening and monitoring. + {{#ref}} abusing-roles-clusterroles-in-kubernetes/ {{#endref}} @@ -102,6 +143,8 @@ abusing-roles-clusterroles-in-kubernetes/ - [https://github.com/open-policy-agent/gatekeeper](https://github.com/open-policy-agent/gatekeeper) - [https://kyverno.io/](https://kyverno.io/) - [https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) +- [https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/](https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/) +- [https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) diff --git a/src/pentesting-cloud/kubernetes-security/pentesting-kubernetes-services/README.md b/src/pentesting-cloud/kubernetes-security/pentesting-kubernetes-services/README.md index 4c4ebf3d2..1eed12c92 100644 --- a/src/pentesting-cloud/kubernetes-security/pentesting-kubernetes-services/README.md +++ b/src/pentesting-cloud/kubernetes-security/pentesting-kubernetes-services/README.md @@ -8,6 +8,16 @@ Kubernetes uses several **specific network services** that you might find **expo One way could be searching for `Identity LIKE "k8s.%.com"` in [crt.sh](https://crt.sh) to find subdomains related to kubernetes. Another way might be to search `"k8s.%.com"` in github and search for **YAML files** containing the string. +Useful external recon signals to correlate before scanning: + +- DNS and certificate transparency names containing `k8s`, `kube`, `api`, `apiserver`, `eks`, `gke`, `aks`, `cluster`, `ingress`, `argocd`, `grafana`, `prometheus`, `harbor`, `registry`, `dashboard`, `dev`, `stage`, or region names. +- Cloud load balancer names, CNAMEs, tags, and provider hostnames that can link an exposed application or platform UI back to a cluster. +- Public repositories, CI logs, Helm values, Terraform state, rendered manifests, container images, and documentation leaking kubeconfigs, API server URLs, namespaces, service accounts, `type: LoadBalancer`, `type: NodePort`, Ingress hosts, Gateway listeners, or dashboard settings. +- Managed Kubernetes inventory, when cloud credentials are in scope: EKS endpoint public/private access and public CIDRs, GKE public/private control-plane settings and authorized networks, and AKS private cluster/API server authorized IP settings. +- Exposed platform tools around the cluster such as Argo CD, Prometheus, Grafana, Harbor, registries, CI/CD dashboards, service mesh dashboards, and ingress-controller admin or metrics endpoints. + +Treat these as attribution and prioritization clues. A public Ingress application is normal in many clusters, while exposed kubelet, etcd, dashboard, CI/CD deploy control, or leaked kubeconfig material should be prioritized much higher. + ## How Kubernetes Exposes Services It might be useful for you to understand how Kubernetes can **expose services publicly** in order to find them: @@ -126,6 +136,31 @@ When a port is exposed in all the nodes via a **NodePort**, the same port is ope sudo nmap -sS -p 30000-32767 ``` +### Service mesh and proxy surfaces + +Clusters using **Istio, Linkerd, Cilium service mesh, or Envoy-based gateways** add another service layer to enumerate. A mesh can provide mTLS, workload identity, L7 routing, authorization policy, telemetry, and gateway/egress controls, but it only protects traffic that is actually enrolled and intercepted by the mesh. + +Useful checks from Kubernetes access: + +```bash +kubectl get ns --show-labels | egrep 'istio|linkerd|mesh|cilium' +kubectl get crd | egrep 'istio.io|linkerd.io|gateway.networking.k8s.io|cilium.io' +kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration | egrep 'istio|linkerd|cilium' +kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,CONTAINERS:.spec.containers[*].name' +kubectl get svc -A | egrep 'istio|envoy|linkerd|kiali|jaeger|prometheus|grafana|zipkin|hubble' +``` + +Review: + +- Namespaces or workloads that opted out of injection, still run without a proxy, or were created before injection was enabled. +- mTLS mode. Permissive migration modes may still accept plaintext from unmeshed sources. +- Istio `PeerAuthentication`, `AuthorizationPolicy`, `RequestAuthentication`, gateways, waypoints, and egress resources. +- Linkerd policy resources, identity, Server/authorization objects, and exposed `linkerd-viz`, tap, or metrics surfaces. +- Cilium service mesh and Gateway API resources, Hubble visibility, Cilium policies, and Envoy integration points. +- Envoy admin, config dump, stats, metrics, tracing, dashboard, and debug endpoints. These can leak routes, upstreams, certificates, identity, and traffic state if exposed too broadly. + +Do not treat service mesh as a replacement for Kubernetes RBAC or NetworkPolicies. A mesh policy can block an HTTP request while an unmeshed Pod, skipped port, direct Pod IP path, gateway, egress proxy, or missing NetworkPolicy still leaves a practical route. + ## Vulnerable Misconfigurations ### Kube-apiserver Anonymous Access @@ -212,5 +247,3 @@ https://labs.f-secure.com/blog/attacking-kubernetes-through-kubelet {{#endref}} {{#include ../../../banners/hacktricks-training.md}} - - From c4d28b9207cc53b28c5aa3d2a7ac4fa810225105 Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Sat, 4 Jul 2026 13:35:45 +0200 Subject: [PATCH 3/8] Support absolute sponsor image URLs --- theme/sponsor.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/theme/sponsor.js b/theme/sponsor.js index 337740dab..6e54e2614 100644 --- a/theme/sponsor.js +++ b/theme/sponsor.js @@ -107,8 +107,30 @@ return "" } + function resolveSponsorImageUrl(imageUrl) { + var value = String(imageUrl || "").trim() + + if (!value) { + return "" + } + + if (/^https:\/\//i.test(value)) { + return value + } + + if (/^\/\//.test(value)) { + return "https:" + value + } + + if (value.charAt(0) === "/") { + return new URL(value, window.location.origin).href + } + + return new URL(value, document.baseURI || window.location.href).href + } + function setLegacySponsorContent(sponsor, container, nodes) { - nodes.img.src = sponsor.image_url + nodes.img.src = resolveSponsorImageUrl(sponsor.image_url) nodes.img.alt = sponsor.name nodes.title.textContent = sponsor.name nodes.description.innerHTML = sponsor.description From 339eaf5abadcb3a72b4e5b9d23eeb493413a84d6 Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Sat, 4 Jul 2026 13:36:42 +0200 Subject: [PATCH 4/8] changes --- .../exposing-services-in-kubernetes.md | 17 +- .../kubernetes-security/kubernetes-basics.md | 12 +- .../kubernetes-securitycontext-s.md | 8 +- theme/css/chrome.css | 232 +++++++++++++----- theme/index.hbs | 152 +++++++++--- 5 files changed, 320 insertions(+), 101 deletions(-) diff --git a/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md b/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md index da2c54fe3..5395873b6 100644 --- a/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md +++ b/src/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes.md @@ -95,6 +95,19 @@ spec: If you **don't specify** the **nodePort** in the yaml (it's the port that will be opened) a port in the **range 30000–32767 will be used**. +When reviewing NodePort or LoadBalancer Services, also inspect traffic-policy fields because they change which nodes and backends are useful from a given source: + +```bash +kubectl get services --all-namespaces \ + -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,ETP:.spec.externalTrafficPolicy,ITP:.spec.internalTrafficPolicy,AFFINITY:.spec.sessionAffinity,DIST:.spec.trafficDistribution,NODEPORTS:.spec.ports[*].nodePort' +``` + +- `externalTrafficPolicy: Local` preserves the original client source IP for NodePort/LoadBalancer traffic and avoids forwarding to endpoints on other nodes. A node without a local ready endpoint may drop the traffic even if the Service has endpoints elsewhere. +- `externalTrafficPolicy: Cluster` is the default and can forward through any node, but backend logs may see node IPs instead of the real external client IP. +- `internalTrafficPolicy: Local` limits in-cluster Service traffic to endpoints local to the source node. This is locality routing, not an authorization boundary. +- `sessionAffinity: ClientIP` can make repeated tests from one client hit the same backend, hiding other ready endpoints during manual checks. +- `trafficDistribution` and EndpointSlice topology hints can prefer same-zone or same-node endpoints on newer clusters; treat them as routing preferences rather than hard security policy. + ### LoadBalancer Exposes the Service externally **using a cloud provider's load balancer**. On GKE, this will spin up a [Network Load Balancer](https://cloud.google.com/compute/docs/load-balancing/network/) that will give you a single IP address that will forward all traffic to your service. In AWS it will launch a Load Balancer. @@ -254,8 +267,10 @@ Check Gateway listeners, allowed route namespaces, Route `parentRefs`, hostnames - [https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0) - [https://kubernetes.io/docs/concepts/services-networking/service/](https://kubernetes.io/docs/concepts/services-networking/service/) - [https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/](https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/) +- [https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/](https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/) +- [https://kubernetes.io/docs/tutorials/services/source-ip/](https://kubernetes.io/docs/tutorials/services/source-ip/) +- [https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/](https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/) - [https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/) - [https://gateway-api.sigs.k8s.io/](https://gateway-api.sigs.k8s.io/) {{#include ../../banners/hacktricks-training.md}} - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-basics.md b/src/pentesting-cloud/kubernetes-security/kubernetes-basics.md index 50fe24675..e5412f576 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-basics.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-basics.md @@ -39,6 +39,8 @@ Note that as the might be several nodes (running several pods), there might also When a pod creates data that shouldn't be lost when the pod disappear it should be stored in a physical volume. **Kubernetes allow to attach a volume to a pod to persist the data**. The volume can be in the local machine or in a **remote storage**. If you are running pods in different physical nodes you should use a remote storage so all the pods can access it. +Kubernetes also supports **image volumes** in recent versions. An `image` volume mounts an OCI image or artifact as a **read-only** filesystem source inside the Pod, using fields such as `volumes[].image.reference` and `volumes[].image.pullPolicy`. The kubelet pulls the artifact with the same credential sources used for container images, including node credentials, Pod `imagePullSecrets`, and ServiceAccount `imagePullSecrets`. During a security review, treat image volumes as runtime inputs and supply-chain dependencies: check whether the reference is pinned by digest, which registry credentials can fetch it, where it is mounted, and whether `subPath` limits the visible directory. + **Other configurations:** - **ConfigMap**: You can configure **URLs** to access services. The pod will obtain data from here to know how to communicate with the rest of the services (pods). Note that this is not the recommended place to save credentials! @@ -564,6 +566,12 @@ https://sickrov.github.io/ https://www.youtube.com/watch?v=X48VuDVv0do {{#endref}} +{{#ref}} +https://kubernetes.io/docs/concepts/storage/volumes/#image +{{#endref}} + +{{#ref}} +https://kubernetes.io/docs/tasks/configure-pod-container/image-volumes/ +{{#endref}} + {{#include ../../banners/hacktricks-training.md}} - - diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md b/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md index 33203cf40..30860657f 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-hardening/kubernetes-securitycontext-s.md @@ -67,6 +67,9 @@ High-risk combinations to prioritize: - `seccompProfile: Unconfined`, `procMount: Unmasked`, or missing runtime profiles on sensitive workloads. - Writable root filesystems or broad writable volume mounts in workloads that process untrusted input. - Missing CPU, memory, or ephemeral-storage requests and limits in multi-tenant namespaces. +- Missing or unrealistic pod-level `spec.resources` budgets, and principals with `patch` or `update` on the Pod `resize` subresource, because supported clusters can change running CPU and memory desired state without recreating the Pod. + +Resource controls are not part of `securityContext`, but review them in the same workload pass because they define the availability boundary. Modern Kubernetes can define CPU, memory, and hugepage budgets at Pod level under `spec.resources` in addition to container-level `resources`. A Pod with sidecars may be bounded by an aggregate Pod envelope even when one container has no individual limits, while local ephemeral storage still needs separate `ephemeral-storage` limits, `emptyDir.sizeLimit`, LimitRanges, and ResourceQuotas. Also compare desired resources in the Pod spec with `status.containerStatuses[].resources` after an in-place resize request; a failed or pending resize can leave the requested value in `spec` while the kubelet keeps the previous runtime allocation and reports a `PodResizePending` condition. For most application workloads, a good baseline is to run as a non-root UID, set `runAsNonRoot: true`, set `allowPrivilegeEscalation: false`, drop all capabilities and add back only the minimum required ones, use `seccompProfile: RuntimeDefault`, prefer a read-only root filesystem, and avoid host namespaces, hostPath mounts, and privileged mode. @@ -77,10 +80,11 @@ At cluster level, use [Pod Security Admission](https://kubernetes.io/docs/concep - [https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritycontext-v1-core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritycontext-v1-core) - [https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core) - [https://kubernetes.io/docs/tasks/configure-pod-container/security-context/](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) +- [https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) - [https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/](https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/) - [https://kubernetes.io/docs/concepts/security/pod-security-standards/](https://kubernetes.io/docs/concepts/security/pod-security-standards/) - [https://kubernetes.io/docs/concepts/security/pod-security-admission/](https://kubernetes.io/docs/concepts/security/pod-security-admission/) +- [https://kubernetes.io/docs/tasks/configure-pod-container/assign-pod-level-resources/](https://kubernetes.io/docs/tasks/configure-pod-container/assign-pod-level-resources/) +- [https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/) {{#include ../../../banners/hacktricks-training.md}} - - diff --git a/theme/css/chrome.css b/theme/css/chrome.css index f546d1b28..30c444b2d 100644 --- a/theme/css/chrome.css +++ b/theme/css/chrome.css @@ -155,18 +155,80 @@ body.sidebar-visible #menu-bar { margin: 0 15px; display: flex; flex-wrap: wrap; - column-gap: 2.6rem; + column-gap: 1.25rem; align-items: center; } .right-buttons a { text-decoration: none; } +.menu-socials { + position: relative; + display: inline-flex; + align-items: center; +} +.menu-socials-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + border: 0; + padding: 0; + font: inherit; + background: transparent; + cursor: pointer; +} +.menu-socials-caret { + font-size: 14px; + line-height: 1; + opacity: 0.9; +} +.menu-socials-popup { + position: absolute; + right: 0; + top: 30px; + z-index: 105; + border-radius: 5px; + font-size: 14px; + color: var(--fg); + background: var(--bg); + border: 1px solid var(--table-border-color); + margin: 0; + padding: 0; + display: none; + flex-direction: column; + min-width: 140px; + overflow: hidden; +} +.menu-socials:hover .menu-socials-popup, +.menu-socials:focus-within .menu-socials-popup { + display: flex; +} +.menu-socials-popup .menu-bar-link { + display: inline-flex; + align-items: center; + gap: 8px; + border: 0; + margin: 0; + padding: 8px 20px; + line-height: 25px; + white-space: nowrap; + text-align: start; + color: inherit; + background: inherit; + font-size: inherit; +} +.menu-socials-popup .menu-bar-link i { + width: 16px; + text-align: center; +} +.menu-socials-popup .menu-bar-link:hover { + background-color: var(--theme-hover); +} @media only screen and (min-width:800px) { #menubar-collapse { display: flex !important; flex-wrap: wrap; - column-gap: 2.6rem; + column-gap: 1.25rem; align-items: center; } @@ -289,6 +351,22 @@ body.sidebar-visible #menu-bar { overflow: auto; background: transparent; } +#menubar-collapse-popup.show-socials #menubar-socials-popup { + display: flex; +} +#menubar-socials-popup { + display: none; + flex-direction: column; + border-top: 1px solid var(--table-border-color); + background: transparent; +} +#menubar-collapse-popup #menubar-socials-popup .menu-bar-link { + padding-left: 34px; +} +#menubar-socials-popup .menu-bar-link i { + width: 16px; + text-align: center; +} #menubar-collapse-popup .menu-bar-link { border: 0; margin: 0; @@ -796,7 +874,7 @@ ul#searchresults li a span.teaser em { } } -@media only screen and (min-width:550px) { +@media only screen and (min-width:800px) { .sidebar-float-toggle { position: fixed; top: calc(var(--menu-bar-height) + 90px); @@ -841,15 +919,7 @@ ul#searchresults li a span.teaser em { margin-right: auto; } -#sidebar-toggle-anchor:not(:checked) ~ .page-wrapper .page #container .sidetoc { - position: fixed; - right: max(var(--page-padding), calc((100vw - var(--container-max-width)) / 2)); - top: 0; - width: 250px; - margin-top: 25px; -} - -@media only screen and (max-width:549px) { +@media only screen and (max-width:799px) { .sidebar-float-toggle { position: fixed; top: calc(var(--menu-bar-height) + 10px); @@ -877,14 +947,14 @@ ul#searchresults li a span.teaser em { } } -@media only screen and (max-width:549px) { +@media only screen and (max-width:799px) { #sidebar-toggle-anchor:checked ~ .page-wrapper .page #container .sidebar-float-toggle { left: calc(100vw - 34px); } } /* Sidebar */ -@media only screen and (min-width:550px) { +@media only screen and (min-width:800px) { .sidebar { position: sticky; top: 4rem; @@ -903,7 +973,7 @@ ul#searchresults li a span.teaser em { display: none !important; } } -@media only screen and (max-width:549px) { +@media only screen and (max-width:799px) { .sidebar { position: fixed; bottom: 0; @@ -919,7 +989,7 @@ ul#searchresults li a span.teaser em { z-index: 105; } } -@media only screen and (max-width:549px) { +@media only screen and (max-width:799px) { #sidebar { display: none; } @@ -935,11 +1005,26 @@ ul#searchresults li a span.teaser em { #sidebar-toggle-anchor:checked ~ .page-wrapper .page #container #sidebar { display: block !important; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .page #container #sidebar .sidebar-scrollbox { + bottom: 58px; + } + #sidebar-toggle-anchor:checked ~ .page-wrapper .page #container #sidebar .sidebar-socials { + display: flex; + } #sidebar-toggle-anchor:checked ~ .page-wrapper .page #container #content { display: none !important; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .page > .footer { + display: none !important; + } + #sidebar-toggle-anchor:not(:checked) ~ .page-wrapper .page #container #content { + display: block !important; + } + #sidebar-toggle-anchor:not(:checked) ~ .page-wrapper .page > .footer { + display: flex !important; + } } -@media only screen and (max-width:549px) { +@media only screen and (max-width:799px) { #sidebar { display: none !important; } @@ -990,6 +1075,11 @@ html:not(.sidebar-resizing) .sidebar { padding: 10px 10px; background-color: transparent; } +@media only screen and (min-width:800px) { + .sidebar .sidebar-scrollbox { + bottom: 58px; + } +} .sidebar .sidebar-resize-handle { position: absolute; cursor: col-resize; @@ -1187,9 +1277,75 @@ html:not(.sidebar-resizing) .sidebar { height: var(--footer-height); border-top: 1px solid var(--table-border-color); margin-top: 1rem; - align-content: center; + display: flex; + align-items: center; + justify-content: center; z-index: 106; } +.footer-socials { + display: flex; + align-items: center; + justify-content: center; + gap: 14px; +} +.footer-socials a, +.footer-socials a:visited { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 9999px; + color: var(--icons); + text-decoration: none; + border: 1px solid var(--table-border-color); + background: color-mix(in srgb, var(--bg) 88%, transparent); +} +.footer-socials a:hover { + color: var(--icons-hover); + background: var(--theme-hover); +} +.footer-socials i { + font-size: 17px; +} +.sidebar-socials { + display: none; +} +@media only screen and (max-width:799px) { + .sidebar-socials { + position: absolute; + left: 10px; + right: 10px; + bottom: 12px; + padding-top: 10px; + border-top: 1px solid var(--table-border-color); + background: var(--bg); + z-index: 2; + } +} +@media only screen and (min-width:800px) { + .page > .footer { + display: none; + position: fixed; + left: max(0px, calc((100vw - var(--container-max-width)) / 2)); + bottom: 0; + width: var(--sidebar-width); + height: auto; + margin: 0; + padding: 10px; + box-sizing: border-box; + border-top: 1px solid var(--table-border-color); + background: var(--bg); + z-index: 107; + } + #sidebar-toggle-anchor:checked ~ .page-wrapper .page > .footer { + display: flex; + } + [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper .page > .footer { + left: auto; + right: max(0px, calc((100vw - var(--container-max-width)) / 2)); + } +} .theme-wrapper--menubar { position: relative; display: flex; @@ -1230,46 +1386,6 @@ html:not(.sidebar-resizing) .sidebar { background-color: var(--theme-hover); } -.theme-wrapper--menubar .theme-orb { - position: absolute; - right: 6px; - top: 50%; - width: 8px; - height: 8px; - border-radius: 9999px; - background: radial-gradient(circle at 30% 30%, #ffffff, color-mix(in srgb, var(--links) 70%, transparent)); - opacity: 0.7; - transform: translateY(10px); - filter: blur(0.2px); - pointer-events: none; - animation: theme-orb-rise 4.2s ease-in-out infinite; -} - -.theme-wrapper--menubar .theme-side-icon { - font-size: 16px; - color: var(--icons); - opacity: 0.9; - line-height: 1; -} - -@keyframes theme-orb-rise { - 0% { - transform: translateY(10px); - opacity: 0; - } - 35% { - opacity: 0.6; - } - 70% { - transform: translateY(-22px); - opacity: 0.4; - } - 100% { - transform: translateY(-30px); - opacity: 0; - } -} - /* Details/Summary */ summary { diff --git a/theme/index.hbs b/theme/index.hbs index 2c1687ab1..cf44ba12f 100644 --- a/theme/index.hbs +++ b/theme/index.hbs @@ -100,7 +100,7 @@ var sidebar = null; var sidebar_toggle = document.getElementById("sidebar-toggle-anchor"); var body = document.querySelector("body"); - if (document.body.clientWidth >= 550) { + if (document.body.clientWidth >= 800) { try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } sidebar = sidebar || 'visible'; } else { @@ -160,18 +160,24 @@ HT Tools + + HT Friends + - Request Page update - - - Sponsor - - - Linkedin - - - X + Request update +
-
- -
{{#if print_enable}} @@ -287,6 +298,23 @@ + From 58413bbe99b6e6b2fef856ff03828b0b82666885 Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Sat, 4 Jul 2026 20:53:44 +0200 Subject: [PATCH 5/8] Update sponsor card styling --- theme/index.hbs | 57 +++-- theme/pagetoc.css | 565 +++++++++++++++++++++++++++++++++++++++------- theme/sponsor.js | 58 ++++- 3 files changed, 576 insertions(+), 104 deletions(-) diff --git a/theme/index.hbs b/theme/index.hbs index cf44ba12f..e7b9a4df6 100644 --- a/theme/index.hbs +++ b/theme/index.hbs @@ -33,6 +33,9 @@ + + + {{#if copy_fonts}} {{/if}} @@ -330,15 +333,35 @@ + + + + + Sponsored + + + + + + + + + {{{ content }}}
- - -
-
-
-
-
+
+ + + + Sponsored + + + + + + + +