Limiet
Er is een beperking op LinkedIn — . Hoogstwaarschijnlijk ben je, net als ik tot voor kort, nooit eerder met deze beperking geconfronteerd of ervan gehoord.

De limiet houdt in dat als je te vaak naar mensen zoekt buiten jouw contacten (er zijn geen exacte metrics, het algoritme beslist op basis van jouw acties — hoe vaak en hoeveel je hebt gezocht, mensen hebt toegevoegd), de zoekresultaten beperkt worden tot drie profielen, in plaats van 1000 (standaard 100 pagina's, met 10 profielen per pagina). De limiet wordt elke maand opnieuw ingesteld. Natuurlijk, .
Maar niet zo lang geleden, voor een klein project, begon ik veel met de zoekfunctie van LinkedIn te experimenteren en kreeg ik plots deze beperking. Natuurlijk vond ik dat niet leuk, want ik gebruikte het niet voor commerciële doeleinden, dus mijn eerste gedachten waren om de beperking te bestuderen en te proberen deze te omzeilen.
[Belangrijke opmerking — de materialen in dit artikel worden uitsluitend voor informatieve en educatieve doeleinden gepresenteerd. De auteur moedigt het niet aan om deze voor commerciële doeleinden te gebruiken.]
We bestuderen het probleem
We hebben: in plaats van tien profielen met paginering, geeft de zoekopdracht slechts drie profielen terug, waarna er een blok verschijnt met de 'aanbeveling' voor een premiumaccount en daaronder verschijnen vervaagde en niet-klikbare profielen.
Direct reikt mijn hand naar de ontwikkelaarsconsole om deze verborgen profielen te bekijken — misschien kunnen we bepaalde stijlen verwijderen die de vervaging veroorzaken, of informatie uit het blok in de markup extraheren. Maar, zoals verwacht, zijn deze profielen slechts en bevatten geen informatie.

Goed, laten we nu het tabblad Netwerk bekijken en controleren of de alternatieve zoekresultaten daadwerkelijk worden geactiveerd, waarbij alleen drie profielen worden teruggegeven. We vinden de relevante aanvraag naar '/api/search/blended' en bekijken het antwoord.

Profielen komen in een array 'included', maar er zijn in totaal 15 entiteiten. In dit geval zijn de eerste drie hiervan objecten met aanvullende informatie, waarbij elk object informatie over een specifiek profiel bevat (bijvoorbeeld of het profiel een premiumaccount is).

De volgende 12 zijn echte profielen - zoekresultaten, waarvan er slechts drie worden getoond. Zoals je kunt raden, worden alleen die getoond waar aanvullende informatie over beschikbaar is (de eerste drie objecten). Bijvoorbeeld, als we een antwoord van een profiel zonder limiet nemen, worden er 28 entiteiten geretourneerd - 10 objecten met aanvullende informatie en 18 profielen.
Antwoord voor profiel zonder limiet

Waarom er meer dan 10 profielen binnenkomen, terwijl er precies 10 worden gevraagd, en ze niet betrokken zijn bij de weergave, zelfs op de volgende pagina zullen ze er niet zijn - ik weet het nog niet. Als we de URL van het verzoek analyseren, kunnen we zien dat count=10 (hoeveel profielen in het antwoord moeten worden geretourneerd, maximaal 49).
Ik sta open voor opmerkingen hierover.
We experimenteren
Goed, het belangrijkste is dat we nu zeker weten - er komen meer profielen in het antwoord dan we te zien krijgen. Dat betekent dat we meer gegevens kunnen ophalen, ondanks de limiet. Laten we proberen de API zelf aan teroepen, direct vanuit de console, met behulp van fetch.

Verwacht, we krijgen een foutmelding, 403. Dit heeft te maken met beveiliging, hier sturen we geen CSRF-token mee (. In het kort - aan elk verzoek wordt een uniek token toegevoegd, dat op de server op echtheid wordt gecontroleerd).

Het kan worden gekopieerd van elk ander succesvol verzoek of uit cookies, waar het wordt opgeslagen in het veld ‘JSESSIONID’.
Waar het token te vindenKoptekst van een ander verzoek:

Of vanuit de cookies, rechtstreeks via de console:

Proberen we het nog een keer, deze keer geven we in fetch instellingen door, waarin we ons csrf-token in de header specificeren.

Succes, we ontvangen alle 10 profielen. :tada:
Door de verschil in headers verschilt de structuur van het antwoord iets van wat in het originele verzoek komt. We kunnen een vergelijkbare structuur krijgen door ‘Accept: ‘application/vnd.linkedin.normalized+json+2.1’ toe te voegen aan ons object, naast het csrf-token.
Voorbeeld van een antwoord met de toegevoegde header
Wat nu?
Daarna kunnen we de parameter `start` bewerken (handmatig of automatisch), die aangeeft vanaf welke index we 10 profielen krijgen (standaard = 0) uit het totale zoekresultaat. Met andere woorden, door het met 10 te incremente voor elke aanvraag, krijgen we een normale paginagedeelte, met 10 profielen per keer.
In deze fase had ik genoeg gegevens en vrijheden om verder te werken aan mijn pet-project. Maar het zou zonde zijn geweest om deze gegevens niet rechtstreeks ter plaatse te visualiseren, nu ik ze toch had. We zullen niet ingaan op Ember, dat aan de voorkant wordt gebruikt. Op de website was jQuery geïnstalleerd, en door mijn basiskennis van de syntaxis op te frissen, kun je in een paar minuten het volgende creëren.
jQuery-code
/* рендер блока, принимаем данные профиля и вставляем блок в список профилей используя эти данные */
const createProfileBlock = ({ headline, publicIdentifier, subline, title }) => {
$('.search-results__list').append(
`<li class="search-result search-result__occluded-item ember-view">
<div class="search-entity search-result search-result--person search-result--occlusion-enabled ember-view">
<div class="search-result__wrapper">
<div class="search-result__image-wrapper">
<a class="search-result__result-link ember-view" href="/nl/in/${publicIdentifier}/">
<figure class="search-result__image">
<div class="ivm-image-view-model ember-view">
<img class="lazy-image ivm-view-attr__img--centered EntityPhoto-circle-4 presence-entity__image EntityPhoto-circle-4 loaded" src="http://www.userlogos.org/files/logos/give/Habrahabr3.png" />
</div>
</figure>
</a>
</div>
<div class="search-result__info pt3 pb4 ph0">
<a class="search-result__result-link ember-view" href="/nl/in/${publicIdentifier}/">
<h3 class="actor-name-with-distance search-result__title single-line-truncate ember-view">
${title.text}
</h3>
</a>
<p class="subline-level-1 t-14 t-black t-normal search-result__truncate">${headline.text}</p>
<p class="subline-level-2 t-12 t-black--light t-normal search-result__truncate">${subline.text}</p>
</div>
</div>
</div>
<li>`
);
};
// we halen de api, krijgen data en renderen profielen
const fetchProfiles = () => {
// token
const csrf = 'ajax:9082932176494192209';
// object met instellings voor het verzoek, token doorgeven
const settings = { headers: { 'csrf-token': csrf } }
// url van het verzoek, met dynamische startindex aan het einde
const url = `https://www.linkedin.com/voyager/api/search/blended?count=10&filters=List(geoRegion-jp0,network-S,resultType-PEOPLE)&origin=FACETED_SEARCH&q=all&queryContext=List(spellCorrectionEnabled-true,relatedSearchesEnabled-true)&start=${nextItemIndex}`;
/* verzoek doen, voor elk profiel in de respons renderen we de blok, en verhogen daarna de startindex met 10 */
fetch(url, settings).then(response => response.json()).then(data => {
data.elements[0].elements.forEach(createProfileBlock);
nextItemIndex += 10;
});
};
// verwijder alle profielen uit de lijst
$('.search-results__list').find('li').remove();
// voeg de knop toe om profielen te laden
$('.search-results__list').after('<button id="load-more">Meer laden</button>');
// add functionality to the button
$('#load-more').addClass('artdeco-button').on('click', fetchProfiles);
// set the default profile index for the request
window.nextItemIndex = 0;
Als je dit rechtstreeks in de console op de zoekpagina uitvoert, zal het een knop toevoegen die bij elke klik 10 nieuwe profielen laadt en deze weergeeft in een lijst. Natuurlijk moeten het token en de URL vooraf worden aangepast. Het profielblok bevat de naam, functie, locatie, een link naar het profiel en een placeholder-afbeelding.

Conclusie
Zo hebben we met minimale inspanning een kwetsbaarheid kunnen vinden en het zoeken zonder beperkingen terug weten te krijgen. Het was voldoende om de gegevens en hun pad te analyseren, en in de aanvraag zelf te kijken.
Ik kan niet zeggen dat dit een serieus probleem is voor LinkedIn, omdat het geen bedreiging vormt. Het ergste wat kan gebeuren, is een verloren winst door dergelijke 'omleggingen', waardoor men niet hoeft te betalen voor premium. Misschien is dit soort serverantwoord noodzakelijk voor de correcte werking van andere delen van de site, of is het simpelweg luiheid van de ontwikkelaars, een gebrek aan middelen die het onmogelijk maken om het goed te doen. (De beperking is in januari 2015 ingevoerd, daarvoor was er geen limiet).
P.S.
Natuurlijk is de jQuery-code een vrij primitief voorbeeld van de mogelijkheden. Op dit moment heb ik een extensie voor de browser gecreëerd voor mijn eigen behoeften. Het voegt controleknoppen toe en render volledige profielen met afbeeldingen, een uitnodigingsknop en algemene connecties. Bovendien verzamelt het dynamisch filters voor locaties, bedrijven en meer, en haalt het token uit de cookies. Dus er is geen hardcoding meer nodig. Het voegt ook extra instellingen velden toe, zoals 'hoeveel profielen in één keer aanvragen, tot 49'.

Ik ben nog steeds bezig met deze uitbreiding en van plan het openbaar te maken. Laat het me weten als je geïnteresseerd bent.
Bron: habr.com


