-
Notifications
You must be signed in to change notification settings - Fork 48
Add Contact Feedback Form with Security Flows and CAPTCHA Validation #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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/ | ||
|
|
| 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(); | ||
| ImageIO.write(captchaImage, "jpg", out); | ||
Check noticeCode 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 noticeCode 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 noticeCode 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
|
||
| } | ||
| } | ||
| 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 noticeCode 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 warningCode 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 failureCode 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 noticeCode 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 noticeCode 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 noticeCode 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 noticeCode 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 noticeCode 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 noticeCode 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 failureCode 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 failureCode 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 failureCode 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 failureCode 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(); | ||
| } | ||
| } | ||
Check notice
Code scanning / SonarQube
Exceptions should not be thrown from servlet methods Low