Extract
Can you extract the secrets of the library? - by l000g1c
The following post by 0xb0b is licensed under CC BY 4.0
Recon
We start with a Nmap scan and find two open ports: Port 22 with SSH, and the other is port 80, which has the Apache 2.4.58 web server.

We visit the index page and see a document preview page for PDFs. Nothing special yet.

In the source we see a smal script loads a given PDF into an iframe by passing its URL to preview.php and then makes the iframe visible.

We continue with a directory scan and we find a management directory.

But the Access is denied to http://extract.thm/management

Next, we visit the preview.php page, recalling the source of the index page. It informs us, that the url param is missing.

SSRF
The preview page with the URL parameter looks very much like a server-side request forgery (SSRF) vulnerability. We are now testing for this.
Detection
We have a Server Side Request Vulnerability in front of us, since we can reach out to our own web server: http://extract.thm/preview.php?url=http://10.14.90.235.
Since we know we are dealing with a PHP web server we could try to provide a web shell with a simple Python web server, but the SSRF does not leverage to evaluation of the code. If we provide the web shell via a PHP web server it gets evaluated on our machine.

For now, we submit the localhost and see a preview is still possible.

Next, we try to reach out to /management which we had no access before. This reveals us a login page for the TryBookMe page.
From there we can't just submit anything to the form, unless we can use gopher protocol to leverage GET and POST requests in SSRF.

Enumerate Internal Services
But for now, we continue with enumerating internal services. For this we craft a wordlist containing all possible ports.
And reach out to every possible service using FFuF. We are able to identify a service on port 10000.

We reach out to 127.0.0.1:10000 and see that the access is permitted. But There's also a link to the API http://extract.thm/customapi.

We view the source on http://extract.thm/preview.php?url=127.0.0.1:10000 and it looks like a page crafted with NextJS.
API Access - Authorization Bypass CVE-2025-29927
After some research, we find a recently published CVE on NextJS and an authorization bypass:
The vulnerability requires the following header to apply the authorization bypass.
So, we need to make a request with a header.
As we have already mentioned some times, we will probably have to use Gopher. A Gopher request is a text-based query sent to a server over TCP port 70 as part of the Gopher protocol.
The request contains a selector string ending with \r\n, and the server responds with either menus, documents, or binary data.
Although largely obsolete, Gopher requests can be still relevant in security contexts such as SSRF exploitation like we have here.
A small guide on how to use Gopher and crafting own request can be found here:
The following tools might assist us:
We try to use SSRFgopher to craft us a request with the middleware-subrequest header to bypass the authorization check like explained in CVE-2025-29927.

Neither the single URL encoded payload nor the double URL encoded seems to work. From now on we try to craft them on our own.

We want to make a GET request with the middleware-subrequest header set. We explain the structure of the Gopher request using the example from SSRFGopher, which did not work in the first place. It seems like there is an encoding issue.
In Gopher, the underscore _ is just the separator that marks the start of the selector string. When /_GET... is passed in the URL, the server connects and sends that string exactly as raw data.
Breakdown:
gopher://127.0.0.1:10000/→ Target internal service running on localhost port 10000_→ Separator (start of the selector string in Gopher)GET customapi HTTP/1.1→ The raw HTTP request line we want the internal service to process%0a→ Line feed (\n), used to terminate each header line (here LF is enough, though in strict HTTP it’s usually%0d%0a)Host: 127.0.0.1→ Host header pointing to the internal servicex-middleware-subrequest: middleware→ The special header that exploits CVE-2025-29927 (Next.js middleware auth bypass)Final
%0a%0a→ Blank line that ends the HTTP headers section and signals the start of the body (none here, since it’s a GET request)
After URL decoding, the remote service should end up seeing and evaluating the follwing:
But we just get a blank response.

With the following request without using the header yet, we retrieve a chunked redirect by double URL encoding it manually. Its encoded by the following scheme:
double-encode spaces (
%2520), CR (%250d), LF (%250a).Single-encode the forward slash (
%2f).Not encoded: keywords like
GET,HTTP/1.1,Host:, digits, and colons.

Now we add the header x-middleware-subrequest: middleware:
With the following double URL encoded request we are able to retrieve the credentials of the user librarian - recalling the login page at /management. Furthermore we are able to spot the first flag.

Management Access
With the obtained credentials, we return back to the /management page - which was also accessible via the SSRF. We then craft a Gopher GET request and review the page source to identify the POST parameters used by the form. The POST data parameters username and password are used.

We could also just inspect the source by our initial SSRF request.

We prepare the gopher request, and URL encoded first the last bits of the password.
Next, we double URL encode the request again with the scheme explained initially.
We receive a redirect to a 2FA page, we see an auth token set in the cookies as well as a PHPSESSID.

We decode the token, to prepare another gopher request to the 2FA page with the token.

Our next request is prepared with the PHPSESSID cookie and the auth token:
This time we need three layers of URL encoding. The third layer takes place in the AuthToken cookie to escape the special characters there. If we do not see a redirect, we set the token and PHPSESSID correctly.

We are not logged in yet, since have not set the validation of the 2FA token: auth_token=O:9:"AuthToken":1:{s:9:"validated";b:0;}
We can set it like the following:
We prepare the Gopher request and URL Encode the payload like mentioned before:
We are logged in an are able to retrieve the final flag:

Unintended
There is an uninteded we overlooked while testing the room. We are also able to leverage the URL parameter to a file inclusion. While file:/// gets filtered, file:/ doesn't and get properly evaluated. In this example we include /etc/passwd.

We can inspect the sources of the different pages.

In /var/www/html/preview.php we can see the filter that is applied, but not sufficient.

We can spot the management page we found using our directory scan. Revealing username and password. Furthermore the 2fa.php page.

This 2fa.php page contains the second flag.

AutoPwn by 0day aka Ryan Montgomery
This autopwn script chains multiple SSRF payloads to extract flags automatically. It first bypasses the Next.js middleware to grab the initial flag, then forges a serialized cookie to bypass 2FA and retrieve the second flag. The script is written and sharedd by 0day - Ryan Montgomery! Thank you very much for sharing!
Last updated
Was this helpful?