NullCheck is a critical (CVSS 9.1 max) bundle of vulnerabilities I discovered around the beginning of July, towards the middle of July, reported on the 6th July at 2AM & 16th July at 8PM to the GoogleVRP, regarding Google's ProjectShield. Now, ProjectShield is a free (if eligible) service enabling DDoS protection for:
- Independent media and investigative journalism outlets
- Election monitoring and human rights groups
- Non-profit arts and sciences initiatives
- Political organizations and democratic campaigns
- Marginalised community defense groups
The reason its important to mention who is eligible for ProjectShield, is due to how the vulnerabilities affect users, and how they can be utilised by threat actors for very malicious purposes, as follows:
-
Malicious file storage
- Attacker can upload arbitrary files (executable, scripts, payloads) to Google's ProjectShield database
- Files persist indefinitely with valid Salesforce record IDs
-
Supply chain risk
- ProjectShield protects nonprofits and activists from DDoS attacks. These vulnerabilities, collectively, can damage that protection.
- Compromise of ProjectShield infrastructure could affect protected organizations
- Malicious files could be used as pivot point for further attacks
-
Scale of attack
- No rate limiting observed - attacker can create thousands of records per minute, which enables high-scale malware attacks as a single script can i.e.,automatically change a CNC beaconing destination
- Can upload files of arbitrary size
- Can enumerate and download existing Attachment records via same Aura endpoint
- Potential for mass data exfiltration from ProjectShield database
Google's ProjectShield runs multiple Salesforce instances, but for this particular area of the write-up we're going to focus on https://help.projectshield.google. The Aura framework allows unauthenticated users to create and upload files to the Attachment object. The vulnerability exists due to improper permission configuration on the guest-accessible recordGlobalValueProvider.RecordGvpController Aura service component.
Salesforce can, a lot of the time, mean literally anything. However, in this context that is only true on the surface - as the help. subdomain simply looks like your average "Contact us" / FAQ page, but when you look a little deeper, that is but the case.
I got curious. So, I started off by first extracting the Aura context (CSRF Token and FWUID):
Response (real FWUID redacted):
Nice. Decided to skip all the basic boxes I usually tick off with Salesforce auditing (usually not the best idea, this can waste a lot of time, going backwards) and go straight to testing the Attachment apiName with ACTION$saveRecord. This should not give a 200 OK, and certainly no response body, right? So, I craft a JSON payload for file upload. The body field contains Base64-encoded file content - example below decodes to "TEST FILE CONTENT":
Looks good, now its time to actually send the request. This may give a 200 OK response, I said to myself doubtingly, replacing <PAYLOAD_JSON> with the crafted payload from above, and <FWUID_FROM_STEP_1> with the actual Framework UID:
Wow. Okay, it actually worked. The server actually sent a response with both 200 status code and response body. Hoping this wasn't a false positive, I checked the actual body, and of course, there it is.
state:SUCCESSconfirms the actual Salesforce host accepted the requestid:00P5b000001ABCDEis a valid Salesforce Attachment record ID- Record is persisted in production database (not temporary)
- File content is stored in the Body field
- HTTP 200 confirms no errors
Now all I had to do was confirm persistence, report, and wait. If you understand this write-up, then you've probably noticed that we haven't gotten to any auth bypass, but that's a key part of the vulnerability. Here is where it comes in. Its a very simple auth bypass that's commonly found in a lot of:
- New MCP servers
- AI generated middlewares
- Beginner login-flow setups
It is, of course, the guest session ID. So we can either retrieve the file via Salesforce DetailController:
Or directly via Aura getRecord:
And so, I reported the vulnerability, and waited for a response.
The same day I uploaded the initial vulnerability, I had realised I uploaded the completely wrong scan log to the VRP without double-checking. This scan log was for multiple .gov websites that had the same vulnerability as ProjectShield, that I had planned to report to the U.S. Department of State the same day. So, quickly, I left a comment on my report apologising for the mishap and explaining. Lesson learned, always double-check your reports
Between then and 16th July 4PM, I had 2 comments to read. One, on 13th July 2PM mentioning that based upon their initial assessment, "it seems this issue is not severe enough to qualify for a reward. However, the VRP panel will take a closer look at this report at their next meeting." I thought "eh thats not a total rejection," so I continued waiting. On 16th July 4PM, I received a notification from the buganizer-system, the following is what I read:
RIP. No financial reward. I decided instead of pouting and being bummed theres no financial reward, I'd attempt to escalate. Boy was that a good idea.
I noticed that in previous scans over ProjectShield I had also caught another Salesforce instance, at https://support.projectshield.withgoogle.com. I cannot believe I missed this, seriously.
So, upon digging, I noticed I wasn't only able to access ACTION$saveRecord (write access,) but also ACTION$getRecord/getItems (read access.)
In my repport, I highlighted that, while the original report focused strictly on guest-creatable Attachment uploads, this addition shows that unauthenticated users have write access to eight distinct Salesforce tables (including User and Chatter FeedItem tables) and unauthenticated read/download access to internal documents, workflow tracking items, and tenant infrastructure configuration.
This particular vulnerability contains 7 parts, as follows:
The first part, User (005), allows unauth users to insert new user records, which enables record injection and backdoor profile creation. When replacing <VULNERABLE_PROFILE_ID>, Salesforce attempts to assign that profile to the newly created user record. cURL command below:
Second part, ContentVersion (068), as you can see from the screenshot showing the seven parts, allows juploading arbitrary files utilising the modern Salesforce Files architecture - replacing <BASE64_ENCODED_PAYLOAD> with the desired payload, malware.exe with desired file name, and Malicious_Payload with the desired title:
Chatter Feed Spam via FeedItem (0D5), replace <TARGET_RECORD_OR_USER_ID> with the target record or User ID & replace Body value with whatever is desired, you get the point:
Attachment Creation (00P):
With ContenDocumentLink (06A), the request attempts to link an existing ContentDocument (such as a file uploaded via ContentVersion, <CONTENT_DOCUMENT_ID>) to a target record (e.g., a specific User, Case, or Account, <TARGET_RECORD_ID>), changing file visibility (AllUsers):
ContentNote (069) & Note (002):
Now, ObjectRelatedUrl (0zC) is an interesting one, as it allows you to inject a custom link or URL entry associated with a specific object, enabling phishing, social engineering, and defacement attacks on a real Google page:
This vulnerability allowed you to query and extract internal Salesforce CRM records. By targeting the ACTION$getRecord and ACTION$getItems methods, bypassing restrictions to retrieve corporate files, internal workflows, and user directory information is made trivial.
ContentVersion & ContentDocument Retrieval:
Extracting internal workflows via ProcessInstanceWorkitem, showing active internal approval workflows, queues, and operational transit states, which exposes internal processes and pending company actions:
If you've dealt with Salesforce auditing before, you know that Knowledge__kav is usually public, and just FAQ stuff for support, so it isn't much impact, so I have refrained from putting it here.
Making a guest call to ACTION$getConfigData returns the internal Salesforce routing domains, such as:
defaultOrgDomainsetupOrgOriginvfDomainlightningOrgOrigin
Guest requests retrieve a pre-generated CSRF token and a valid JWT token representing guest roles ['gud:0050b000005eOKTAA2'] allowing the authorisation bypass.






















