Skip to content

Commit 52dc354

Browse files
Merge pull request #723 from devampkid:flowise-CVE-2025-58434
PiperOrigin-RevId: 808796607 Change-Id: Icb6fd4b9b838035cc9a6e49747f2d75ee6323452
2 parents b565e8e + 99a9410 commit 52dc354

6 files changed

Lines changed: 478 additions & 0 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Flowise CVE-2025-58434 Detector
2+
3+
This Tsunami plugin detects Flowise authentication bypass vulnerability
4+
(CVE-2025-58434). Flowise is a drag & drop UI tool for building LLM
5+
applications. The vulnerability allows credential retrieval via the
6+
forgot-password endpoint by knowing the email address of a user.
7+
8+
## Description
9+
10+
The detector performs the following checks: - Fingerprints Flowise instances by
11+
checking the main page title and tests the forgot-password endpoint for
12+
authentication bypass
13+
14+
## Build jar file for this plugin
15+
16+
Using `gradlew`:
17+
18+
```shell
19+
./gradlew jar
20+
```
21+
22+
Tsunami identifiable jar file is located at `build/libs` directory.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
plugins {
2+
id 'java-library'
3+
}
4+
5+
description = 'Tsunami Flowise CVE-2025-58434 VulnDetector plugin.'
6+
group = 'com.google.tsunami'
7+
version = '0.0.1-SNAPSHOT'
8+
9+
repositories {
10+
maven { // The google mirror is less flaky than mavenCentral()
11+
url 'https://maven-central.storage-download.googleapis.com/repos/central/data/'
12+
}
13+
mavenCentral()
14+
mavenLocal()
15+
}
16+
17+
def coreRepoBranch = System.getenv("GITBRANCH_TSUNAMI_CORE") ?: "stable"
18+
def tcsRepoBranch = System.getenv("GITBRANCH_TSUNAMI_TCS") ?: "stable"
19+
20+
dependencies {
21+
implementation("com.google.tsunami:tsunami-common") {
22+
version { branch = "${coreRepoBranch}" }
23+
}
24+
implementation("com.google.tsunami:tsunami-plugin") {
25+
version { branch = "${coreRepoBranch}" }
26+
}
27+
implementation("com.google.tsunami:tsunami-proto") {
28+
version { branch = "${coreRepoBranch}" }
29+
}
30+
31+
testImplementation "junit:junit:4.13.2"
32+
testImplementation "com.google.truth:truth:1.4.4"
33+
testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0"
34+
}
35+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
rootProject.name = 'flowise_cve_2025_58434'
2+
3+
def coreRepository = System.getenv("GITREPO_TSUNAMI_CORE") ?: "https://github.com/google/tsunami-security-scanner.git"
4+
def tcsRepository = System.getenv("GITREPO_TSUNAMI_TCS") ?: "https://github.com/google/tsunami-security-scanner-callback-server.git"
5+
6+
sourceControl {
7+
gitRepository("${coreRepository}") {
8+
producesModule("com.google.tsunami:tsunami-common")
9+
producesModule("com.google.tsunami:tsunami-plugin")
10+
producesModule("com.google.tsunami:tsunami-proto")
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.tsunami.plugins.detectors.flowise;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import com.google.common.annotations.VisibleForTesting;
22+
import com.google.common.collect.ImmutableList;
23+
import com.google.common.flogger.GoogleLogger;
24+
import com.google.protobuf.ByteString;
25+
import com.google.protobuf.util.Timestamps;
26+
import com.google.tsunami.common.data.NetworkServiceUtils;
27+
import com.google.tsunami.common.net.http.HttpClient;
28+
import com.google.tsunami.common.net.http.HttpHeaders;
29+
import com.google.tsunami.common.net.http.HttpRequest;
30+
import com.google.tsunami.common.net.http.HttpResponse;
31+
import com.google.tsunami.common.time.UtcClock;
32+
import com.google.tsunami.plugin.PluginType;
33+
import com.google.tsunami.plugin.VulnDetector;
34+
import com.google.tsunami.plugin.annotations.PluginInfo;
35+
import com.google.tsunami.proto.AdditionalDetail;
36+
import com.google.tsunami.proto.DetectionReport;
37+
import com.google.tsunami.proto.DetectionReportList;
38+
import com.google.tsunami.proto.DetectionStatus;
39+
import com.google.tsunami.proto.NetworkService;
40+
import com.google.tsunami.proto.Severity;
41+
import com.google.tsunami.proto.TargetInfo;
42+
import com.google.tsunami.proto.TextData;
43+
import com.google.tsunami.proto.Vulnerability;
44+
import com.google.tsunami.proto.VulnerabilityId;
45+
import java.io.IOException;
46+
import java.time.Clock;
47+
import java.time.Instant;
48+
import java.util.Optional;
49+
import javax.inject.Inject;
50+
51+
/** A {@link VulnDetector} that detects Flowise authentication bypass vulnerability. */
52+
@PluginInfo(
53+
type = PluginType.VULN_DETECTION,
54+
name = "FlowiseAuthBypassDetector",
55+
version = "0.1",
56+
description =
57+
"This detector checks whether a Flowise installation is vulnerable to "
58+
+ "CVE-2025-58434 (authentication bypass).",
59+
author = "DeVampKid",
60+
bootstrapModule = FlowiseAuthBypassDetectorBootstrapModule.class)
61+
public final class FlowiseAuthBypassDetector implements VulnDetector {
62+
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
63+
64+
private final Clock utcClock;
65+
private final HttpClient httpClient;
66+
67+
@VisibleForTesting
68+
static final String RECOMMENDATION =
69+
"Please update your Flowise instance to version 3.0.6 and higher."
70+
+ " Ensure proper authentication is enforced on the forgot-password endpoint.";
71+
72+
@VisibleForTesting
73+
static final ImmutableList<String> EMAILS_TO_TEST =
74+
ImmutableList.of("admin@admin.com", "test@example.com", "user@domain.com");
75+
76+
@Inject
77+
FlowiseAuthBypassDetector(@UtcClock Clock utcClock, HttpClient httpClient) {
78+
this.utcClock = checkNotNull(utcClock);
79+
this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build();
80+
}
81+
82+
@Override
83+
public ImmutableList<Vulnerability> getAdvisories() {
84+
return ImmutableList.of(
85+
Vulnerability.newBuilder()
86+
.setMainId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE_2025_58434"))
87+
.addRelatedId(
88+
VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2025-58434"))
89+
.setSeverity(Severity.HIGH)
90+
.setTitle("Flowise Authentication Bypass (CVE-2025-58434)")
91+
.setDescription(
92+
"Flowise instance is vulnerable to authentication bypass via the forgot-password"
93+
+ " endpoint, allowing unauthorized account creation and credential retrieval.")
94+
.setRecommendation(RECOMMENDATION)
95+
.build());
96+
}
97+
98+
@Override
99+
public DetectionReportList detect(
100+
TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {
101+
logger.atInfo().log("FlowiseAuthBypassDetector starts detecting.");
102+
103+
var detectionReport = DetectionReportList.newBuilder();
104+
matchedServices.stream()
105+
.filter(NetworkServiceUtils::isWebService)
106+
.filter(this::isFlowiseService)
107+
.forEach(
108+
networkService -> {
109+
Optional<String> vulnerableAccountEmail = isServiceVulnerable(networkService);
110+
vulnerableAccountEmail.ifPresent(
111+
email ->
112+
detectionReport.addDetectionReports(
113+
buildDetectionReport(targetInfo, networkService, email)));
114+
});
115+
return detectionReport.build();
116+
}
117+
118+
private boolean isFlowiseService(NetworkService networkService) {
119+
String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService);
120+
121+
try {
122+
HttpResponse response =
123+
httpClient.send(HttpRequest.get(targetUri).withEmptyHeaders().build(), networkService);
124+
return response.bodyString().isPresent()
125+
&& response
126+
.bodyString()
127+
.get()
128+
.contains("<title>Flowise - Build AI Agents, Visually</title>");
129+
} catch (IOException e) {
130+
logger.atWarning().withCause(e).log("Unable to query Flowise for fingerprinting.");
131+
return false;
132+
}
133+
}
134+
135+
private Optional<String> isServiceVulnerable(NetworkService networkService) {
136+
String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService);
137+
138+
// Try each email in the test list
139+
for (String testEmail : EMAILS_TO_TEST) {
140+
HttpResponse response;
141+
try {
142+
String forgotPasswordUri = targetUri + "api/v1/account/forgot-password";
143+
String forgotPayload = String.format("{\"user\":{\"email\":\"%s\"}}", testEmail);
144+
response =
145+
httpClient.send(
146+
HttpRequest.post(forgotPasswordUri)
147+
.setHeaders(
148+
HttpHeaders.builder().addHeader("Content-Type", "application/json").build())
149+
.setRequestBody(ByteString.copyFromUtf8(forgotPayload))
150+
.build(),
151+
networkService);
152+
if (response.status().code() != 201) {
153+
continue;
154+
}
155+
if (response.bodyString().isPresent()
156+
&& response.bodyString().get().contains("\"credential\"")
157+
&& response.bodyString().get().contains("\"tempToken\"")
158+
&& response.bodyString().get().contains("\"tokenExpiry\"")) {
159+
return Optional.of(testEmail);
160+
}
161+
} catch (IOException e) {
162+
logger.atWarning().withCause(e).log(
163+
"Unable to query Flowise for vulnerability check with email: %s", testEmail);
164+
}
165+
}
166+
167+
return Optional.empty();
168+
}
169+
170+
private DetectionReport buildDetectionReport(
171+
TargetInfo targetInfo, NetworkService vulnerableNetworkService, String testedAccountEmail) {
172+
return DetectionReport.newBuilder()
173+
.setTargetInfo(targetInfo)
174+
.setNetworkService(vulnerableNetworkService)
175+
.setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli()))
176+
.setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)
177+
.setVulnerability(
178+
getAdvisories().getFirst().toBuilder()
179+
.addAdditionalDetails(
180+
AdditionalDetail.newBuilder()
181+
.setTextData(
182+
TextData.newBuilder()
183+
.setText(
184+
String.format(
185+
"The Flowise instance at %s is vulnerable to authentication"
186+
+ " bypass (CVE-2025-58434). A password reset token was"
187+
+ " successfully obtained for the account %s.",
188+
NetworkServiceUtils.buildWebApplicationRootUrl(
189+
vulnerableNetworkService),
190+
testedAccountEmail)))))
191+
.build();
192+
}
193+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.tsunami.plugins.detectors.flowise;
18+
19+
import com.google.tsunami.plugin.PluginBootstrapModule;
20+
21+
/** A {@link PluginBootstrapModule} for {@link FlowiseAuthBypassDetector}. */
22+
public final class FlowiseAuthBypassDetectorBootstrapModule extends PluginBootstrapModule {
23+
@Override
24+
protected void configurePlugin() {
25+
registerPlugin(FlowiseAuthBypassDetector.class);
26+
}
27+
}

0 commit comments

Comments
 (0)