DryRun Security Shows Why Context Matters in Java Spring Application Security
Welcome to the fourth installment of our head-to-head series, where we take on a Java Spring Boot application to see how DryRun Security stacks up against traditional pattern-matching SAST tools. (Want to see past showdowns? Check these: Ruby on Rails, Python/Django, and .NET C#)
We tested DryRun Security against Snyk, GitHub CodeQL, SonarQube, and Semgrep on a Java Spring Boot app packed with intentional vulnerabilities—ranging from SQL Injection and Cross-Site Scripting (XSS) to deeper logic flaws like Insecure Direct Object Reference (IDOR) and broken authentication checks. Below is how each tool scored; DryRun Security emerged as the only tool to catch all of the major vulnerabilities, however there were two logic flaws that were missed by all SAST vendors, including DryRun Security, and for the sake of transparency in these benchmarks we have included those issues in our comparison as well. These are both addressable with our natural language code policies (NLCP), but all tools tested were used in their out-of-the-box configuration—even us!
{{table6}}
(✓ = tool detected the issue, ✗ = missed the issue)
Key Observation: The older scanners flagged the “obvious” bugs (injections, SSRF, XSS), but only DryRun Security caught multiple IDOR flaws as well as an insecure authentication check—where a malicious request can skip password verification entirely.
A Deeper Dive Into The Results
1. SSRF in User-Supplied restTemplate
Call
Vulnerable Code Snippet:
@GetMapping("/client-request")
public ResponseEntity<String> fetchUrl(@RequestParam String targetUrl) {
String data = restTemplate.getForObject(targetUrl, String.class);
return ResponseEntity.ok(data);
}
- DryRun Security found SSRF immediately, pointing out that user input flows into getForObject with no validation.
- Snyk, CodeQL, Semgrep, SonarQube: All identified SSRF as a high-severity issue (though SonarQube listed it as a “Security Hotspot”).
Why It Matters: Attackers can coerce your app server to make requests to internal services, cloud metadata endpoints, or other restricted URLs, potentially disclosing sensitive data or enabling pivot attacks.
2. Cross-Site Scripting (XSS) in HTML Response
Vulnerable Code Snippet:
@GetMapping("/content")
public ResponseEntity<String> xssVuln(@RequestParam String input) {
return ResponseEntity.ok("<html><body>" + input + "</body></html>");
}
- Snyk, CodeQL, DryRun Security, and Semgrep: Flagged this as XSS. DryRun and others recognized that concatenating unescaped user input into HTML is dangerous.
- SonarQube: Missed all XSS issues.
@GetMapping("/verify-email")
public ResponseEntity<String> verify(@RequestParam String userId, @RequestParam
long timestamp) {
long now = System.currentTimeMillis();
if (now - timestamp < 300000) {
return ResponseEntity.ok("Email verified for user: " + userId);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Token
expired");
}
- Snyk, CodeQL, DryRun Security, and Semgrep: Flagged this as XSS. DryRun and others recognized that concatenating unescaped user input into HTML is dangerous.
- SonarQube: Missed all XSS issues.
@GetMapping("/client-request")
public ResponseEntity<String> fetchUrl(@RequestParam String targetUrl) {
String response = restTemplate.getForObject(targetUrl, String.class);
return ResponseEntity.ok(response);
}
- SonarQube, CodeQL, DryRun Security, and Semgrep: Missed that the SSRF issue could also potentially lead to HTML Injection if the targetUrl contains HTML and the content type returns the content as
text/html
. - Snyk: As a pleasant surprise, Snyk was the only tool that caught this potential XSS issue.
Why It Matters: XSS allows attackers to execute malicious scripts in a victim’s browser. If your endpoint returns user-supplied text as HTML, you must sanitize/encode it.
3. SQL Injection (SQLi) in JdbcTemplate
Queries
Vulnerable Code Snippet:
String query = "SELECT * FROM users WHERE username = '" + username + "'";
List<?> users = jdbcTemplate.queryForList(query);
- All Tools: All found it. String concatenation in a query is a well-known no-no and this is a blatantly vulnerable query. We expected that all tools would find this flaw.
String userQuery = "SELECT COUNT(*) FROM users WHERE username = '" + username +
"'";
Integer userCount = jdbcTemplate.queryForObject(userQuery, Integer.class);
- All Tools: All found it. String concatenation in a query is a well-known no-no and this is a blatantly vulnerable query. We expected that all tools would find this flaw.
Why It Matters: SQLi is a top-tier risk (OWASP #3). Attackers can exfiltrate data, modify records, or drop entire tables. Always use parameterized queries rather than string concatenation.
4. Insecure Direct Object Reference (IDOR)
Vulnerable Code Snippet (from getUserData
):
@GetMapping("/user-data/{userId}")
public ResponseEntity<String> getUserData(@PathVariable int userId,
@RequestParam int currentUserId, @RequestParam boolean isAdmin) {
if (userId == currentUserId || isAdmin) {
return ResponseEntity.ok("Here is the user data");
}
return new ResponseEntity<>("Unauthorized", HttpStatus.UNAUTHORIZED);
}
Flaw: isAdmin
is client-controlled. Anyone can send ?isAdmin=true
to access another user’s data and also because userId, currentUserId, and isAdmin are all user controllable values - both conditional checks can be bypassed.
- DryRun Security: Flagged this logical flaw as an IDOR. All other tools missed it.
- All other tools: Legacy SAST missed this. These more traditional SAST tools rely on pattern-based checks. They are hamstrung by their underlying technology and unable to interpret custom logic (like trusting a request param for admin status) and perform real time evaluation of the conditional logic, its intent, and its efficacy.
@GetMapping("/verify-email")
public ResponseEntity<String> verify(@RequestParam String userId, @RequestParam
long timestamp) {
long now = System.currentTimeMillis();
if (now - timestamp < 300000) {
return ResponseEntity.ok("Email verified for user: " + userId);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Token
expired");
}
Flaw: userId
is client-controlled. While this code does not show updating a record using the userId
, in deeper analysis it is obvious that the intention will be to utilize the userId
in order to update the record.
- DryRun Security: Flagged this logical flaw as an IDOR. All other tools missed it.
- Snyk, CodeQL, SonarQube, and Semgrep: Again, all traditional SAST tools missed this and are unable to interpret what code is doing. They can only search for patterns in a predefined list of patterns. Logical flaws, IDOR, and any vulnerability that requires interpreting things like intention and effect to detect will not be discovered.
Why It Matters: Broken access control is consistently a top cause of data breaches and OWASP’s #1 risk. Relying on user input for privilege checks is a big red flag.
5. Broken Authentication Logic (Missing Password Check)
Vulnerable Code Snippet:
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam String username,
@RequestParam String password) {
String userQuery = "SELECT COUNT(*) FROM users WHERE username = '" +
username + "'";
Integer userCount = jdbcTemplate.queryForObject(userQuery, Integer.class);
if (userCount == null || userCount == 0) {
return ResponseEntity.badRequest().body("Invalid username");
}
// There's no validation for the password at all!
return ResponseEntity.badRequest().body("Invalid credentials");
}
- DryRun Security: Flagged it as a “weak authentication” logic flaw—password is never verified.
- Others: No mention. As previously mentioned in this article, CodeQL, Snyk, Semgrep, and SonarQube check for patterns or rules and do not understand intent or behavior so logic flaws are not within their capabilities.
Why It Matters: Broken authentication can let attackers bypass login with just a username (or if a future change modifies the return statements). Omitting password checks is catastrophic for security. In this case, the code is not “feature-complete” in that it does not set a session or otherwise use a mechanism that authenticates a user. However, DRS understands the intention of the function and that future changes will need to take into account the missing password check.
6. Invalid Token Validation Logic
Vulnerable Code Snippet:
@GetMapping("/secure-download")
public ResponseEntity<String> download(@RequestParam String token) {
if (token.length() > 10) {
return ResponseEntity.ok("Here's your file");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid
token");
}
- DryRun Security: Missed it. Similar to the broken email verification logic check, the token check here is insufficient. It does not ensure the format or issuance of the token is valid.
Others: No mention. CodeQL, Snyk, Semgrep, and SonarQube cannot generally detect logic issues.
Why It Matters: Broken logic for validating tokens as done here is something that attackers can easily identify and bypass. Insecure Design flaws (OWASP #4) are some of the most difficult to detect by SAST tooling.
7. Broken Email Verification Logic
Vulnerable Code Snippet:
@GetMapping("/verify-email")
public ResponseEntity<String> verify(@RequestParam String userId, @RequestParam
long timestamp) {
long now = System.currentTimeMillis();
if (now - timestamp < 300000) {
return ResponseEntity.ok("Email verified for user: " + userId);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Token
expired");
}
- DryRun Security: Although DryRun Security did mention IDOR in this function due to the use of a
userId
parameter without validation, we had also expected it would catch the weak token value and validation. The problem here is that the token is not unique with high-entropy. Instead, it represents time in milliseconds. So long as it has not been longer than 5 minutes since the “token” was generated, it will pass the conditional check. Granted, the function is not really doing anything but we would have liked to see a mention of this bad pattern to account for future changes to that code. - Others: No mention. CodeQL, Snyk, Semgrep, and SonarQube cannot generally detect logic issues.
Why It Matters: Broken validation logic here is not going to protect the application from verifying a user. The use of a timestamp in this way should be caught.
Why DryRun Security Leads the Pack
- Contextual Analysis: DryRun Security goes beyond searching for known anti-patterns. It examines how code should behave (e.g., verifying passwords, validating admin privileges) and flags omissions or flawed logic.
- Accurate + Comprehensive: DryRun found all the “standard” vulnerabilities and the deeper logic flaws. This prevents dangerous false negatives.
- No Extra Tuning: We used the default install for every tool. DryRun’s out-of-the-box coverage was stronger and included logic flaw detection.
Final Thoughts & Next Steps
Don’t settle for “clean scans” that ignore your most critical risks—like authorization bypass and authentication mishaps. As shown, DryRun Security catches both the classic vulnerabilities and the nuanced logic bugs that can lead to real-world breaches—flaws that pattern-matching tools consistently miss.
While DryRun Security clearly outperformed all other tools significantly AND was the only tool that would have prevented breaches resulting from Insecure Direct Object Flaws (IDOR) flaws, we still missed a couple of lesser severity flaws in feature-incomplete functions. Those flaws could lead to significant issues once those functions were considered code-complete. We left these vulnerabilities and the results where all tools, including DryRun Security, missed them. We did this because we felt it was important for us to figure out our gaps and work to fix them as well as provide an honest and transparent comparison to the public.
What makes DryRun even more powerful is our Natural Language Code Policies (NLCPs). NLCPs allow teams to write custom security checks in plain English, enabling detection of organization-specific logic and validation issues that go far beyond what static rules can catch. In fact, many of our customers are already using NLCPs today to flag risky code patterns unique to their applications—and catching things that traditional scanners would never see.
AND writing custom checks in plain English means there’s no scripting required—it’s approachable for everyone from AppSec engineers to development leads, and even product managers!
Follow DryRun Security on LinkedIn and X (Twitter) for more dev-focused security tips, and be sure to download our free research paper on authorization flaws to learn how to prevent IDORs, broken access controls, and other logic-based issues.