Collecting and Crafting User Information from LinkedIn

Justin Angel //

Penetration testing and red team engagements often require operators to collect user information from various sources that can then be translated into inputs to support social engineering and password attacks. LinkedIn is obviously a prime source for this type of information since users can associate themselves with a particular company. Assuming we have identified the companies our target organization owns during earlier stages of reconnaissance, we may be able to enumerate employee information by simply finding the company page within LinkedIn and browsing the “people” section.

However, for the following (and more) reasons (or magic), this isn’t very efficient or straightforward:

  • So much copypasta, unless you’re being smart and using LinkedInt by @vysec — the key inspiration for this tool
  • LinkedIn restricts the information a given account can access on a target profile based on a constellation of variables, such as the industry it’s currently affiliated with and if it shares any overlapping connections
  • Depending on the effectiveness of the target company’s security awareness training, personnel may have their profiles configured to hide personal information before first establishing a connection with your account

The remainder of this brief post will discuss my strategy for gaining as much profile content as possible and a quick example of how to quickly manipulate the output from Peasant into actionable values.

I use the following strategy to maximize the number of accessible profiles associated with a company:

  1. I configure my profile such that it appears to hold a position with the target company. This often opens up a few profiles I can connect with.
  2. Harvest information from accessible profiles.
  3. If this yields insufficient information (mileage varies), I’ll duplicate the content from a high-impact profile to my own.
    • The goal here is to make your profile appealing to accounts you’re about to send connection requests to.
    • The greater number of connections your account shares with a company, the greater number of profiles it can access.
    • Recruiter profiles specializing in the same industry as the target organization is a good choice.
    • Blocking the account you just spoofed is a good idea otherwise things might get weird.
  4. For profiles I can connect with, send a connection request.
    • Tip: It’s good to exercise some discretion here. Sending a connection request to the security team isn’t a great idea… probably.
  5. Wait for one or more requests to be accepted.
  6. For every couple of connection requests accepted, repeat steps 2 and 5 until the desired number of profiles are collected.

This is a laborious and time-consuming process when performed manually, so I developed a utility (Peasant) to automate many of these steps. It provides three basic modes of operation:

  1. harvest_contacts – For each given company identifier, harvests contact information from accessible LinkedIn profiles.
  2. add_contacts – Forge connection requests for target user profiles.
  3. spoof_contact – Spoof an accessible LinkedIn profile, i.e. copy content from the target LinkedIn profile to your own.

Warning: The LinkedIn API is quite complicated and I have not researched the calls at great length, only enough to get this code working. Depending on the inputs, there may be times when Peasant fails — particularly when spoofing profiles.

Try to clear all content of your current profile before spoofing content from the foreign profile if this occurs.

You should also know that your profile may be flagged for nasty behavior. Kudos to the LinkedIn team for taking the time and effort to implement detection mechanisms.

Restricted Account Alert

Contact Harvesting

If you’ve ever visited the People section of a company profile, you may have noticed that scrolling to the bottom of the list results in the page dynamically adding additional profiles. The web interface is using JavaScript to make API calls and add new HTML elements for each result. Peasant capitalizes on these API calls to extract profile content directly from the API.

  • Notes:
    • LinkedIn often alerts users when their profile has been visited. This shouldn’t occur when harvesting profiles using Peasant since they’re not directly accessed.
    • LinkedIn allows access only to 1,000 search results at a time, however, different results may be returned each time a query is submitted – so running Peasant multiple times is recommended.
    • Use the ‘-ac’ flag to tell Peasant to generate connection requests when a profile is discovered (if desired), removing the need to run it again (this is a bit reckless).
    • Warning: Unless you have a premium account, there’s a strict API limit enforced that will not be refreshed until the next month.
    • Warning: Though the output from LinkedIn is structured well, you’ll need to browse through and massage the output from Peasant since we’re still dealing with user-generated content. All content will be dumped without discretion, emojis and all.

Adding Contacts

Information gathered when harvesting profiles includes an “entity URN” field that is used to identify a specific LinkedIn profile. We can take this value and craft an API call that will send a connection request to the associated URN. Peasant can accept CSV files generated by the harvest_contacts mode and send a connection request to each record. The request message can be customized as well.

  • Notes:
    • You must have enough access to a target profile in order to send a connection request, otherwise, the request will be ignored.
    • LinkedIn enforces a loose API limit on sending connection requests. You can usually run it hourly in windows.

Profile Spoofing

Peasant can “spoof” content from a foreign profile and update your profile with that content, including images. This is particularly useful in social engineering situations when you’d like to impersonate an entity that works within a target organization.

  • Notes:
    • You must have enough access with a target profile to view its contents.
    • This will likely trigger a “view alert” on the foreign profile (unconfirmed).
    • The API calls made to support this are somewhat complex and could introduce imperfections to your profile. Take the time to review it for accuracy!
    • Should Peasant choke while spoofing a profile, clear your current profile of all content and try again.


Let’s close out with a quick example focusing on Microsoft’s company profile while using a new account with zero connections. First, I’ll export my credentials to an environment variable (creds) in colon-delimited format (username:password) and run the harvest command (aliased to “h” for short). We can see that a health 338 profiles are returned right off.

