Penetration Testing Techniques: Conducting effective recon for enhanced phishing (Office 365 edition)

This article describes a typical penetration testing / hacking scenario : gathering as many email addresses of a target company in order to carry out more effective phishing campaigns. Note this is only one possible approach out of many…

In this particular example, we’re picking on Kaspersky Labs, for no particular reason, just as an illustrative example (besides… any press is good press, right?)

Figure out the typical target email address format

This is relatively straight forward, since most companies use the format. The first step is to figure out if this is indeed the correct format or some other format is being used. A couple of techniques to do this:

  • Use “” to make an initial search. Note the “most common pattern” heading below. 
  • See if a simple Google search similar to the following turns up anything:
(email | mailto | contact) *

It’s interesting to note that in this case, when using “” as an example, and Google returned a lot of results, but in typical pentests with smaller companies this is very rare – necessitating some more elbow grease.

Obtain an initial list of targets

If or Google return enough results, great! If not (or in addition), you can use LinkedIn for this. Search LinkedIn for the company name and make sure you have the correct company name (i.e. not leaving out any LTD or PLC parts of the name). LinkedIn will helpfully tell you how many employees it knows work for the company:

Now, you could manually use this information to build email addresses. We built a script that scrapes all the employee information it can using this method. The code is at the end of this blog post. A couple of notes:

  • The scraper is based off NodeJS and uses puppeteer, so make sure this is installed before
  • change the constants defined in the beginning of the file:
    • linkedinEmail: the email address to be used as login username to LinkedIn
    • linkedinPassword: the LinkedIn login password
    • companyName: the target company (ex “Kaspersky Labs”)
    • extraFilter: when researching smaller companies without significant social media presence, LinkedIn returns some spurious results. The “extraFilter” makes sure that whatever you specify here is present in the job position. In our example, we set this to “kaspersky”. If you don’t need this, it can be set to null
    • maxPages: the number of result pages to scrape

Figure out the email service being used

In this article, we’re focusing on Office 365, and this is very easy to detect because of the presence of “” in the target’s DNS MX records, for example:

$ dig -t MX

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> -t MX
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48125
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 65494
;			IN	MX


Send an initial phishing attack

Using the results from LinkedIn, you should be able to carry out an initial phishing attack and (hopefully) return some results.

Let’s say that that you now have a credential, but it’s very low-level (think receptionist) and the objective is to expand your recon to see if some higher-level accounts can be compromised. In this article we consider the following scenario:

  • Your target is using Office 365 as per above information
  • Your target does not have 2FA enabled

In this case, very probably it’s possible to enumerate all the email addresses your target has by leveraging Azure Active Directory. Typically this service is used to populate information such as email address books. Thankfully, Microsoft has provided us with a handy powershell module, called MSOnline, and particularly the Get-MsolUser cmdlet.

Given correct credentials (which you hopefully got from your initial phishing wave), this will give you a list of full emails. Usage is simple:

  • Import the module: Import-Module MSOnline
  • First login using the phished credentials via the Connect-MsolService cmdlet
  • Run the enumeration cmdlet Get-MsolUser -All

Update: you can also use the newer Get-AzureADUser cmdlet, details here:

This highlights the dangers of phishing. It’s very easy to escalate an attack given even limited, public information – as long as just one person falls for the attack. Given an organization of a few tens of employees and some creativity the chances of success are pretty good!

Scraper code

const linkedinEmail = '*******';
const linkedinPassword = '*******';
const companyName = 'CHANGE ME';
const extraFilter = 'change me (or set to null)';
const maxPages = 2;
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
const encodedCompanyName = encodeURIComponent(companyName)
// Login
await page.goto(';);
await page.waitForSelector('#login-email');
await page.type('#login-email', linkedinEmail);
await page.type('#login-password', linkedinPassword);
await page.waitFor(5000);
let users = [];
for (let pageNumber=1; pageNumber <= maxPages; pageNumber++){
await page.goto(`${encodedCompanyName}&page=${pageNumber}`);
await page.waitForSelector('#nav-settings__dropdown-trigger > img');
await page.evaluate(()=>{
await page.waitFor(5000);
let results = await page.evaluate( (extraFilter)=>{
let pageUsers = [];
let pagePositions = [];
let pageResults = [];
Array.from(document.getElementsByClassName('actor-name')).forEach( el => pageUsers.push(el.textContent));
Array.from(document.querySelectorAll('span[dir="ltr"]')).forEach( el => pagePositions.push(el.innerHTML));
let innerCounter = 0;
for (let counter = 0; counter < pageUsers.length; counter++){
const fullPosition = `${pagePositions[innerCounter]}, ${pagePositions[innerCounter+1]}`;
if (extraFilter && fullPosition.toLowerCase().indexOf(extraFilter.toLowerCase()) >= 0){
pageResults.push(`${pageUsers[counter]} : ${fullPosition}`);
} else if (extraFilter===null){
pageResults.push(`${pageUsers[counter]} : ${fullPosition}`);
innerCounter = innerCounter +2;
return pageResults;
}, extraFilter);
users = users.concat(results);
await browser.close();