Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Java CI

on:
push:
branches:
- main
- 'feature/**'
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven

- name: Build with Maven
run: mvn clean verify

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: target/surefire-reports/

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: target/site/jacoco/

5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/demo/security/servlet/CaptchaServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package demo.security.servlet;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Properties;

@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

// Configure CAPTCHA
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width", "200");
properties.setProperty("kaptcha.image.height", "50");
properties.setProperty("kaptcha.textproducer.char.length", "5");
properties.setProperty("kaptcha.textproducer.font.names", "Arial");

DefaultKaptcha captchaProducer = new DefaultKaptcha();
captchaProducer.setConfig(new Config(properties));

// Generate CAPTCHA
String captchaText = captchaProducer.createText();

// Store in session for validation
request.getSession().setAttribute("captcha", captchaText);

// Create image
BufferedImage captchaImage = captchaProducer.createImage(captchaText);

// Send image response
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "getOutputStream": IOException. See more on SonarQube
ImageIO.write(captchaImage, "jpg", out);

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "write": IOException. See more on SonarQube
out.flush();

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "flush": IOException. See more on SonarQube
out.close();

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "close": IOException. See more on SonarQube
}
}
168 changes: 168 additions & 0 deletions src/main/java/demo/security/servlet/ContactFeedbackServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package demo.security.servlet;

import org.apache.commons.codec.digest.DigestUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@WebServlet("/contact-feedback")
public class ContactFeedbackServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String FEEDBACK_DIR = "/tmp/feedback/";

@Override
public void init() throws ServletException {
super.init();
// Ensure feedback directory exists
new File(FEEDBACK_DIR).mkdirs();
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

// Retrieve feedback by ID with SQL Injection vulnerability
String feedbackId = request.getParameter("id");
if (feedbackId != null) {
try {
List<String> feedbacks = getFeedbackById(feedbackId);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h2>Feedback Results</h2>");
for (String feedback : feedbacks) {
// XSS vulnerability - no escaping
out.println("<p>" + feedback + "</p>");
}
out.println("</body></html>");
out.close();
} catch (Exception e) {
throw new ServletException(e);

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the "ServletException" thrown here in a "try/catch" block. See more on SonarQube
}
}

// View feedback file with Path Traversal vulnerability
String filename = request.getParameter("file");
if (filename != null) {
// Path traversal vulnerability
File file = new File(FEEDBACK_DIR + filename);
if (file.exists()) {

Check warning

Code scanning / SonarQube

Accessing files should not lead to filesystem oracle attacks Medium

Change this code to not construct the path from user-controlled data. See more on SonarQube
response.setContentType("text/plain");
Files.copy(file.toPath(), response.getOutputStream());

Check failure

Code scanning / SonarQube

I/O function calls should not be vulnerable to path injection attacks Critical

Change this code to not construct the path from user-controlled data. See more on SonarQube

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "getOutputStream": IOException. See more on SonarQube

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "copy": IOException. See more on SonarQube
} else {
response.sendError(404, "File not found");

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "sendError": IOException. See more on SonarQube
}
}
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

HttpSession session = request.getSession();

// Optional CAPTCHA validation
String captchaEnabled = request.getParameter("captcha_enabled");
if ("true".equals(captchaEnabled)) {
String captchaInput = request.getParameter("captcha");
String sessionCaptcha = (String) session.getAttribute("captcha");

// Weak comparison - timing attack vulnerability
if (!captchaInput.equals(sessionCaptcha)) {
response.sendError(400, "Invalid CAPTCHA");

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "sendError": IOException. See more on SonarQube
return;
}
}

// Get form parameters
String name = request.getParameter("name");
String email = request.getParameter("email");
String subject = request.getParameter("subject");
String message = request.getParameter("message");
String priority = request.getParameter("priority");

// Weak random token generation
Random random = new Random();
String token = String.valueOf(random.nextInt(1000000));

// Weak MD5 hashing for email
String emailHash = DigestUtils.md5Hex(email);

// Store feedback in database with SQL Injection
try {
saveFeedback(name, email, subject, message, priority, token, emailHash);
} catch (Exception e) {
throw new ServletException("Error saving feedback", e);

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the "ServletException" thrown here in a "try/catch" block. See more on SonarQube
}

// Save feedback to file with predictable name
String filename = "feedback_" + token + ".txt";
File feedbackFile = new File(FEEDBACK_DIR + filename);

// Write sensitive data to file with weak permissions
try (PrintWriter fileWriter = new PrintWriter(new FileWriter(feedbackFile))) {
fileWriter.println("Name: " + name);
fileWriter.println("Email: " + email);
fileWriter.println("Subject: " + subject);
fileWriter.println("Message: " + message);
fileWriter.println("Priority: " + priority);
fileWriter.println("Token: " + token);
fileWriter.println("Email Hash: " + emailHash);
}

// Log sensitive information
System.out.println("Feedback submitted by: " + email + " with token: " + token);

// Redirect with token in URL
response.sendRedirect("/contact-feedback?success=true&token=" + token);

Check notice

Code scanning / SonarQube

Exceptions should not be thrown from servlet methods Low

Handle the following exception that could be thrown by "sendRedirect": IOException. See more on SonarQube
}

private List<String> getFeedbackById(String feedbackId) throws Exception {
// SQL Injection vulnerability
Connection connection = DriverManager.getConnection(
"myJDBCUrl", "myJDBCUser", "myJDBCPass");

Check failure

Code scanning / SonarQube

Credentials should not be hard-coded Critical

Revoke and change this password, as it is compromised. See more on SonarQube

String query = "SELECT * FROM feedback WHERE id = " + feedbackId;
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);

Check failure

Code scanning / SonarQube

Database queries should not be vulnerable to injection attacks Critical

Change this code to not construct SQL queries directly from user-controlled data. See more on SonarQube

List<String> feedbacks = new ArrayList<>();
while (resultSet.next()) {
String feedback = "Name: " + resultSet.getString("name") +
", Subject: " + resultSet.getString("subject") +
", Message: " + resultSet.getString("message");
feedbacks.add(feedback);
}

connection.close();
return feedbacks;
}

private void saveFeedback(String name, String email, String subject,
String message, String priority, String token, String emailHash) throws Exception {
// SQL Injection vulnerability
Connection connection = DriverManager.getConnection(
"myJDBCUrl", "myJDBCUser", "myJDBCPass");

Check failure

Code scanning / SonarQube

Credentials should not be hard-coded Critical

Revoke and change this password, as it is compromised. See more on SonarQube

String query = "INSERT INTO feedback (name, email, subject, message, priority, token, email_hash) " +
"VALUES ('" + name + "', '" + email + "', '" + subject + "', '" +
message + "', '" + priority + "', '" + token + "', '" + emailHash + "')";

Statement statement = connection.createStatement();
statement.executeUpdate(query);

Check failure

Code scanning / SonarQube

Database queries should not be vulnerable to injection attacks Critical

Change this code to not construct SQL queries directly from user-controlled data. See more on SonarQube
connection.close();
}
}
Loading