(Ab)using Google’s Unlimited Photo Storage for Fun and Profit

'Unlimited' Storage

Google has recently made unlimited free storage available on google photos. At first glance this seems truly amazing (and ripe for abuse). The one caveat to this claim is that all photos uploaded to Google Photos are compressed with lossy compression. This means that any photos uploaded to Google Photos are automatically compressed thereby loosing some detail.

At first glance, it would appear that this would prevent people from abusing their service since it would make it impossible to upload arbitrary files to their servers since lossy compression would destroy any files uploaded. This also means that many types of steganography would fail if one were to try to store arbitrary data on Google Photos.

The Workaround

By uploading images of text to Google Photos, we are able to upload arbitrary data. This is because whatever type of lossy image compression Google is using will leave all text in photos intact (as would be expected by the users of Google Photos).

So this means that in order to store arbitray data on Google Photos, all you have to do is:

  1. Base64 encode a file.
  2. Create a series of images containing the above text (Google Photos only allows images under 16 megapixels)
  3. Upload the images

And conversely, when retrieving the data from Google Photos, all that has to be done is:

  1. Run image recognition on each photo to get the text.
  2. Base64 decode the text
  3. Concatenate the base64 decoded text from each image into one big file

The Code

I have written a custom python program to implement the above. The code for it is here. To use it import is as a library and call getImages('fileToBackup') and it will generate the photos for you to upload to Google Photos. To recover the file, download the images from Google Photos into your current dir and call getMessage('fileToBackup').

So What?

There are two main uses of this. The first use would be to use this to scheme to upload images to Google Photos thereby preventing Google from running their lossy compression algorithm on your photos.

The other use of this would be as a true back up service. Since one can upload arbitray files to Google Photos, one can use this as a long term backup solution. One could even write a FUSE filesystem so as to allow for Google Photos to be mounted onto a computer as an external hard drive.

Bug Bounties List!

The List

Over the past 2 months, I've spent some time working on bug bounties. In the past two months, I have found vulnerabilities in the following websites:

  • AT&T
  • Adobe
  • Dyn
  • Go Daddy
  • Western Union
  • Symantec
  • IBM
  • Vistaprint
  • Hobby King
  • Tumblr
  • Haiku Learning
  • Mozilla
  • Maret
  • eBay
  • iFixIt
  • MailChimp
  • Amazon
  • Steam
  • Netflix
  • Appcelerator
  • mail.ru

So far most of these companies have responded and they have either patched or they are actively working towards patching the vulnerabilities. Once these companies approve public disclosure, I will be adding write ups for each vulnerability to the bottom of this blog post.

Using XSScrapy to Scan for XSS Vulnerabilities

Using XSScrapy to Scan for XSS Vulnerabilities

XSScrapy is an amazing tool for the aspiring cyber security researcher. Entering the cyber security field used to be challenging and full of hours of wasted research with nothing to show for it. With XSScrapy, that is no longer true.

So what is an XSS Vulnerability

A XSS vulnerability is what happens when a website displays user input without escaping this. If this makes sense to you, continue to the next section.

A common example of when this would happen is if the website returned the following HTML:

<a href="[Link to the current page]">

So on example.com, this would display:

<a href="http://example.com/">

By loading example.com/"><script>alert(0)</script><", the HTML becomes:

<a href="example.com/"><script>alert(0)</script><"">

Which when indented properly looks like:

<a href="example.com/">

