Skip to content

Commit

Permalink
graphql: Add tech detection for GraphQL Engines
Browse files Browse the repository at this point in the history
- CHANGELOG > Added note.
- build file > Added dependency.
- GraphQlFingerprinter > Updated to add Technology matches when GraphQL
engines are fingerprinted.
- ExtensionTechDetection > Added to facilitate optional dependence.
- ExtensionTechDetectionUnitTest > Tests :)
- Messages.properties > Added key/value pairs to support the optional
dependency.

Signed-off-by: kingthorin <kingthorin@users.noreply.github.com>
  • Loading branch information
kingthorin committed Oct 24, 2024
1 parent 9f6590d commit 98ca05d
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 1 deletion.
3 changes: 2 additions & 1 deletion addOns/graphql/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added
- If the Tech Detection (Wappalyzer) add-on is installed and a GraphQL engine is successfully finger printed it is added to the Technology tab/data.

## [0.25.0] - 2024-09-24
### Changed
Expand Down
14 changes: 14 additions & 0 deletions addOns/graphql/graphql.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ zapAddOn {
}
}
}

register("org.zaproxy.addon.graphql.techdetection.ExtensionTechDetection") {
classnames {
allowed.set(listOf("org.zaproxy.addon.graphql.techdetection"))
}
dependencies {
addOns {
register("wappalyzer") {
version.set(">= 21.42.0")
}
}
}
}
}
}

Expand All @@ -61,6 +74,7 @@ dependencies {
zapAddOn("automation")
zapAddOn("commonlib")
zapAddOn("spider")
zapAddOn("wappalyzer")

implementation("com.graphql-java:graphql-java:22.3")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
import org.apache.commons.httpclient.URI;
import org.apache.logging.log4j.LogManager;
Expand All @@ -44,12 +45,14 @@ public class GraphQlFingerprinter {
CommonAlertTag.toMap(CommonAlertTag.WSTG_V42_INFO_02_FINGERPRINT_WEB_SERVER);
private static final Logger LOGGER = LogManager.getLogger(GraphQlFingerprinter.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final BiConsumer<URI, String> DEFAULT_APP_CONSUMER = (site, app) -> {};

private final Requestor requestor;
private final Map<String, HttpMessage> queryCache;

private HttpMessage lastQueryMsg;
private String matchedString;
private static BiConsumer<URI, String> appConsumer = DEFAULT_APP_CONSUMER;

public GraphQlFingerprinter(URI endpointUrl) {
requestor = new Requestor(endpointUrl, HttpSender.MANUAL_REQUEST_INITIATOR);
Expand Down Expand Up @@ -95,6 +98,9 @@ public void fingerprint() {
try {
if (fingerprinter.getValue().getAsBoolean()) {
raiseFingerprintingAlert(fingerprinter.getKey());

appConsumer.accept(
lastQueryMsg.getRequestHeader().getURI(), fingerprinter.getKey());
break;
}
} catch (Exception e) {
Expand Down Expand Up @@ -576,4 +582,8 @@ private boolean checkWpGraphQlEngine() {
}
return false;
}

public static void setAppConsumer(BiConsumer<URI, String> consumer) {
appConsumer = consumer == null ? DEFAULT_APP_CONSUMER : consumer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.graphql.techdetection;

import java.util.List;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.Extension;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.zaproxy.addon.graphql.GraphQlFingerprinter;
import org.zaproxy.zap.extension.wappalyzer.Application;
import org.zaproxy.zap.extension.wappalyzer.ApplicationMatch;
import org.zaproxy.zap.extension.wappalyzer.ExtensionWappalyzer;
import org.zaproxy.zap.view.ScanPanel;

public class ExtensionTechDetection extends ExtensionAdaptor {

public static final String NAME = "ExtensionTechDetectionGraphQl";

private static final List<Class<? extends Extension>> DEPENDENCIES =
List.of(ExtensionWappalyzer.class);

private static ExtensionWappalyzer extTech;

public ExtensionTechDetection() {
super(NAME);
}

@Override
public String getUIName() {
return Constant.messages.getString("graphql.techdetection.name");
}

@Override
public String getDescription() {
return Constant.messages.getString("graphql.techdetection.desc");
}

@Override
public List<Class<? extends Extension>> getDependencies() {
return DEPENDENCIES;
}

@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);
GraphQlFingerprinter.setAppConsumer(this::addApp);
}

@Override
public boolean canUnload() {
return true;
}

@Override
public void unload() {
// Nothing to do
}

private static String normalizeSite(URI uri) {
String lead = uri.getScheme() + "://";
try {
return lead + uri.getAuthority();
} catch (URIException e) {
// Shouldn't happen, but sure fallback
return ScanPanel.cleanSiteName(uri.toString(), true);
}
}

private static ApplicationMatch getAppForEngine(String engineId) {
final String enginePrefix = "graphql.engine." + engineId + ".";

Application engine = new Application();
engine.setName(Constant.messages.getString(enginePrefix + "name"));
engine.setCategories(List.of("GraphQL Engine"));
engine.setWebsite(Constant.messages.getString(enginePrefix + "docsUrl"));
engine.setImplies(List.of(Constant.messages.getString(enginePrefix + "technologies")));

return new ApplicationMatch(engine);
}

public void addApp(URI uri, String engineId) {
getExtTech().addApplicationsToSite(normalizeSite(uri), getAppForEngine(engineId));
}

private static ExtensionWappalyzer getExtTech() {
if (extTech == null) {
extTech = Control.getSingleton().getExtensionLoader()
.getExtension(ExtensionWappalyzer.class);
}
return extTech;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,8 @@ graphql.options.value.split.rootField = Each Field of an Operation
graphql.spider.desc = GraphQL Spider Integration
graphql.spider.name = GraphQL Spider

graphql.techdetection.desc = GraphQL Technology Detection Integration
graphql.techdetection.name = GraphQL Tech Detection

graphql.topmenu.import.importgraphql = Import a GraphQL Schema
graphql.topmenu.import.importgraphql.tooltip = Specify a GraphQL endpoint and optionally a GraphQL schema file to import.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.graphql.techdetection;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;
import org.zaproxy.addon.graphql.ExtensionGraphQl;
import org.zaproxy.zap.testutils.TestUtils;

class ExtensionTechDetectionUnitTest extends TestUtils {

@Test
void shouldHaveName() {
// Given
ExtensionTechDetection td = new ExtensionTechDetection();
mockMessages(new ExtensionGraphQl());
// When / Then
assertThat(td.getName(), is(equalTo("ExtensionTechDetectionGraphQl")));
}

@Test
void shouldHaveUiName() {
// Given
ExtensionTechDetection td = new ExtensionTechDetection();
mockMessages(new ExtensionGraphQl());
// When / Then
assertThat(td.getUIName(), is(equalTo("GraphQL Tech Detection")));
}

@Test
void shouldBeUnloadable() {
// Given
ExtensionTechDetection td = new ExtensionTechDetection();
// When / Then
assertThat(td.canUnload(), is(equalTo(true)));
}
}

0 comments on commit 98ca05d

Please sign in to comment.