diff --git a/src/main/java/com/microfocus/example/api/controllers/ApiSiteController.java b/src/main/java/com/microfocus/example/api/controllers/ApiSiteController.java index 762bde18..e3abec84 100644 --- a/src/main/java/com/microfocus/example/api/controllers/ApiSiteController.java +++ b/src/main/java/com/microfocus/example/api/controllers/ApiSiteController.java @@ -239,8 +239,8 @@ public ResponseEntity signIn(@Valid @RequestBody LoginRequest login .collect(Collectors.toList()); Cookie jwtTokenCookie = new Cookie("jwtToken", jwt); - jwtTokenCookie.setSecure(false); - jwtTokenCookie.setHttpOnly(false); + jwtTokenCookie.setSecure(true); + jwtTokenCookie.setHttpOnly(true); response.addCookie(jwtTokenCookie); RefreshToken refreshToken = refreshTokenService.createRefreshToken(user.getId()); @@ -287,5 +287,3 @@ public ResponseEntity refreshToken(@Valid @RequestBody Ref } } - - diff --git a/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java b/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java index 98f61c68..38dea249 100644 --- a/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java +++ b/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java @@ -128,7 +128,9 @@ protected void configure(HttpSecurity httpSecurity) throws Exception { .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //.and().httpBasic().authenticationEntryPoint(basicAuthenticationEntryPoint) .and().exceptionHandling().accessDeniedHandler(apiAccessDeniedHandler) - .and().csrf().disable(); + .and().csrf().disable() + .headers() + .contentSecurityPolicy("default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"); httpSecurity.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); @@ -145,7 +147,7 @@ protected void configure(HttpSecurity httpSecurity) throws Exception { if (activeProfile.contains("dev")) { log.info("Running development profile"); httpSecurity.csrf().disable(); - httpSecurity.headers().frameOptions().disable(); + // Frame options protection enabled (using Spring Security defaults) httpSecurity.cors().disable(); httpSecurity.headers().xssProtection().disable(); } diff --git a/src/main/java/com/microfocus/example/config/handlers/ApiAccessDeniedHandler.java b/src/main/java/com/microfocus/example/config/handlers/ApiAccessDeniedHandler.java index 3e915024..dd3dbc1d 100644 --- a/src/main/java/com/microfocus/example/config/handlers/ApiAccessDeniedHandler.java +++ b/src/main/java/com/microfocus/example/config/handlers/ApiAccessDeniedHandler.java @@ -50,7 +50,7 @@ public void handle(HttpServletRequest request, HttpServletResponse response, Acc throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); ArrayList errors = new ArrayList<>(); - errors.add(ex.getLocalizedMessage()); + errors.add("Access denied. You do not have permission to access this resource."); ApiStatusResponse apiStatusResponse = new ApiStatusResponse .ApiResponseBuilder() .withSuccess(false) diff --git a/src/main/java/com/microfocus/example/config/handlers/AuthenticationEntryPointJwt.java b/src/main/java/com/microfocus/example/config/handlers/AuthenticationEntryPointJwt.java index 86f5c5d8..a56805e5 100644 --- a/src/main/java/com/microfocus/example/config/handlers/AuthenticationEntryPointJwt.java +++ b/src/main/java/com/microfocus/example/config/handlers/AuthenticationEntryPointJwt.java @@ -53,7 +53,7 @@ public void commence(HttpServletRequest request, HttpServletResponse response, //response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); ArrayList errors = new ArrayList<>(); - errors.add(ex.getLocalizedMessage()); + errors.add("Authentication failed. Please check your credentials."); ApiStatusResponse apiStatusResponse = new ApiStatusResponse .ApiResponseBuilder() .withSuccess(false) diff --git a/src/main/java/com/microfocus/example/config/handlers/CustomAuthenticationSuccessHandler.java b/src/main/java/com/microfocus/example/config/handlers/CustomAuthenticationSuccessHandler.java index 4a5fabb6..b44dbe07 100644 --- a/src/main/java/com/microfocus/example/config/handlers/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/com/microfocus/example/config/handlers/CustomAuthenticationSuccessHandler.java @@ -98,24 +98,49 @@ public static String getTargetUrl(HttpServletRequest request, HttpServletRespons log.debug("No loginReferer; redirecting to users home page"); targetUrl = USER_HOME_URL; } else { - targetUrl = loginReferer; String targetPath = null; try { - targetPath = new URL(targetUrl).getPath(); + URL url = new URL(loginReferer); + // Validate that the URL is from the same host + String requestHost = request.getServerName(); + String refererHost = url.getHost(); + if (refererHost != null && !refererHost.equals(requestHost)) { + log.warn("Referer host mismatch. Expected: " + requestHost + ", Got: " + refererHost); + targetUrl = USER_HOME_URL; + } else { + targetPath = url.getPath(); + targetUrl = targetPath; + if (targetUrl.contains("?")) targetUrl = targetUrl.substring(0, targetUrl.indexOf("?")); + if (targetPath.endsWith("/cart")) { + targetUrl = targetUrl.replace("/cart", "/cart/checkout"); + } else if (targetPath.endsWith("/login")) { + targetUrl = targetUrl.replace("/login", "/user"); + } else if (targetPath.endsWith("/register")) { + targetUrl = targetUrl.replace("/register", "/"); + } else if (targetPath.equals("/")) { + targetUrl = targetUrl + "user"; + } + } } catch (MalformedURLException ex) { log.error(ex.getLocalizedMessage()); + // If URL is malformed, treat as relative path + targetPath = loginReferer; + if (targetPath.startsWith("/")) { + targetUrl = targetPath; + if (targetUrl.contains("?")) targetUrl = targetUrl.substring(0, targetUrl.indexOf("?")); + if (targetPath.endsWith("/cart")) { + targetUrl = targetUrl.replace("/cart", "/cart/checkout"); + } else if (targetPath.endsWith("/login")) { + targetUrl = targetUrl.replace("/login", "/user"); + } else if (targetPath.endsWith("/register")) { + targetUrl = targetUrl.replace("/register", "/"); + } else if (targetPath.equals("/")) { + targetUrl = targetUrl + "user"; + } + } else { + targetUrl = USER_HOME_URL; + } } - if (targetUrl.contains("?")) targetUrl = targetUrl.substring(0, targetUrl.indexOf("?")); - if (targetPath.endsWith("/cart")) { - targetUrl = targetUrl.replace("/cart", "/cart/checkout"); - } else if (targetPath.endsWith("/login")) { - targetUrl = targetUrl.replace("/login", "/user"); - } else if (targetPath.endsWith("/register")) { - targetUrl = targetUrl.replace("/register", "/"); - } else if (targetPath.equals("/")) { - targetUrl = targetUrl + "user"; - } - } } return targetUrl; @@ -154,4 +179,4 @@ protected RedirectStrategy getRedirectStrategy() { protected void setRedirectStrategy(RedirectStrategy redirectStrategy) { this.redirectStrategy = redirectStrategy; } -} \ No newline at end of file +} diff --git a/src/main/java/com/microfocus/example/config/handlers/UrlAuthenticationSuccessHandler.java b/src/main/java/com/microfocus/example/config/handlers/UrlAuthenticationSuccessHandler.java index 3798cff8..46985da8 100644 --- a/src/main/java/com/microfocus/example/config/handlers/UrlAuthenticationSuccessHandler.java +++ b/src/main/java/com/microfocus/example/config/handlers/UrlAuthenticationSuccessHandler.java @@ -85,9 +85,39 @@ protected void handle(HttpServletRequest request, boolean isUser = false; boolean isAdmin = false; - String targetUrl = request.getParameter("referer"); - //if (targetUrl.endsWith("/")) targetUrl = targetUrl.substring(0, targetUrl.length()); - String targetPath = new URL(targetUrl).getPath(); + String refererParam = request.getParameter("referer"); + String targetUrl = "/"; + String targetPath = "/"; + + // Validate referer is a relative path or same-origin URL + if (refererParam != null && !refererParam.isEmpty()) { + try { + if (refererParam.startsWith("/")) { + // Relative path - safe to use + targetPath = refererParam; + targetUrl = refererParam; + } else { + // Absolute URL - validate it's same origin + URL refererUrl = new URL(refererParam); + URL requestUrl = new URL(request.getRequestURL().toString()); + if (refererUrl.getHost().equals(requestUrl.getHost()) && + refererUrl.getProtocol().equals(requestUrl.getProtocol()) && + refererUrl.getPort() == requestUrl.getPort()) { + targetPath = refererUrl.getPath(); + targetUrl = refererParam; + } else { + // External URL - reject and use default + log.warn("Rejecting external referer URL: " + refererParam); + targetPath = "/"; + targetUrl = "/"; + } + } + } catch (Exception e) { + log.warn("Invalid referer URL: " + refererParam, e); + targetPath = "/"; + targetUrl = "/"; + } + } Collection authorities = authentication.getAuthorities(); for (GrantedAuthority grantedAuthority : authorities) { diff --git a/src/main/java/com/microfocus/example/repository/ProductRepository.java b/src/main/java/com/microfocus/example/repository/ProductRepository.java index 62760f83..f00c9b34 100644 --- a/src/main/java/com/microfocus/example/repository/ProductRepository.java +++ b/src/main/java/com/microfocus/example/repository/ProductRepository.java @@ -50,9 +50,9 @@ public int count() { } public List findAll(int offset, int limit) { - String sqlQuery = "select * from products" + - " LIMIT " + limit + " OFFSET " + offset; - return jdbcTemplate.query(sqlQuery, new ProductMapper()); + String sqlQuery = "select * from products LIMIT ? OFFSET ?"; + return jdbcTemplate.query(sqlQuery, new ProductMapper(), limit, offset); + } } public List findAvailable(int offset, int limit) { @@ -91,8 +91,8 @@ public Optional findByCode(String code) { List result = new ArrayList<>(); String query = code.toLowerCase(); String sqlQuery = "SELECT * FROM " + getTableName() + - " WHERE lower(code) = '" + query + "'"; - result = jdbcTemplate.query(sqlQuery, new ProductMapper()); + " WHERE lower(code) = ?"; + result = jdbcTemplate.query(sqlQuery, new ProductMapper(), query); Optional optionalProduct = Optional.empty(); if (!result.isEmpty()) { optionalProduct = Optional.of(result.get(0)); @@ -110,12 +110,13 @@ public List findByKeywords(String keywords, int offset, int limit) { return jdbcTemplate.query(sqlQuery, new ProductMapper()); } - public List findByKeywordsFromProductName(String keywords) { - String query = keywords.toLowerCase(); - String sqlQuery = "SELECT * FROM " + getTableName() + - " WHERE lower(name) LIKE '%" + query + "%' "; - return jdbcTemplate.query(sqlQuery, new ProductMapper()); - } + public List findByKeywordsFromProductName(String keywords) { // L113 + String query = keywords.toLowerCase(); // L114 + String sqlQuery = "SELECT * FROM " + getTableName() + // L115 + " WHERE lower(name) LIKE ?"; // L116 + String likePattern = "%" + query + "%"; // L117 + return jdbcTemplate.query(sqlQuery, new ProductMapper(), likePattern); // L118 + } // L119 public List findAvailableByKeywords(String keywords, int offset, int limit) { String query = keywords.toLowerCase(); @@ -129,10 +130,10 @@ public List findAvailableByKeywords(String keywords, int offset, int li } public List findAvailableByKeywordsFromProductName(String keywords) { - String query = keywords.toLowerCase(); - String sqlQuery = "SELECT * FROM " + getTableName() + - " WHERE available = true AND lower(name) LIKE '%" + query + "%' "; - return jdbcTemplate.query(sqlQuery, new ProductMapper()); + String query = "%" + keywords.toLowerCase() + "%"; + String sqlQuery = "SELECT * FROM " + getTableName() + + " WHERE available = true AND lower(name) LIKE ?"; + return jdbcTemplate.query(sqlQuery, new ProductMapper(), query); } public Product save(Product p) { diff --git a/src/main/java/com/microfocus/example/service/FileSystemStorageService.java b/src/main/java/com/microfocus/example/service/FileSystemStorageService.java index a5a6197b..71c32e23 100644 --- a/src/main/java/com/microfocus/example/service/FileSystemStorageService.java +++ b/src/main/java/com/microfocus/example/service/FileSystemStorageService.java @@ -140,7 +140,11 @@ public Stream loadAll() { @Override public Path load(String filename) { - return rootLocation.resolve(filename); + Path resolvedPath = rootLocation.resolve(filename).normalize().toAbsolutePath(); + if (!resolvedPath.getParent().equals(this.rootLocation.toAbsolutePath())) { + throw new StorageException("Cannot access file outside current directory."); + } + return resolvedPath; } @Override @@ -156,9 +160,12 @@ public Resource loadAsResource(String filename, boolean traverse) { try { Path file = null; if (traverse) { - file = Paths.get(filename); + file = rootLocation.resolve(filename).normalize(); + if (!file.startsWith(rootLocation.toAbsolutePath().normalize())) { + throw new StorageException("Cannot access file outside current directory."); + } } else { - file = load(filename); + file = load(filename); } Resource resource = new UrlResource(file.toUri()); @@ -173,7 +180,7 @@ public Resource loadAsResource(String filename, boolean traverse) { } catch (MalformedURLException e) { throw new StorageFileNotFoundException("Could not read file: " + filename, e); - } + } } @Override diff --git a/src/main/java/com/microfocus/example/utils/UserUtils.java b/src/main/java/com/microfocus/example/utils/UserUtils.java index 30f834a0..99e4b6be 100644 --- a/src/main/java/com/microfocus/example/utils/UserUtils.java +++ b/src/main/java/com/microfocus/example/utils/UserUtils.java @@ -57,13 +57,13 @@ public static void writeUser(String username, String password) throws IOExceptio jGenerator.writeStartObject(); jGenerator.writeFieldName("username"); - jGenerator.writeRawValue("\"" + username + "\""); + jGenerator.writeString(username); jGenerator.writeFieldName("password"); - jGenerator.writeRawValue("\"" + password + "\""); + jGenerator.writeString(password); jGenerator.writeFieldName("role"); - jGenerator.writeRawValue("\"default\""); + jGenerator.writeString("default"); jGenerator.writeEndObject(); @@ -78,7 +78,9 @@ public static void registerUser(String firstName, String lastName, String email) File dataFile = new File(getFilePath(NEWSLETTER_USER_FILE)); if (dataFile.exists()) { - jsonArray = (JSONArray) jsonParser.parse(new FileReader(getFilePath(NEWSLETTER_USER_FILE))); + try (FileReader reader = new FileReader(getFilePath(NEWSLETTER_USER_FILE))) { + jsonArray = (JSONArray) jsonParser.parse(reader); + } } else { dataFile.createNewFile(); log.debug("Created: " + getFilePath(NEWSLETTER_USER_FILE)); @@ -94,13 +96,13 @@ public static void registerUser(String firstName, String lastName, String email) jGenerator.writeStartObject(); JSONObject person = (JSONObject) jsonObject; jGenerator.writeFieldName("firstName"); - jGenerator.writeRawValue("\"" + (String) person.get("firstName") + "\""); + jGenerator.writeString((String) person.get("firstName")); jGenerator.writeFieldName("lastName"); - jGenerator.writeRawValue("\"" + (String) person.get("lastName") + "\""); + jGenerator.writeString((String) person.get("lastName")); jGenerator.writeFieldName("email"); - jGenerator.writeRawValue("\"" + (String) person.get("email") + "\""); + jGenerator.writeString((String) person.get("email")); jGenerator.writeFieldName("role"); - jGenerator.writeRawValue("\"" + (String) person.get("role") + "\""); + jGenerator.writeString((String) person.get("role")); jGenerator.writeEndObject(); } @@ -108,13 +110,13 @@ public static void registerUser(String firstName, String lastName, String email) // write new user jGenerator.writeStartObject(); jGenerator.writeFieldName("firstName"); - jGenerator.writeRawValue("\"" + firstName + "\""); + jGenerator.writeString(firstName); jGenerator.writeFieldName("lastName"); - jGenerator.writeRawValue("\"" + lastName + "\""); + jGenerator.writeString(lastName); jGenerator.writeFieldName("email"); - jGenerator.writeRawValue("\"" + email + "\""); + jGenerator.writeString(email); jGenerator.writeFieldName("role"); - jGenerator.writeRawValue("\"" + DEFAULT_ROLE + "\""); + jGenerator.writeString(DEFAULT_ROLE); jGenerator.writeEndObject(); jGenerator.writeEndArray(); @@ -126,11 +128,12 @@ public static void registerUser(String firstName, String lastName, String email) public void logZipContents(String fName) throws IOException, SecurityException, IllegalStateException, NoSuchElementException { - ZipFile zf = new ZipFile(fName); - @SuppressWarnings("unchecked") - Enumeration e = (Enumeration) zf.entries(); - while (e.hasMoreElements()) { - log.info(e.nextElement().toString()); + try (ZipFile zf = new ZipFile(fName)) { + @SuppressWarnings("unchecked") + Enumeration e = (Enumeration) zf.entries(); + while (e.hasMoreElements()) { + log.info(e.nextElement().toString()); + } } } diff --git a/src/main/java/com/microfocus/example/web/controllers/ProductController.java b/src/main/java/com/microfocus/example/web/controllers/ProductController.java index 8a03f0b3..0d287182 100644 --- a/src/main/java/com/microfocus/example/web/controllers/ProductController.java +++ b/src/main/java/com/microfocus/example/web/controllers/ProductController.java @@ -87,11 +87,16 @@ String GetControllerName() { @GetMapping("/xss") @ResponseBody public ResponseEntity getKeywordsContent(@Param("keywords") String keywords) { - - String retContent = "Product search using: " + keywords; - + String encodedKeywords = keywords != null ? + keywords.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") : ""; + String retContent = "Product search using: " + encodedKeywords; return ResponseEntity.ok().body(retContent); } + } @GetMapping("/firstaid") public String firstaid(Model model, @Param("keywords") String keywords, @Param("limit") Integer limit, Principal principal) { diff --git a/src/main/java/com/microfocus/example/web/controllers/UserController.java b/src/main/java/com/microfocus/example/web/controllers/UserController.java index 9928214e..4f9e5bb8 100644 --- a/src/main/java/com/microfocus/example/web/controllers/UserController.java +++ b/src/main/java/com/microfocus/example/web/controllers/UserController.java @@ -669,9 +669,10 @@ public ResponseEntity serveUnverifiedFile(@Param("file") String file) { return ResponseEntity.badRequest().build(); } - Resource rfile = storageService.loadAsResource(file, true); + Resource rfile = storageService.loadAsResource(file, true); + String sanitizedFilename = rfile.getFilename().replaceAll("[\\r\\n]", ""); return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + rfile.getFilename() + "\"").body(rfile); + "attachment; filename=\"" + sanitizedFilename + "\"").body(rfile); } @GetMapping("/log") diff --git a/src/main/resources/templates/user/register.html b/src/main/resources/templates/user/register.html index 7f5177ed..b141f6be 100644 --- a/src/main/resources/templates/user/register.html +++ b/src/main/resources/templates/user/register.html @@ -105,7 +105,7 @@

Enter your registration details

- + Must be 8-20 characters long, containing letters, numbers and special characters. @@ -115,7 +115,7 @@

Enter your registration details

- + Confirm your password.