All of which is valid HTML and will run properly. In this case, we have injected javascript code to display a popup box. While a popup box is not dangerous, the javascript can be used to do a number of other malicious things. For example, the injected javascript could automatically exfiltrate cookies back to an attacker owned server (thus giving the attacker access to the victim's account).

So what is XSScrapy?

XSScrapy is an XSS scanner written by Dan McInerney in Python. XSScrapy works by using scrapy to create a web spider to download the HTML of all pages on a given domain name. Scrapy finds URLs by automatically following all of the links on the website until it has scanned every single URL. Once the HTML for a page is downloaded, XSScrapy automatically searches the page for XSS vulnerabilities.

This is done by looking for a number of common injection points and injecting the string 9zqjxel'"(){}<x>:9zqjxel;9. The important part of this string is '"(){}<x>:;. This string contains pretty much every possibly "dangerous" character. So if the HTML is rendered without those characters being escaped, it is likely that there is an XSS vulnerability.

Ok, let's start scanning!

First, follow the installation instructions here. Once you have done that, let's find a website to scan.

Never run XSScrapy against a domain that you do not own or have permission to penetration test. So if you are looking for a website to practice your new found skills on, try any website that offers a bug bounty. Look on websites like Bug Crowd, Hacker One, or CrowdCurity to find websites that allow for automated scanning.

Once you found one, run:

xsscrapy.py -u http://example.com

Once the scan is finished, let's look through the xsscrapy-vulns.txt file. So run cat xsscrapy-vulns.txt. (Assuming the program found some potential vulnerabilities) This should give you a nice list of vulnerabilities in the form of:

URL: [URL of the Vulnerability]
response URL: [URL of the Vulnerability]
Unfiltered: [The code causing the problem]
Payload: [What was injected to trigger the problem]
Type: [Type of Vulnerability]
Injection point: [Where the payload was injected]
Possible payloads: [A suggested payload to exploit: Note this is often incorrect]
Line: [The problematic line of HTML code]

So now lets go through an example vulnerability report line by line. 

<pre>`URL: www.example.com/?q=9zqjxxe'"(){}<x>:9zqjxxe;9
response URL: www.example.com/?q=9zqjxxe'"(){}<x>:9zqjxxe;9
Unfiltered: '"(){}<x>
Payload: 9zqjxxe'"(){}<x>:9zqjxxe;9
Type: form
Injection point: q
Possible payloads: x"/onmouseover=prompt(9)/", x"x><svG/onLoad=prompt(9)>, x" onmouseover=prompt(9) "
Line:  <input type="hidden" name="q" value="9zqjxgm'"(){}<x>:9zqjxgm;9

[<span class="octicon octicon-link"></span>](#developing-the-exploit-if-type--form)Developing the Exploit (if Type == form)

Take the URL from the first line and load it in your browser `www.example.com/?q=9zqjxxe'"(){}<x>:9zqjxxe;9`

Now modify the URL to `www.example.com/?q=`

Then look at the the last line of XSScrapy's vulnerability report: 

<pre>`Line:  <input type="hidden" name="q" value="9zqjxgm'"(){}<x>:9zqjxgm;9

So based off of the above except from the HTML, we can tell that we want to first add a " to escape from the value variable followed by a > to escape out into the HTML. Together, these two characters will get all following text to be interpreted as HTML. So now that we have that done, we simply tack a standard <script>alert(0)</script> onto the end of "> making our final input "><script>alert(0)</script>. So now we have our payload.

Take that payload and add it on to the URL we got earlier (www.example.com/?q=) to make www.example.com/?q="><script>alert(0)</script>. Then load that URL, and you should get a pop up box displaying 0!

Now that you have gotten this far, the final step is to report the vulnerability to the website. Create an account on what ever bug bounty website they use and submit it. If you've gotten this far, then Congratulations! You've reported your first vulnerability.



For a while now I've been wanting to make my own interface to WMATA's API. WMATA (Washington Metropolitan Area Transit Authority) has a pretty good API that allows for you to get anything from current train locations, a path between two stations on the same line, elevator/escalator/rail incidents, even an API endpoint for stations near a location.

The one thing that is lacking is an API to go from any one station to another. Their API does have an endpoint to get a list of stations between any two given stations with the caveat that this only works if the supplied stations are on the same line.

So I wrote my own wrapper around their API in python that implements this and much much more.


Scanning for Malicious Proxies

In the past few years, there has been a lot of press about HTTP proxies that transparently modify traffic to inject javascript for malicious purposes. There have been multiple presentations at DEFCON and blackhat about this topic and a variety of ways of exploiting it including: DDOS, credential theft, and even a distributed method of storage. 

Due to this, I decided to write a script to automatically scan proxies for malicious behavior such as: injecting javascript, modifying forms, and editing HTML. 

The first step was to get a list of proxies that are currently up and freely accessible. For this, I used a modified version of Dan McInerney's Elite Proxy Finder. Originally, this script allowed for automated speed testing and discovery of elite proxies. I modified this script so as to: 

  1. Output all types of proxies, not just elite proxies (since anonymity is not an issue).
  2. Output proxies that support only http. This was done because proxies are not able to modify resources transmitted over SSL, so support for a protocol that I will not be testing is not needed.
  3. Export a list of proxies and their ports

From there, I had a list of approximately 1000 active free proxies. While this is a relatively small number, I decided to move on with the experiment to see if there was any malicious behavior to be observed even in this small sample size. So on to the results!

~In all 1500 proxies, there was no malicious traffic. What I mean by this is that no proxies transparently edited the traffic without making it very clear that one was not reaching the requested website. There was no injection of javascript. There was no modification of forms. Absolutely no interesting modifications.~

~Thus the next step is going to be to use masscan to scan the ipv4 address space to look for free proxies, then once again search for malicious behavior.~

The code used for this post is posted here 

Update: I updated the script slightly so as to increase the number of proxies it discovered. With the addition of the updated script, I have discovered a number of malicious proxies that are currently active. 

Here is a list of all proxies detected with errant behavior and a description of their errant behavior.

The proxy server at

modifies HTML sent over HTTP to add the following code to every webpage

<script type='text/javascript' charset='utf-8' src='http://www.adfocus.com.cn/adscript/adfocus.js'></script>

Sadly, the adfocus.com.cn domain is not currently accessible, so I cannot view the injected javascript. Based off of the URL of the injected javascript, two conclusions can be made. 

  1. It is javascript used to automatically inject ads into websites accessed over HTTP. This seems to be the most obvious solution since the domain is "adfocus.com.cn" and the javascript file is stored in the "adscript" folder and is called "adfocus.js". This would make sense since it is a free proxy, the owners of the proxy are likely trying to monetize the business. 
  2. However, it seems like this might be what we are supposed to think. I say this because the javascript is hosted on the adfocus.com.cn domain, very similar to the legitimate adfocus.com. It might be that the administrators of this proxy are attempting to capture the reputation of adfocus.com so as to prove the legitimate nature of the injected code. This makes me wonder whether this code serves a more malicious purpose. The injected javascript could do anything from exploit the computer to be part of a botnet to be part of a distributed filesystem.

The proxy server at

automatically strips all HTML comments from the transmitted data. While this does not have any obvious negative effect, it still modifies the transmitted HTML which is an inherently bad thing for any proxy server to do. 

The proxy server at

modifies HTML sent over HTTP to add the following code to every webpage

<script type="text/javascript" charset="utf-8" mediaproAccountID="0" mediaproSlotID="0" usermac="" src="/7b26d4601fbe440b35e496a0fcfa15f7/000c4347fbe8/w1/i.js" async="async" defer></script><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

The interesting this about this javascript, is that the source of the javascript is on / (aka the server itself). At first glance, this seems impossible, but it turns out what the proxy is doing is hijacking all requests to 7b26d4601fbe440b35e496a0fcfa15f7/000c4347fbe8/w1/i.js (hence the long randomly generated string) and replying with a javascript file that is actually not hosted on the webserver of the page you are trying to access.

The proxy server at

modifies HTML sent over HTTP to add the following code to every webpage

<link rel="stylesheet" type="text/css" href="http://ads.adt100.com/bottom.css" /><div id="center_xad" class="cwindow_xad">    <div class="center_title_xad">          <img onclick="closeWindow()" width="15px" height="15px" src="http://ads.adt100.com/images/close_btn.gif">    </div>  <div id='center_xad_cnt' class="injection_content"></div></div><div id="right_xad" class="window_xad">  <div class="right_title_xad"><img onclick="closeWindow()" width="15px" height="15px" src="http://ads.adt100.com/images/close_btn.gif"></div><div id='right_xad_cnt' class="injection_content"></div></div><script type="text/javascript" src="http://ads.adt100.com/bottom.js"></script></body>

This code does a number of interesting things. First of all, it adds a new CSS style sheet. While the style sheet does not do anything malicious (instead choosing to allow the page to appear normally) it is clearly malicious in its purpose, the stylesheet is not the key to the malicious code.The interesting part is here. As my knowledge of javascript is quite limited, I'm going to stop my analysis here. But based off of a quick read through of the code, it seems to be malicious in nature considering the injection of a flash object attempting to download an exe.

So far, I have only analyzed a few select malicious proxies. A full data dump from running this script is available in the Github repo here if anyone wants to give an automated analysis a shot (6500+ lines).

Recoverable Secret Generator

David Dworken


Error Correcting Storage of Secrets in Human Memory


    In the past a number of different schemes have been proposed for the storage of encryption secrets in human memory. The vast majority of these schemes rely on the idea of asking the user to memorize a passphrase, often a string of randomly generated words, and then running this passphrase though a key derivation formula, often a hashing algorithm, to deterministically generate an encryption key. This method has a number of weaknesses. Whenever any sort of error is introduced, the key is drastically changed. This has the benefit of preventing calculation of the passphrase from the encryption key. The problem with this is it causes a minor error in human memory to lead to a completely different encryption key. If one were to: forget a part of the passphrase, input the string of words in the incorrect order, or misspell one of the words the generation scheme would fail. All of these errors are extremely likely to occur when humans attempt to memorize a series of words.

Passphrase Generation

    A new RSG compatible passphrase can be generated by following this general list of steps:

  1. Randomly generate a number between 0 and 5120

  2. $randomNumber %  512

  3. This is done to decrease the range of possible random numbers to 0 to 512. A random number in this range was not calculated originally since it allowed an attacker to figure out the state of python’s RNG from only part of the password.

Calculate what word from the PGP wordlist corresponds to that random number

Add that word to the passphrase variable.

Add a space to the passphrase variable

Repeat steps 1 through 5 until a passphrase with enough entropy has been generated.

    The corresponding checksum for a given passphrase can be generated by following this general list of steps:

  1. Split the passphrase into a list of strings, each of which is a single word from the PGP wordlist

  2. Calculate the place of each word in the wordlist

  3. Add this number to the sum variable

  4. Repeat steps 1 through 3 until you have iterated through all words

  5. Encode  $sum % 512 into a word from the PGP wordlist, this is the checksum

Authentication and Key Generation

   The Recoverable Secret Generator, or RSG for short, allows all of these errors to occur while still allowing the passphrases with high entropy. RSG allows for automatic correction of: forgotten words, incorrect ordering of the words, and misspelled words.

    Incorrect ordering of the words is solved by simply requiring the passphrase to be alphabetized. This means that whenever the passphrase is supplied, the key generation algorithm must first alphabetize the input.

    Misspelled words can be corrected through Levenshtein distance matching. The key generation algorithm first breaks the passphrase into a list of words. Each word is then checked against the list of words used in the generation of the passphrase. Any words that are not found in the list of possible words, are marked. Then the program computes the Levenshtein distance between the incorrect word and every word in the wordlist. The word that the incorrect word is closed to according to the Levenshtein distance is then stored in the place of the incorrect word.

    Correcting for forgotten words must be done as the final step in the key generation algorithm. A checksum of the passphrase is generated (as described in the Passphrase Generation section) and compared to the user supplied passphrase checksum word. If the calculated checksum is the same as the user supplied checksum,then no words have been forgotten. If the checksums do not match, the difference between the user supplied checksum and the calculated checksum is calculated. The difference is then encoded as a word, which is the forgotten word.

See a python implementation of this for key generation and general authentication against a hash here. Implementations in other languages are welcome!