Skip to content

Commit 8eb99bf

Browse files
committed
screenshots, dyn. adopt verification, dns-change & refactorings
Refactored Bidi Network request capturing Dynamically adopt Verification Strategy change dns resolution via config Take Screenshots & Improved Error Handling
1 parent 625d56b commit 8eb99bf

49 files changed

Lines changed: 1095 additions & 405 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
## [1.0.3] - 2026-03-14
13+
14+
### Added
15+
16+
- Take Screenshots
17+
- Dynamically adopt Verification Strategy
18+
- change dns resolution via config
19+
20+
### Changed
21+
22+
- Improved Error Handling
23+
- Refactored Bidi Network request capturing
24+
1225
## [1.0.1] - 2026-03-10
1326

1427
### Added

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ RUN mvn dependency:go-offline
88
COPY src ./src
99
RUN mvn clean package -DskipTests
1010

11-
FROM eclipse-temurin:21-jre-jammy
11+
FROM debian:sid-slim
1212
# Source: https://github.com/SeleniumHQ/docker-selenium/tree/trunk/NodeChromium
1313

1414
USER root
1515

1616
ENV DEBIAN_FRONTEND=noninteractive
1717

18+
RUN apt-get update -qqy && apt-get -qqy install wget curl default-jdk
19+
1820
COPY docker/install-chromium.sh ./install-chromium.sh
1921
RUN ./install-chromium.sh
2022

@@ -31,8 +33,6 @@ RUN /opt/bin/wrap_chromium_binary && chromium --version
3133
COPY ./docker/chrome-cleanup.sh /opt/bin/chrome-cleanup.sh
3234
COPY ./docker/chrome-cleanup.conf /etc/supervisor/conf.d/chrome-cleanup.conf
3335

34-
USER ${SEL_UID}
35-
3636
#============================================
3737
# Dumping Browser information for config
3838
#============================================
@@ -54,4 +54,4 @@ RUN useradd -m -u 1000 appuser && \
5454

5555
USER appuser
5656

57-
ENTRYPOINT ["sh", "-c", "java", "-jar", "app.jar"]
57+
ENTRYPOINT ["java", "-jar", "app.jar"]

docker/install-chromium.sh

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/usr/bin/env bash
22
set -e
33

4+
apt-get update -qqy
5+
apt-get -qqy install libnss3-tools
6+
47
# Install Chromium
58
export CHROMIUM_VERSION="145.0.7632.159"
69
export CHROMIUM_DEB_SITE="http://deb.debian.org/debian"
@@ -25,12 +28,6 @@ wget "$URL2" -O /tmp/chromium/chromium.deb
2528
wget "$URL3" -O /tmp/chromium/chromium-l10n.deb
2629
wget "$URL4" -O /tmp/chromium/chromium-driver.deb
2730

28-
echo "deb ${CHROMIUM_DEB_SITE}/ sid main" >/etc/apt/sources.list.d/debian.list
29-
wget -qO- https://ftp-master.debian.org/keys/archive-key-12.asc | gpg --dearmor > /etc/apt/trusted.gpg.d/debian-archive-keyring.gpg
30-
wget -qO- https://ftp-master.debian.org/keys/archive-key-12-security.asc | gpg --dearmor > /etc/apt/trusted.gpg.d/debian-archive-security-keyring.gpg
31-
apt-get update -qqy
32-
apt-get -qqy install libnss3-tools
33-
34-
apt-get -qqyf install /tmp/chromium/chromium-common.deb /tmp/chromium/chromium.deb /tmp/chromium/chromium-l10n.deb /tmp/chromium/chromium-driver.deb
31+
apt-get -yf install /tmp/chromium/chromium-common.deb /tmp/chromium/chromium.deb /tmp/chromium/chromium-l10n.deb /tmp/chromium/chromium-driver.deb
3532

3633
rm -rf /tmp/chromium;

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<dependency>
5252
<groupId>org.seleniumhq.selenium</groupId>
5353
<artifactId>selenium-java</artifactId>
54-
<version>4.40.0</version>
54+
<version>4.41.0</version>
5555
</dependency>
5656

5757
<dependency>

src/main/java/de/denniskniep/safed/SafedCli.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,16 @@ public void run(String... args) throws MalformedURLException {
9292
var report = new Report();
9393
report.setStatus(ScanResultStatus.FAILED);
9494
report.setErrors(List.of("Provided ClientId '" + clientId + "' not found!"));
95+
reports.add(report);
9596
}
9697
}
9798

98-
LOG.info(Serialization.AsPrettyJson(reports));
99+
LOG.debug(Serialization.AsPrettyJson(reports));
100+
if(!LOG.isDebugEnabled()) {
101+
LOG.info(Serialization.AsJsonString(reports));
102+
}
103+
99104
webhookService.sendReports(reports);
100-
LOG.info("Finished assessment");
101105
SpringApplication.exit(applicationContext, () -> 0);
102106
}
103107

