This is a guest badge that quietly opens the server room
CVE-2026-28797 is an authenticated server-side template injection in RAGFlow Agent workflow components, specifically Text Processing (StringTransform) and Message. In RAGFlow 0.24.0 and earlier, those components render user-controlled Jinja2 templates without sandboxing, so a low-privileged user can turn a harmless-looking template field into OS command execution on the RAGFlow server.
The vendor's HIGH call is directionally right on impact but a little hot if you care about enterprise patch queues. This is not pre-auth internet spray-and-pray RCE: the attacker needs a valid account and access to Agent workflow features, which sharply narrows exposed population; but the default self-registration path and the fact that *any* authenticated user can jump straight to server command execution keep it solidly in HIGH rather than MEDIUM.
4 steps from start to impact.
Get a basic RAGFlow account
REGISTER_ENABLED=1, so on casually exposed deployments this can be as simple as creating a normal user instead of stealing one.- RAGFlow is reachable from the attacker's network position
- The attacker can self-register or already has valid credentials
- Agent workflow features are available to the authenticated user
- Many enterprise deployments put RAGFlow behind SSO, VPN, or reverse proxies
- Some shops disable self-registration or do not expose the app publicly
- This prerequisite already implies a post-initial-access or valid-user scenario when registration is closed
Plant a Jinja2 SSTI payload in an Agent component
Text Processing or Message node and inserts a malicious Jinja2 expression such as the advisory's cycler.__init__.__globals__.os.popen(...) payload. The vulnerable code path uses unsandboxed jinja2.Template, so template syntax becomes code-reachable rather than data-only.- Authenticated user can create or edit an Agent flow
- Vulnerable code is present in
agent/component/string_transform.pyoragent/component/message.py
- Requires knowledge of the Agent workflow UI and which nodes render templates
- If the deployment already moved to sandboxed rendering in
v0.25.0+, this breaks
Trigger render and execute commands
os.popen() executes with the privileges of the RAGFlow server process, turning a normal user workflow action into host-level command execution in the application context.- The crafted workflow is executed
- The server process can spawn commands through Python runtime access
- EDR can sometimes catch suspicious child processes like
bash,sh, orpythonspawned by the app - Container hardening, seccomp, or restricted runtime privileges can limit follow-on damage even if code execution lands
Harvest secrets and pivot inside the stack
conf/service_conf.yaml, environment variables, and service credentials for MySQL, Redis, MinIO, and Elasticsearch. That means the practical blast radius is not just the web app process; it often becomes database access, document theft, API key exposure, and lateral movement into adjacent internal services.- RAGFlow stores usable credentials locally or in environment variables
- Backend services are reachable from the compromised app host or container
- Segmentation and secret externalization reduce the value of app compromise
- Read-only service accounts and isolated data-plane services can blunt the pivot
conf/service_conf.yaml, bulk DB access from the app tier, unusual Redis/Elasticsearch/MinIO connections, and egress after Agent workflow executions.REGISTER_ENABLED=0 or otherwise closing public signup. For a HIGH verdict, deploy this compensating control within 30 days; do it first on any internet-reachable instance.os.popen, reverse shells, or unusual outbound connections from the RAGFlow process/container. This is a detection-oriented compensating control and should also be in place within 30 days for lingering vulnerable nodes.- A generic WAF is not enough; the exploit sits in authenticated business logic and uses legitimate workflow fields rather than a clean unauthenticated HTTP exploit string.
- Relying on network-only vuln scanning will miss too much because the vulnerable path requires login and specific Agent workflow interactions.
- Simply rotating LLM provider API keys does not solve the core issue; the attacker can still execute commands and read whatever new secrets the app can access.
The supporting signals.
| In-the-wild status | No public active-exploitation evidence found in the reviewed authoritative sources, and CISA KEV does not list this CVE as of 2026-05-30. |
|---|---|
| PoC availability | Yes, effectively public. The GitHub advisory includes full reproduction steps, working payloads, and a demo video, which is enough for immediate copy-paste weaponization. |
| EPSS | User-supplied EPSS is 0.00102 (~0.10%), which is low. Primary FIRST reference confirms EPSS is a daily probability signal, but the exact percentile for this CVE was not directly verifiable from reviewed primary sources. |
| KEV status | Not KEV-listed. That materially lowers urgency versus edge-device or mass-exploitation bugs. |
| CVSS vector readout | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H means network reachable, easy to exploit, but requires valid low privileges. The whole reassessment turns on that PR:L prerequisite. |
| Affected versions | Authoritative sources say RAGFlow 0.24.0 and earlier are affected, in Agent workflow StringTransform and Message components. |
| Fixed version | The advisory said no public patch at publication. Upstream code and release timing indicate the sandboxed fix is present by v0.25.0; no distro backport guidance was found. |
| Exposure reality | This is not a firewall-class product with huge edge prevalence. Public web search of Shodan facet pages shows only low-count indexed banners titled RAGFlow, suggesting limited direct internet exposure; exact counts fluctuate and this is an inference from public scan pages. |
| Disclosure timeline | GitHub advisory published 2026-04-01; NVD published 2026-04-03; NVD last modified 2026-04-22. |
| Reporter / source | The authoritative disclosure path is GitHub Security Advisory GHSA-vvwj-fvwh-4whx, published by yingfeng for infiniflow/ragflow. |
Sources.
Why this verdict
- Downgrade from vendor 8.8 for
PR:L: requiring authenticated remote access means the attacker is not starting from the open internet unless self-registration is enabled or creds are already compromised. - Exposure population is narrower: RAGFlow is popular in open-source circles, but it is not an internet-edge staple, and public scan artifacts suggest only limited direct exposure compared with mass-targeted platforms.
- Upgrade pressure remains because blast radius is ugly: once inside, the attacker can pivot from app-level template injection to command execution, secret theft, data access, and likely control of backing services.
Why not higher?
This is not pre-auth RCE and not a KEV-listed or publicly observed mass-exploitation event. The chain assumes either valid credentials or a registration path, and many enterprise deployments should already put this app behind SSO, VPN, or restricted admin access.
Why not lower?
The permission bar is low: GitHub's advisory says *any authenticated user* can reach the vulnerable workflow components, and self-registration is enabled by default. Once triggered, the vulnerability is not a narrow tenant-only issue; it can expose host commands, service credentials, and all tenant data reachable by the server.
Crowdsourced verification payload.
Run this on the target RAGFlow host or inside the ragflow-server container. Example: docker exec -i ragflow-server bash -s -- /ragflow < ./check_cve_2026_28797.sh or, from a source checkout, bash ./check_cve_2026_28797.sh /path/to/ragflow; no root is required unless you need docker exec access.
#!/usr/bin/env bash
# check_cve_2026_28797.sh
# Detects likely vulnerable vs patched RAGFlow code for CVE-2026-28797
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
TARGET="${1:-/ragflow}"
if [ ! -d "$TARGET" ]; then
if [ -d ".git" ] && [ -f "agent/component/string_transform.py" ]; then
TARGET="."
fi
fi
STR_FILE="$TARGET/agent/component/string_transform.py"
MSG_FILE="$TARGET/agent/component/message.py"
if [ ! -f "$STR_FILE" ] || [ ! -f "$MSG_FILE" ]; then
echo "UNKNOWN - could not find RAGFlow source files under: $TARGET"
exit 2
fi
has_vuln_pattern() {
local f="$1"
grep -Eq 'from jinja2 import Template as Jinja2Template|Jinja2Template\(' "$f"
}
has_patch_pattern() {
local f="$1"
grep -Eq 'from jinja2\.sandbox import SandboxedEnvironment|SandboxedEnvironment\(|_jinja2_sandbox|from_string\(' "$f"
}
str_vuln=0
msg_vuln=0
str_patch=0
msg_patch=0
has_vuln_pattern "$STR_FILE" && str_vuln=1
has_vuln_pattern "$MSG_FILE" && msg_vuln=1
has_patch_pattern "$STR_FILE" && str_patch=1
has_patch_pattern "$MSG_FILE" && msg_patch=1
version_hint="unknown"
if [ -f "$TARGET/README.md" ]; then
version_hint=$(grep -Eo 'v0\.[0-9]+\.[0-9]+' "$TARGET/README.md" | head -n1 || true)
[ -z "$version_hint" ] && version_hint="unknown"
fi
if [ "$str_vuln" -eq 1 ] || [ "$msg_vuln" -eq 1 ]; then
echo "VULNERABLE - unsandboxed Jinja2 usage detected (version hint: $version_hint)"
exit 1
fi
if [ "$str_patch" -eq 1 ] && [ "$msg_patch" -eq 1 ]; then
echo "PATCHED - sandboxed Jinja2 patterns detected in both vulnerable components (version hint: $version_hint)"
exit 0
fi
echo "UNKNOWN - source present but could not confidently classify patched state (version hint: $version_hint)"
exit 2