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 762bde1..e3abec8 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 98f61c6..68e9dc6 100644 --- a/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java +++ b/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java @@ -145,15 +145,15 @@ 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(); } - /* - http.headers() - .contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"); - */ + + httpSecurity.headers() + .contentSecurityPolicy("default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'"); + httpSecurity.authorizeRequests() .antMatchers("/", 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 3e91502..dd3dbc1 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 86f5c5d..a56805e 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 4a5fabb..ae9c1ba 100644 --- a/src/main/java/com/microfocus/example/config/handlers/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/com/microfocus/example/config/handlers/CustomAuthenticationSuccessHandler.java @@ -101,20 +101,42 @@ public static String getTargetUrl(HttpServletRequest request, HttpServletRespons targetUrl = loginReferer; String targetPath = null; try { - targetPath = new URL(targetUrl).getPath(); + URL url = new URL(targetUrl); + // Only allow relative URLs or URLs from the same host + String requestHost = request.getServerName(); + if (!url.getHost().equals(requestHost)) { + log.warn("Attempted redirect to external host: " + url.getHost() + ", redirecting to user home instead"); + targetUrl = USER_HOME_URL; + } else { + targetPath = url.getPath(); + 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 (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"; + } } - 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"; - } + + } } } @@ -154,4 +176,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 3798cff..171d2bc 100644 --- a/src/main/java/com/microfocus/example/config/handlers/UrlAuthenticationSuccessHandler.java +++ b/src/main/java/com/microfocus/example/config/handlers/UrlAuthenticationSuccessHandler.java @@ -85,8 +85,27 @@ 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 refererParam = request.getParameter("referer"); + String targetUrl = "/user"; // default safe target + if (refererParam != null && !refererParam.isEmpty()) { + try { + URL refererURL = new URL(refererParam); + String requestHost = request.getServerName(); + int requestPort = request.getServerPort(); + String refererHost = refererURL.getHost(); + int refererPort = refererURL.getPort() == -1 ? (refererURL.getProtocol().equals("https") ? 443 : 80) : refererURL.getPort(); + + // Only allow redirects to same host and port + if (refererHost.equals(requestHost) && refererPort == requestPort) { + targetUrl = refererURL.getPath(); + if (targetUrl == null || targetUrl.isEmpty()) { + targetUrl = "/"; + } + } + } catch (Exception e) { + log.warn("Invalid referer URL provided, using default: " + e.getMessage()); + } + } String targetPath = new URL(targetUrl).getPath(); Collection authorities = authentication.getAuthorities(); diff --git a/src/main/java/com/microfocus/example/repository/ProductRepository.java b/src/main/java/com/microfocus/example/repository/ProductRepository.java index 62760f8..07a1518 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) { @@ -89,16 +89,16 @@ public boolean existsById(UUID id) { 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()); + String sqlQuery = "SELECT * FROM " + getTableName() + + " WHERE lower(code) = ?"; + result = jdbcTemplate.query(sqlQuery, new ProductMapper(), code.toLowerCase()); Optional optionalProduct = Optional.empty(); if (!result.isEmpty()) { optionalProduct = Optional.of(result.get(0)); } return optionalProduct; } + } public List findByKeywords(String keywords, int offset, int limit) { String query = keywords.toLowerCase(); @@ -113,8 +113,8 @@ public List findByKeywords(String keywords, int offset, int limit) { 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()); + " WHERE lower(name) LIKE ?"; + return jdbcTemplate.query(sqlQuery, new ProductMapper(), "%" + query + "%"); } public List findAvailableByKeywords(String keywords, int offset, int limit) { @@ -129,10 +129,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 a5a6197..90b67ec 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 @@ -152,13 +156,18 @@ public Resource loadAsResource(String filename) { * To expose OWASP A01:2021 - Broken Access Control */ @Override + @Override public Resource loadAsResource(String filename, boolean traverse) { try { Path file = null; if (traverse) { - file = Paths.get(filename); + file = Paths.get(filename).normalize().toAbsolutePath(); + // Security check: ensure the resolved path is within rootLocation + if (!file.startsWith(this.rootLocation.toAbsolutePath())) { + throw new StorageException("Cannot access file outside current directory."); + } } else { - file = load(filename); + file = load(filename); } Resource resource = new UrlResource(file.toUri()); diff --git a/src/main/java/com/microfocus/example/utils/UserUtils.java b/src/main/java/com/microfocus/example/utils/UserUtils.java index 30f834a..d5b77ed 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,11 +96,11 @@ 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")); // L97 jGenerator.writeFieldName("lastName"); jGenerator.writeRawValue("\"" + (String) person.get("lastName") + "\""); jGenerator.writeFieldName("email"); - jGenerator.writeRawValue("\"" + (String) person.get("email") + "\""); + jGenerator.writeString((String) person.get("email")); // L101 jGenerator.writeFieldName("role"); jGenerator.writeRawValue("\"" + (String) person.get("role") + "\""); jGenerator.writeEndObject(); @@ -108,11 +110,11 @@ public static void registerUser(String firstName, String lastName, String email) // write new user jGenerator.writeStartObject(); jGenerator.writeFieldName("firstName"); - jGenerator.writeRawValue("\"" + firstName + "\""); + jGenerator.writeString(firstName); // L111 jGenerator.writeFieldName("lastName"); jGenerator.writeRawValue("\"" + lastName + "\""); jGenerator.writeFieldName("email"); - jGenerator.writeRawValue("\"" + email + "\""); + jGenerator.writeString(email); // L115 jGenerator.writeFieldName("role"); jGenerator.writeRawValue("\"" + DEFAULT_ROLE + "\""); jGenerator.writeEndObject(); @@ -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 8a03f0b..ca5d5bf 100644 --- a/src/main/java/com/microfocus/example/web/controllers/ProductController.java +++ b/src/main/java/com/microfocus/example/web/controllers/ProductController.java @@ -1,30 +1,31 @@ -/* - Insecure Web App (IWA) - - Copyright (C) 2020-2022 Micro Focus or one of its affiliates - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.microfocus.example.web.controllers; - -import com.microfocus.example.config.LocaleConfiguration; -import com.microfocus.example.entity.Product; -import com.microfocus.example.exception.ServerErrorException; -import com.microfocus.example.service.ProductService; - -import org.apache.commons.lang3.exception.ExceptionUtils; +/* // L1 + Insecure Web App (IWA) // L2 + // L3 + Copyright (C) 2020-2022 Micro Focus or one of its affiliates // L4 + // L5 + This program is free software: you can redistribute it and/or modify // L6 + it under the terms of the GNU General Public License as published by // L7 + the Free Software Foundation, either version 3 of the License, or // L8 + (at your option) any later version. // L9 + // L10 + This program is distributed in the hope that it will be useful, // L11 + but WITHOUT ANY WARRANTY; without even the implied warranty of // L12 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // L13 + GNU General Public License for more details. // L14 + // L15 + You should have received a copy of the GNU General Public License // L16 + along with this program. If not, see . // L17 +*/ // L18 + // L19 +package com.microfocus.example.web.controllers; // L20 + // L21 +import com.microfocus.example.config.LocaleConfiguration; // L22 +import com.microfocus.example.entity.Product; // L23 +import com.microfocus.example.exception.ServerErrorException; // L24 +import com.microfocus.example.service.ProductService; // L25 + // L26 +import org.apache.commons.lang3.exception.ExceptionUtils; // L27 +import org.springframework.web.util.HtmlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -88,7 +89,7 @@ String GetControllerName() { @ResponseBody public ResponseEntity getKeywordsContent(@Param("keywords") String keywords) { - String retContent = "Product search using: " + keywords; + String retContent = "Product search using: " + HtmlUtils.htmlEscape(keywords); return ResponseEntity.ok().body(retContent); } @@ -152,7 +153,11 @@ public ResponseEntity downloadFile(@PathVariable(value = "id") UUID pr log.debug("Using data directory: " + dataDir.getAbsolutePath()); String fileBasePath = dataDir.getAbsolutePath() + File.separatorChar + productId.toString() + File.separatorChar; - Path path = Paths.get(fileBasePath + fileName); + Path basePath = Paths.get(fileBasePath).normalize(); + Path path = basePath.resolve(fileName).normalize(); + if (!path.startsWith(basePath)) { + return ResponseEntity.notFound().build(); + } try { resource = new UrlResource(path.toUri()); } catch (MalformedURLException e) { 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 9928214..fcc5436 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 safeFilename = rfile.getFilename().replaceAll("[\\r\\n]", ""); return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + rfile.getFilename() + "\"").body(rfile); + "attachment; filename=\"" + safeFilename + "\"").body(rfile); } @GetMapping("/log") diff --git a/src/main/resources/templates/user/register.html b/src/main/resources/templates/user/register.html index 7f5177e..b141f6b 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.