src/main/java/de/denniskniep/safed/common/assessment/Assessment.java

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public abstract class Assessment<T extends Scanner, C extends AppConfig> {
2626

2727
private ScanResult firstScanSuccess;
2828
private ScanResult secondScanSuccess;
29-
private ScanResult thirdScanSuccess;
30-
private ScanResult fourthScanFailure;
29+
private ScanResult isVulnerableScan;
30+
private ScanResult isOkScan;
3131

3232
public Assessment(T successScanner, T failureScanner) {
3333
this.successScanner = successScanner;
@@ -54,93 +54,143 @@ public Report run(String clientId, C scannerConfig) {
5454
var start = Instant.now();
5555
validate(scannerConfig);
5656

57+
try {
58+
return runInternal(clientId, scannerConfig);
59+
}catch (Exception e){
60+
LOG.error("Unexpected error:", e);
61+
var duration = Duration.between(start, Instant.now());
62+
ReportBuilder reportBuilder = new ReportBuilder(clientId, duration.toMillis() , firstScanSuccess, secondScanSuccess, isVulnerableScan, isOkScan, new HashMap<>(), List.of(e.getMessage()));
63+
return reportBuilder.Build();
64+
}
65+
}
66+
67+
private Report runInternal(String clientId, C scannerConfig) {
68+
var start = Instant.now();
69+
5770
List<String> errors = new ArrayList<>();
58-
LOG.info("Start initial tests");
71+
LOG.info("Start first baseline scan");
5972
// First scan with successful login
6073
firstScanSuccess = runScan(scannerConfig, successScanner);
74+
75+
LOG.info("Start second baseline scan");
6176
// Second scan with successful login, but maybe changes in the page content from login to login
6277
secondScanSuccess = runScan(scannerConfig, successScanner);
63-
// Third scan with successful login - means VULNERABLE
64-
thirdScanSuccess = runScan(scannerConfig, successScanner);
65-
// Fourth scan with failed login - means OK
66-
fourthScanFailure = runScan(scannerConfig, failureScanner);
6778

79+
LOG.info("Start test scan - proof ok");
80+
// scan with failure - means OK (not vulnerable)
81+
isOkScan = runScan(scannerConfig, failureScanner);
82+
83+
// Dynamically adopt Verification Strategy
84+
List<String> okVerifications = isOkScan.getVerificationStrategies(ScanResultStatus.OK);
85+
List<String> vulnVerifications = isOkScan.getVerificationStrategies(ScanResultStatus.VULNERABLE);
86+
if(!vulnVerifications.isEmpty() && !okVerifications.isEmpty()){
87+
// a significant drift in the response is expected, therefore verifications should report OK
88+
// if not we remove all verifications that are not reporting OK!
89+
scannerConfig = (C)scannerConfig.deepCopy();
90+
scannerConfig.setVerificationStrategies(okVerifications);
91+
LOG.info("Restart test scan - proof ok (Adopted Verification Strategy)");
92+
isOkScan = runScan(scannerConfig, failureScanner);
93+
}
94+
95+
LOG.info("Start test scan - proof vulnerable");
96+
// Scan with success - means VULNERABLE
97+
isVulnerableScan = runScan(scannerConfig, successScanner);
6898

69-
// For a manipulated OIDCResponse we expect a significant drift in the response
70-
// The third scan with a successful login does not have that drift on purpose!
99+
// For a malicious scan, we expect a significant drift in the response
100+
// The successScanner does not have that drift on purpose!
71101
// As no error occur and the user is normally logged in, we expect that to be classified as VULNERABLE
72-
if (thirdScanSuccess.getStatus() == ScanResultStatus.OK) {
73-
errors.add("Third scan must always be classified as VULNERABLE!");
102+
if (isVulnerableScan.getStatus() == ScanResultStatus.OK) {
103+
var msg = "Fourth scan must always be classified as VULNERABLE!";
104+
LOG.warn(msg);
105+
errors.add(msg);
74106
}
75107

76-
// The fourth scan with a failing login should have a significant drift in the response
108+
// For a malicious scan, we expect a significant drift in the response
109+
// The failureScanner should have a significant drift in the response
77110
// Because an error occur and the user is not logged in, we expect that to be classified as OK
78-
if (fourthScanFailure.getStatus() == ScanResultStatus.VULNERABLE) {
79-
// For a manipulated OIDCResponse we expect a significant drift in the response
80-
// The third scan with positive login does not have that on purpose!
81-
errors.add("Fourth scan must always be classified as OK!");
111+
if (isOkScan.getStatus() == ScanResultStatus.VULNERABLE) {
112+
var msg = "Third scan must always be classified as OK!";
113+
LOG.warn(msg);
114+
errors.add(msg);
82115
}
83116

117+
LOG.info("Finished baseline and test scans");
84118

85-
LOG.info("End initial tests");
86119
var scanResults = new HashMap<String, ScanResult>();
87120
for (var scanner : scanners) {
88121
if (scannerConfig.getScanners() != null && !scannerConfig.getScanners().contains(scanner.getClass().getSimpleName())) {
89122
LOG.info("Skip scanning with {}", scanner.getClass().getSimpleName());
90123
continue;
91124
}
92125

93-
LOG.info("Start scanning with {}", scanner.getClass().getSimpleName());
94-
var scanResult = runScan(scannerConfig, scanner);
95-
scanResults.put(scanner.getClass().getSimpleName(), scanResult);
96-
LOG.trace("Scanner {} finished with status: {}.\nFollowing evidences collected:\n{}", scanner.getClass().getSimpleName(), scanResult.getStatus(), String.join("\n", scanResult.getEvidences()));
126+
LOG.debug("Start scanning with {}", scanner.getClass().getSimpleName());
127+
try{
128+
var scanResult = runScan(scannerConfig, scanner);
129+
scanResults.put(scanner.getClass().getSimpleName(), scanResult);
130+
LOG.info("ClientId: {}; Status: {}; Scanner: {};", clientId, scanResult.getStatus(), scanner.getClass().getSimpleName());
131+
}catch(Exception e){
132+
var scanResult = ScanResult.failed(List.of(e.getMessage()));
133+
LOG.error("ClientId: {}; Status: {}; Scanner: {};", clientId, scanResult.getStatus(), scanner.getClass().getSimpleName());
134+
scanResults.put(scanner.getClass().getSimpleName(), scanResult);
135+
}
97136
}
98-
99-
var duration = Duration.between(start, Instant.now());
100-
ReportBuilder reportBuilder = new ReportBuilder(clientId, duration.toMillis() , firstScanSuccess, secondScanSuccess, thirdScanSuccess, fourthScanFailure, scanResults, errors);
101-
return reportBuilder.Build();
137+
var duration = Duration.between(start, Instant.now());
138+
ReportBuilder reportBuilder = new ReportBuilder(clientId, duration.toMillis() , firstScanSuccess, secondScanSuccess, isVulnerableScan, isOkScan, scanResults, errors);
139+
var report = reportBuilder.Build();
140+
LOG.info("ClientId: {}; Status: {}; Finished Assessment ", report.getClientId(), report.getStatus());
141+
return report;
102142
}
103143

104144
private ScanResult runScan(C inputScannerConfig, T scanner) {
105-
scanner.init(firstScanSuccess, secondScanSuccess, thirdScanSuccess, fourthScanFailure);
145+
scanner.init(firstScanSuccess, secondScanSuccess, isVulnerableScan, isOkScan);
106146

107147
C scannerConfig = (C)scanner.getScannerConfig(inputScannerConfig.deepCopy());
108148

109149
AuthResult authResult = scan(scannerConfig, scanner);
110150

111151
// All VerificationStrategies are used to gather infos
112152
var allVerificationStrategies = createVerificationStrategy(scanResultVerificationStrategies.keySet());
113-
List<String> infos = allVerificationStrategies.extractInfos(authResult);
153+
var infos = extractInfos(allVerificationStrategies, authResult);
114154

115155
if (firstScanSuccess == null || secondScanSuccess == null) {
116-
return new ScanResult(authResult, ScanResultStatus.OK, infos);
156+
return ScanResult.ok(authResult, infos);
117157
}
118158

119159
var selectedVerificationStrategies = createVerificationStrategy(scannerConfig.getVerificationStrategies());
120-
var scanResult = selectedVerificationStrategies.evaluateScanResult(firstScanSuccess.getAuthResult(), secondScanSuccess.getAuthResult(), authResult);
121-
var infosAndEvidences = new ArrayList<>(infos);
122-
infosAndEvidences.addAll(scanResult.getEvidences());
123-
return new ScanResult(scanResult.getAuthResult(), scanResult.getStatus(), infosAndEvidences);
160+
var verifications = evaluate(selectedVerificationStrategies, firstScanSuccess.getAuthResult(), secondScanSuccess.getAuthResult(), authResult);
161+
return new ScanResult(authResult, verifications, infos);
162+
}
163+
164+
private Map<String, List<String>> extractInfos(List<ScanResultVerificationStrategy> verifications, AuthResult scanAuthResult) {
165+
var infos = new HashMap<String, List<String>>();
166+
for(var verificationStrategy : verifications){
167+
infos.put(verificationStrategy.getClass().getSimpleName(), verificationStrategy.extractInfos(scanAuthResult));
168+
}
169+
return infos;
170+
}
171+
172+
public Map<String, VerificationResult> evaluate(List<ScanResultVerificationStrategy> verifications, AuthResult firstPositiveAuthResult, AuthResult secondPositiveAuthResult, AuthResult authResult) {
173+
var results = new HashMap<String, VerificationResult>();
174+
for(var verificationStrategy : verifications){
175+
var result = verificationStrategy.evaluateScanResult(firstPositiveAuthResult, secondPositiveAuthResult, authResult);
176+
results.put(verificationStrategy.getClass().getSimpleName(), result);
177+
}
178+
return results;
124179
}
125180

126181
protected abstract AuthResult scan(C scannerConfig, T scanner);
127182

128-
private ScanResultVerificationStrategy createVerificationStrategy(Collection<String> verificationStrategyNames) {
183+
private List<ScanResultVerificationStrategy> createVerificationStrategy(Collection<String> verificationStrategyNames) {
129184
if (verificationStrategyNames == null || verificationStrategyNames.isEmpty()) {
130-
verificationStrategyNames = List.of(
131-
DiffVerification.class.getSimpleName(),
132-
UrlAndStatusCodeVerification.class.getSimpleName(),
133-
CookieVerification.class.getSimpleName()
134-
);
185+
verificationStrategyNames = scanResultVerificationStrategies.keySet();
135186
}
136187

137188
List<ScanResultVerificationStrategy> verificationStrategies = new ArrayList<>();
138189
for (var name : verificationStrategyNames) {
139190
verificationStrategies.add(findVerificationStrategyByName(name));
140191
}
141192

142-
return new AnyMatchVerification(verificationStrategies);
143-
193+
return verificationStrategies;
144194
}
145195

146196
private ScanResultVerificationStrategy findVerificationStrategyByName(String name) {

src/main/java/de/denniskniep/safed/common/auth/browser/AuthenticationLog.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package de.denniskniep.safed.common.auth.browser;
22

3+
import de.denniskniep.safed.common.auth.browser.bidi.ResponseDataDetails;
34
import org.apache.commons.lang3.StringUtils;
4-
import org.openqa.selenium.bidi.network.ResponseDetails;
55

66
import java.util.ArrayList;
77
import java.util.List;
8+
import java.util.Optional;
9+
import java.util.function.Predicate;
810

911
public class AuthenticationLog {
1012

@@ -14,19 +16,19 @@ public List<RequestResponse> getTraffic() {
1416
return traffic;
1517
}
1618

17-
public RequestResponse add(ResponseDetails responseDetails){
18-
return add("", responseDetails);
19+
public RequestResponse add(ResponseDataDetails responseDataDetails){
20+
return add("", responseDataDetails);
1921
}
2022

2123
public void addAll(String context, List<RequestResponse> requestResponses){
2224
for (RequestResponse r : requestResponses) {
23-
RequestResponse requestResponse = new RequestResponse(r.created(), context, r.responseDetails());
25+
RequestResponse requestResponse = new RequestResponse(r.created(), context, r.responseDataDetails());
2426
traffic.add(requestResponse);
2527
}
2628
}
2729

28-
public RequestResponse add(String context, ResponseDetails responseDetails){
29-
RequestResponse requestResponse = new RequestResponse(context, responseDetails);
30+
public RequestResponse add(String context, ResponseDataDetails responseDataDetails){
31+
RequestResponse requestResponse = new RequestResponse(context, responseDataDetails);
3032
traffic.add(requestResponse);
3133
return requestResponse;
3234
}
@@ -45,8 +47,30 @@ public void clearTrafficAfter(String requestId) {
4547
}
4648
}
4749

50+
public Optional<RequestResponse> find(Predicate<RequestResponse> findCondition){
51+
return findInternal(Optional.empty(), findCondition);
52+
}
53+
54+
public Optional<RequestResponse> findStartingAt(String startAtRequestId, Predicate<RequestResponse> findCondition){
55+
return findInternal(Optional.of(startAtRequestId), findCondition);
56+
}
57+
58+
private Optional<RequestResponse> findInternal(Optional<String> startAtRequestId, Predicate<RequestResponse> findCondition){
59+
var execFind = false;
60+
for (RequestResponse t : List.copyOf(traffic)) {
61+
if (startAtRequestId.isEmpty() || StringUtils.equals(t.getRequest().getRequestId(), startAtRequestId.get())){
62+
execFind = true;
63+
}
64+
65+
if(execFind && findCondition.test(t)){
66+
return Optional.of(t);
67+
}
68+
}
69+
return Optional.empty();
70+
}
71+
4872
public String asShortLogList(){
49-
return String.join("\n",getTraffic().stream().map(RequestResponse::asShortLog).toList());
73+
return String.join("\n",getTraffic().stream().map(RequestResponse::asShortLog).toList());
5074
}
5175
}
5276

0 commit comments

Comments
 (0)