Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Java Security Code Review: Vulnerability Patterns and Defensive Implementations

Tech May 17 1

SQL Injection Vulnerabilities

JDBC Concatenation Flaws

Direct string concatenation in SQL queries without input validation creates injection vectors. When user-supplied parameters embed directly in to query strings, attackers can manipulate query logic.

Vulnerable Pattern:

public User fetchUserDetails(String userId) {
    String query = "SELECT * FROM accounts WHERE uid = '" + userId + "'";
    Statement stmt = connection.createStatement();
    ResultSet rs = stmt.executeQuery(query);
    // ...
}

Defensive Implementation: Parameterized queries eliminate injection risks by separating code from data:

public User fetchUserSecure(String userId) {
    String query = "SELECT * FROM accounts WHERE uid = ?";
    PreparedStatement pstmt = connection.prepareStatement(query);
    pstmt.setString(1, userId);
    ResultSet rs = pstmt.executeQuery();
    // Process results...
}

Alternatively, input validation using allowlists provides defense-in-depth:

public boolean isValidIdentifier(String input) {
    return input != null && input.matches("^[a-zA-Z0-9]{8,16}$");
}

MyBatis Framework Risks

MyBatis dynamic SQL features introduce injection risks when using ${} parameter substitution instead of #{} placeholder syntax.

Risky Implemantation:

@Select("SELECT * FROM products WHERE category = '${cat}'")
List<Product> searchByCategory(@Param("cat") String category);

Secure Alternatives: Use concatenation functions for dynamic patterns:

@Select("SELECT * FROM products WHERE category LIKE CONCAT('%', #{keyword}, '%')")
List<Product> fuzzySearch(@Param("keyword") String keyword);

For sorting operations, avoid direct parameter interpolation. Implement mapped sorting:

public List<User> getSortedUsers(String sortField, String order) {
    String column = resolveColumn(sortField); // Maps to allowed columns only
    String direction = "DESC".equalsIgnoreCase(order) ? "DESC" : "ASC";
    return userMapper.orderBy(column, direction);
}

File Operation Vulnerabilities

Unrestricted File Uploads

Accepting files without validating extensions allows executable content upload.

Vulnerable Handler:

@PostMapping("/upload")
public ResponseEntity<String> handleUpload(MultipartFile incoming) {
    Path destination = Paths.get(STORAGE_PATH, incoming.getOriginalFilename());
    Files.write(destination, incoming.getBytes());
    return ResponseEntity.ok("Uploaded");
}

Validation Strategy:

@PostMapping("/upload")
public ResponseEntity<String> secureUpload(MultipartFile incoming) {
    String originalName = incoming.getOriginalFilename();
    String extension = originalName.substring(originalName.lastIndexOf(".")).toLowerCase();
    
    Set<String> permittedTypes = Set.of(".jpg", ".jpeg", ".png", ".gif", ".pdf");
    if (!permittedTypes.contains(extension)) {
        return ResponseEntity.badRequest().body("Unsupported file type");
    }
    
    String sanitized = UUID.randomUUID().toString() + extension;
    Path destination = Paths.get(STORAGE_PATH, sanitized);
    // Continue with storage...
}

Path Traversal

User-controlled filenames accessing filesystems require normalization to prevent directory escape sequences.

Vulnerable Retrieval:

@GetMapping("/download")
public byte[] getDocument(@RequestParam String filename) {
    Path target = Paths.get(BASE_DIRECTORY, filename);
    return Files.readAllBytes(target);
}

Secure Access Pattern:

public byte[] retrieveSafely(String userInput) throws IOException {
    if (userInput.contains("..") || userInput.contains("/") || userInput.contains("\\")) {
        throw new SecurityException("Invalid path sequence detected");
    }
    
    Path target = Paths.get(BASE_DIRECTORY).resolve(userInput).normalize();
    if (!target.startsWith(Paths.get(BASE_DIRECTORY))) {
        throw new SecurityException("Path traversal attempt blocked");
    }
    return Files.readAllBytes(target);
}

Cross-Site Scripting (XSS) Prevention

Output Encoding Strategies

Untrusted data rendered in HTML contexts requires contextual encoding.

Context-Aware Encoding:

import org.springframework.web.util.HtmlUtils;
import org.owasp.encoder.Encode;

public String sanitizeForHtml(String untrusted) {
    return HtmlUtils.htmlEscape(untrusted);
}

public String encodeForAttribute(String untrusted) {
    return Encode.forHtmlAttribute(untrusted);
}

Rich Content Handling

For HTML content that requires structural elements, use parser-based allowlisting:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist;

public String sanitizeRichContent(String rawHtml) {
    Safelist policy = new Safelist()
        .addTags("p", "br", "strong", "em", "a", "ul", "ol", "li")
        .addAttributes("a", "href")
        .addProtocols("a", "href", "https", "http");
    
    return Jsoup.clean(rawHtml, policy);
}

Server-Side Request Forgery (SSRF) Mitigation

Unrestricted outbound requests enable attackers to probe internal networks and access restricted resources.

Multi-Layer Defense:

public String fetchRemoteResource(String targetUrl) {
    // Layer 1: Protocol validation
    if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
        return "Protocol not allowed";
    }
    
    try {
        URI uri = new URI(targetUrl);
        String host = uri.getHost();
        
        // Layer 2: IP resolution and private range check
        InetAddress address = InetAddress.getByName(host);
        String ip = address.getHostAddress();
        
        if (isInternalAddress(ip)) {
            return "Internal resources inaccessible";
        }
        
        // Layer 3: Disable redirects to prevent bypasses
        HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection();
        conn.setInstanceFollowRedirects(false);
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        
        return readResponse(conn);
    } catch (Exception e) {
        return "Request failed";
    }
}