Note: LinkedIn recently began prompting with captchas and the like. You can work around this using the “–cookies” flag, which expects one or more file names containing an array of JSON objects representing name-to-value cookie pairs from an authenticated session, like:   [{“name”:”cookie_name_here”, ”value”:”cookie_value_here”}]. This should work around the captcha for the moment, and I’ll likely add a jitter capability in the future.

Peasant: Harvested Information from 338 Profiles

Next, I’ll spoof one of the Microsoft profiles that I can view. To protect the innocent, I’ve omitted the profile identifier and elected not to take a screen capture of the results. Running the harvest subcommand returns an additional 40 profiles this time.

Peasant: Spoofing Accessible Profile
Peasant: Harvesting an Additional Forty Profiles

Not too bad, but we can do better by getting some connection requests sent out to target profiles using the add_contacts subcommand while setting the “-if” flag to point at our output CSV file. At least one person accepted the connection request within a minute of sending these out. Harvesting after gaining these connections yielded an additional 662 profiles.

Peasant: Sending Connection Requests from CSV
LinkedIn: Two Additional Requests Accepted
Peasant: Harvesting Additional Profiles

Two requests were accepted within the hour, allowing me to capture profile information from a total of 1,842 accounts.

Peasant: Harvesting Additional Contacts (Total 1,842)

Working with the Output

The CSV output generated by Peasant contains several interesting fields we can use when selecting targets for social engineering and crafting inputs for password attacks. Here are the CSV columns for reference:

  • first_name
  • last_name
  • occupation
  • public_identifier
  • industry
  • location
  • entity_urn
  • company_name
  • company_id
  • connection_requested

Finding Interesting Roles

awk is your friend if you want to grep out security roles (occupations), which may help you avoid starting a fire by sending connection requests to individuals with a heightened level of awareness: awk -F ‘,’ ‘{print $3}’ microsoft.csv|sort -u|grep -vi security. Now you can iterate over each of these roles and use grep with the inverse flag to filter them from the CSV file. Use the reverse of this technique to identify key roles you may be interested in targeting for social engineering attacks.

Extracting Occupations Containing the String “security”

Crafting a List of Emails for Password Spray Attacks

I’m partial to another silly project of mine called Parsuite and the templatizer module for crafting user lists and the like, which accepts and mangles CSV input to return new CSV output containing crafted values. Support for random value generation and basic “encoding” of outputs is available as well.

We’d use the following command to generate a list of email addresses in {first_letter_first_name}{last_name} format. If the template structure looks confusing, clone a copy of Parsuite and run the help command for the templatizer module to get more information.

parsuite templatizer -tts \
'<<<:first_name[1]:lowercase_encode>>><<<:last_name:lowercase_encode>>>' \
-csv microsoft.csv

Parsuite Command to Craft Email Addresses

Parsuite: Crafting Email Addresses from Peasant Output

Crafting Email Addresses and Content for Phishing Campaigns

The templatizer module can accept files containing text templates as well, so you could also generate emails containing unique links and identifiers to support a phishing campaign.

Hello <<<:first_name:>>>,

You should unqestionably click this link:<<<:RAND:>>>

Text Email Template

parsuite templatizer --csv-file microsoft.csv -tts \
'<<<:first_name[1]:lowercase_encode>>><<<:last_name:lowercase_encode>>>'  \

Final Parsuite Command

Parsuite: Generating Emails with Unique Links

Now you can pass this output to any tool that’ll accept CSV files as input. I’ve recently used SendGrid as a mail delivery service, which is supported by another tool I’ve thrown together and can run right with this file format.

Defender Recommendations

First and foremost, be sure to incorporate content into your security awareness training communicating that threat actors use social media as a phishing message delivery platform and how users should exercise good judgment when interacting with Interplebeians. It’s becoming increasingly difficult to land phishing emails in target inboxes due to technical controls but getting a direct line of messaging through social media is always going to be easy since our brains are primed to drop a dopamine fog-bomb each time a like or friend request is received.

Second, I recommend creating several LinkedIn accounts and joining them to your Company profile. You can then monitor them on occasion to see if they each received invitation requests from the same account in a short period of time, an indicator that you’re being targeted for reconnaissance. Do some due diligence to determine if the activity is malicious and consider reporting the account to LinkedIn. I realize this isn’t the most practical recommendation but company admins have minimal control over who can join a company profile.

Bonus Vulnerability Discovery

A previously unknown access control flaw was identified in LinkedIn while developing the image spoofing capability of Peasant. I initially tried to take the URN identifier of the profile/background image configured in a foreign profile and copied it to my evil profile. The advantage of this approach is that it eliminated several API calls and dealing with the binary content of pictures. The unintended consequence was that deleting the pictures from my profile also deleted the image and URN itself, which resulted in the picture no longer being available for the foreign profile – thus causing the foreign profile to display the default profile picture.

I worked directly with LinkedIn’s security team to remediate the vulnerability over the Thanksgiving holiday. Kudos to them for the prompt response.

Ready to learn more?

Level up your skills with affordable classes from Antisyphon!

Pay-What-You-Can Training

Available live/virtual and on-demand