private boolean isInternalAddress(String ip) {
    return ip.startsWith("127.") || ip.startsWith("10.") || 
           ip.matches("^172\\.(1[6-9]|2[0-9]|3[01])\\..*") ||
           ip.startsWith("192.168.");
}

Remote Code Execution Vulnerabilities

Command Injection Prevention

Executing system commands with user input requires strict validation.

Vulnerable Execution:

public String listDirectory(String path) {
    ProcessBuilder pb = new ProcessBuilder("sh", "-c", "ls -la " + path);
    Process process = pb.start();
    // ...
}

Command Allowlisting:

public String executeAllowedCommand(String userCmd) {
    Set<String> allowedCommands = Set.of("status", "version", "help");
    String baseCommand = userCmd.split("\\s+")[0];
    
    if (!allowedCommands.contains(baseCommand)) {
        return "Command not authorized";
    }
    
    ProcessBuilder pb = new ProcessBuilder(baseCommand);
    // Execute with restricted environment...
}

Script Engine Security

Dynamic script evaluation poses significant risks. Restrict code sources:

public Object evaluateScript(String scriptSource) {
    Pattern allowedSource = Pattern.compile("^https://trusted-domain\\.com/scripts/.*$");
    if (!allowedSource.matcher(scriptSource).matches()) {
        throw new SecurityException("Untrusted script source");
    }
    
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    // Additional sandboxing recommended...
    return engine.eval(scriptSource);
}

Deserialization Security

Object Stream Validation

Java deserialization attacks exploit class instantiation during object reconstruction.

Vulnerable Deserialization:

public Object restoreObject(byte[] data) {
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
    return ois.readObject();
}

Class Allowlisting:

import org.apache.commons.io.serialization.ValidatingObjectInputStream;

public Object restoreSecure(byte[] data) {
    try (ValidatingObjectInputStream vois = new ValidatingObjectInputStream(
            new ByteArrayInputStream(data))) {
        
        vois.accept(SafeEntity.class, UserProfile.class);
        return vois.readObject();
    }
}

YAML Processing

SnakeYAML's load() method can instantiate arbitrary classes. Use SafeConstructor:

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

public void parseConfiguration(String yamlContent) {
    Yaml parser = new Yaml(new SafeConstructor());
    Map<String, Object> config = parser.load(yamlContent);
    // Process safe basic types only...
}

XMLDecoder Risks

XMLDecoder executes methods described in XML documents. Avoid with untrusted input:

// Dangerous - do not use with untrusted XML
XMLDecoder decoder = new XMLDecoder(new FileInputStream("data.xml"));
Object result = decoder.readObject();

XML External Entity (XXE) Prevention

XML parsers must disable DTDs and external entities:

public void parseDocument(InputStream xmlInput) throws Exception {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    
    // Security configurations
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    
    SAXParser parser = factory.newSAXParser();
    parser.parse(xmlInput, new DefaultHandler());
}

Similar configurations apply to DocumentBuilderFactory, XMLReader, and SAXReader (dom4j).

Access Control Mechanisms

Horizontal Privilege Escalation

Verify resource ownership before data retrieval:

public AccountDetails viewAccount(String accountId, UserSession session) {
    AccountDetails account = repository.findById(accountId);
    
    if (!account.getOwnerId().equals(session.getUserId())) {
        throw new AccessDeniedException("Resource ownership mismatch");
    }
    
    return account;
}

Vertical Privilege Escalation

Enforce role checks for administrative functions:

public AdminPanel accessAdministration(UserSession session) {
    if (!session.hasRole("ADMINISTRATOR")) {
        throw new AccessDeniedException("Insufficient privileges");
    }
    return loadAdminInterface();
}

Additional Security Considerations

CSRF Protection

Validate request origins using synchronized tokens:

public ResponseEntity<String> processAction(HttpServletRequest request) {
    String requestToken = request.getHeader("X-CSRF-Token");
    String sessionToken = (String) request.getSession().getAttribute("csrfToken");
    
    if (!Objects.equals(requestToken, sessionToken)) {
        return ResponseEntity.status(403).body("Invalid security token");
    }
    // Process action...
}

Open Redirect Prevention

Validate redirection targets against appproved domains:

public String redirectUser(String target) {
    Set<String> approvedHosts = Set.of("app.example.com", "docs.example.com");
    
    try {
        URI uri = new URI(target);
        if (!approvedHosts.contains(uri.getHost())) {
            return "redirect:/error";
        }
        return "redirect:" + target;
    } catch (URISyntaxException e) {
        return "redirect:/error";
    }
}

JNDI Injection Defense

Restrict JNDI lookups to specific, safe contexts:

public Object lookupResource(String name) {
    Set<String> permittedJndi = Set.of(
        "java:comp/env/jdbc/primaryDB",
        "java:comp/env/mail/session"
    );
    
    if (!permittedJndi.contains(name)) {
        throw new NamingException("JNDI resource not authorized");
    }
    
    Context ctx = new InitialContext();
    return ctx.lookup(name);
}

Regular Expression DoS (ReDoS)

Avoid vulnerable regex patterns that cause catastrophic backtracking. Use linear-time engines:

// Vulnerable to ReDoS
boolean risky = Pattern.matches("(a+)+$", userInput);

// Safer alternative using RE2
boolean safe = com.google.re2j.Pattern.matches("(a+)+$", userInput);

API Documentation Exposure

Restrict API documentation endpoints to development environments:

@Configuration
@Profile({"dev", "local"})
public class ApiDocsConfig {
    // Swagger configuration...
}

This approach limits documentation exposure in production deployments.

Tags: Java

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.