CodeNewbie Community 🌱

DeveloperTom404
DeveloperTom404

Posted on

OWASP Top Ten 2021 explained with simple Java examples and SAST insights

In this article, we talk about the OWASP Top Ten 2021 categories through the lens of PVS-Studio Java analyzer warnings. So, if you want to peek at patterns of potential vulnerabilities in Java code or learn about the OWASP Top Ten, this article is for you!

1287_java_sast/image1.png

Intro

We are PVS-Studio, a company that has been developing the static analyzer for almost 18 years. Our tool works with C, C++, C#, and Java. Recently, we've been working hard to ensure the Java analyzer qualifies as a SAST solution because Java is one of the most popular languages in web development.

What is SAST?

SAST (Static Application Security Testing) is a testing methodology where program source code is analyzed for potential vulnerabilities, i.e., static analysis focused on finding security vulnerabilities. This helps prevent certain software security issues before release. You read more here.

A question we often hear is: "How do you decide which diagnostic rules to add to your analyzer? What guides you?" For us, one of our main reference points for SAST in Java is the OWASP Top Ten.

The OWASP Top Ten is a project by the OWASP organization that ranks web vulnerabilities. The ranking is updated every few years based on reports from security experts, bug bounty companies, and web developers. In this article, we'll look at the 2021 edition. However, we'd like to note that the OWASP Top Ten 2025 is expected soon, and we'll definitely write about it on our blog, so subscribe and stay tuned.

How important and relevant are SAST and the OWASP Top Ten? Every year, the number of detected vulnerabilities continues to grow, which clearly shows the number of vulnerabilities reflected in CVE statistics.

What is CVE?

CVE (Common Vulnerabilities and Exposures) is a database of publicly known security vulnerabilities. You can learn more here.

Look at a brief but demonstrative summary of vulnerabilities reported between 2014 and 2024:

You can find the full statistics here.

You can see that the number of detected vulnerabilities is only increasing, and the most comprehensive and in-depth testing can ensure their preventive detection. After all, each testing approach has its own area of responsibility: if not all of them are used, certain problems can be failed to spot.

Comprehensive testing is that thing that helps adhere to the shift-left approach. If problems were detected during static or dynamic analysis—rather than during manual testing or, worse, in production—the cost of fixing them will be much lower. As a result, the demand for SAST tools is growing.

We already had the robust set of the necessary mechanisms in our tool; however, our Java team keeps refining them to ensure full coverage of relevant situations.

Note. We've previously written about what we've been doing to enhance our SAST tool. We talk about the taint analysis mechanism (see article N1 and article N2), support for user annotations, and various enhancements to data-flow analysis.

As a result, the PVS-Studio analyzer covers 9 out of 10 of OWASP Top Ten categories.

OWASP Top Ten 2021 categories

We'll discuss all categories of the OWASP Top Ten in order. We describe the category and then show one or several examples of vulnerable code where PVS-Studio issues a warning, so that you can better understand how such issues might look in practical cases.

A01:2021 — Broken Access Control

Broken Access Control is a category where vulnerabilities can lead to unauthorized information disclosure, modification, or destruction of all data, or performing a business function outside the user's limits.

Here's the first example:

public class RedirectServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response) throws IOException {
    String query = request.getQueryString();
    if (query.contains("url")) {
      String url = request.getParameter("url");
      response.sendRedirect(url);               // <=
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5324 Possible open redirect vulnerability. Potentially tainted data in the 'url' variable is used in the URL. RedirectServlet.java 15, 14

The server receives a GET request, extracts the URL from it, and redirects the user without any validation. The problem is that attackers can freely create a phishing copy of the trusted website with a malicious link, for example:

https://notevil.com/redirect?url=https://evil.com/registration
Enter fullscreen mode Exit fullscreen mode

In this case, the website redirects users to the registration form on the attacker's website. Users think that they're still on the trusted website and may enter their credentials, which will go straight into the attacker's hands.

To prevent this, the server should validate the link before redirecting. This ensures that if the link isn't in a whitelist, redirection should be prohibited:

private static final List<String> ALLOWED_DOMAINS = List.of(
  "https://allowed-host1.com",
  "https://allowed-host2.com",
  "https://allowed-host3.com"
);

public class RedirectServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response) throws IOException {
    String query = request.getQueryString();
    if (query.contains("url")) {
      String url = request.getParameter("url");
      if (ALLOWED_DOMAINS.contains(url)) {      // <=
        response.sendRedirect(url);              
      } else {
        //....
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We've shown only one way to fix this. For a more extensive list of ways to protect against this problem, you can refer to the special Cheat Sheet section on the OWASP blog with recommendations for preventing various vulnerabilities and security risks.

Look at another case:

private final Path uploadDir = Path.of("/var/www/uploads");

@Override
public void init() throws ServletException {
  try {
    Files.createDirectories(uploadDir);
    Files.setPosixFilePermissions(
            uploadDir,
            PosixFilePermissions.fromString("rwxrwxrwx"));  // <=
  } catch (IOException e) {
    throw new ServletException(e);
  }
}

@Override
protected void doPost(HttpServletRequest req,
                      HttpServletResponse resp) throws .... {
  Part filePart = req.getPart("file");
  Path target = uploadDir.resolve(Paths.get(filePart.getSubmittedFileName()));
  try (InputStream in = filePart.getInputStream()) {
    Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
  }
  resp.getWriter().println("uploaded to " + target);
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5318 Setting loose POSIX file permissions (rwxrwxrwx) is security-sensitive. PermissionsExample.java 13

Here, the directory where files are uploaded has full permissions for the OTHERS group, including the rights to execute the file. If users upload an executable file (for example, shell.jsp), it'll be executed when accessed (if we're working with a Tomcat server):

https://example.com/uploads/shell.jsp
Enter fullscreen mode Exit fullscreen mode

Thus, an insecure permission configuration in a web application directory led to RCE (Remote Code Execution). To protect against this, it's important to follow the principle of least privilege—and static analysis can help avoid overlooking such moments.

A02:2021 — Cryptographic Failures

Cryptographic Failures is a category that includes vulnerabilities related to improper encryption of sensitive data, such as the use of outdated cryptographic algorithms or hash functions, transmission of information without encryption, etc.

Here's the example:

public static void callExternalApi() throws Exception {
  SSLContext sslContext = SSLContext.getInstance("TLSv1");     // <=
  sslContext.init(null, null, new java.security.SecureRandom());

  HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
  URL url = new URL("https://external-api.example.com/data");

  HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
  try (BufferedReader in = new BufferedReader(
          new InputStreamReader(conn.getInputStream()))
  ) {
    String inputLine;
    while ((inputLine = in.readLine()) != null)
      System.out.println(inputLine);
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5313 Do not use old versions of SSL/TLS protocols as it may cause security issues. Insecure protocols: TLSv1. SSLExample.java 11

In this example, a legacy version of the SSL/TLS protocol is used to establish the connection. This can expose man-in-the-middle attacks, BEAST (its subtype), and others. More specifically, attackers could intercept, decrypt, or alter data transmitted between two parties.

To protect the code, it'd be better to use a newer and more secure version of this algorithm, "TLSv1.2" or "TLSv1.3".

Such problems may occur in the legacy part of the project. And we'd like to highlight once again: automated detection with SAST tools is the most effective way to prevent them.

A03:2021 — Injection

Injection includes vulnerabilities that arise when untrusted data is passed to certain critical parts of apps (for example, SQL query execution) and alters the program's intended behavior. As a result, sensitive data may be compromised, disclosed, or program execution may be halted.

Our tool has plenty of such diagnostic rules that can detect such issues. We'll demonstrate the most common example, an SQL injection. In the earlier categories, the examples fit within a single method. This time, we'll break down a more realistic case with creating an SQL query inside a simple MVC controller that proves whether PVS-Studio can detect issues in larger, more complex code fragments.

DemoController.java:

@RestController
public class DemoController {
  @Autowired
  private DemoService service;

  @RequestMapping("demo")
  public ResponseEntity<DemoObject> 
            demoEndpoint(@RequestParam(name="name") String name
  ) {
    return service.findByName(name)
                  .map(ResponseEntity::ok)
                  .orElse(ResponseEntity.notFound().build());
  }
}
Enter fullscreen mode Exit fullscreen mode

The request reaches the controller, which extracts the name parameter and passes it to DemoService.

DemoService.java:

@Service
public class DemoService {
  @Autowired
  DemoRepository demoRepository;

  Optional<DemoObject> findByName(String name) {
    return demoRepository.findByName(name);
  }
}
Enter fullscreen mode Exit fullscreen mode

DemoService calls the DemoRepository repository, passing it the received name as a parameter.

DemoRepository.java:

@Repository
public class DemoRepository {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  Optional<DemoObject> findByName(String name) {
    var sql = "SELECT * FROM demoTable WHERE name = '" + name + "'";
    if (name.equals("demoCondition")) {
      sql = "SELECT * FROM demoTable WHERE name = demoName";
      return Optional.ofNullable(
              jdbcTemplate.queryForObject(sql, DemoObject.class));
    }
    return Optional.ofNullable(
            jdbcTemplate.queryForObject(sql, DemoObject.class));
  }
}
Enter fullscreen mode Exit fullscreen mode

In the repository, we generate an SQL query for the database.

The analyzer issues the following warning: V5309 Possible SQL injection. Potentially tainted data in the 'sql' variable is used to create SQL command. DemoRepository.java 23, DemoController.java 16

That query generation is the main problem in this entire construction. Let's illustrate:

var sql = "SELECT * FROM demoTable WHERE name = '" + name + "'";
Enter fullscreen mode Exit fullscreen mode

The request is generated via concatenation, and if the name value is as follows:

' or 1==1; drop table demoTable; --
Enter fullscreen mode Exit fullscreen mode

The demoTable table will be deleted.

From the controller to the repository, the data wasn't cleaned or sanitized in any way. The analyzer detected this and issued a warning. To secure the application, data must be either sanitized or escaped before being passed to the request.

In the case of SQL injection, use parameterized queries:

Optional<DemoObject> findByName(String name) {
  String sql;
  if ("demoCondition".equals(name)) {
    sql = "SELECT * FROM demoTable WHERE name = demoName";
    return Optional.ofNullable(
      jdbcTemplate.queryForObject(
        sql,
        new BeanPropertyRowMapper<>(DemoObject.class)
      )
    );
  }

  sql = "SELECT * FROM demoTable WHERE name = ?";
  return Optional.ofNullable(
    jdbcTemplate.queryForObject(
      sql,
      new BeanPropertyRowMapper<>(DemoObject.class),
      name
    )
  );
}
Enter fullscreen mode Exit fullscreen mode

A04:2021 — Insecure Design

Insecure Design is a broad category that includes a group of vulnerabilities caused by weak, insecure application architecture.

We've already looked at one such cases when discussing category A01. This category also includes vulnerabilities related to granting extensive access rights to the OTHERS group, which is detected by V5318 diagnostic rules. Since we've already discussed this type of vulnerability, let's take a look at another one:

@Override
protected void doGet(HttpServletRequest req,
                     HttpServletResponse res) throws IOException {
  String requestedSessionId = req.getRequestedSessionId();
  if (requestedSessionId != null && requestedSessionId.startsWith("ADMIN-")) {
    // ....
  }
  //....
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5316 The use of HttpServletRequest#getRequestedSessionId is discouraged as it may expose security risks. DemoServlet.java 15

This case is one of the possible patterns of CWE-807, where security decisions are made based on untrusted or unreliable data.

What is CWE?

CWE (Common Weakness Enumeration) is a community-developed list of security vulnerabilities.

Like the OWASP Top Ten, CWE serves as a guideline when we add new diagnostic rules. On our website, you can check how our diagnostic rules map to CWE identifiers.

In this example, the getRequestSessionId method returns the user-specified session ID, not the one that actually belongs to the session.

From an architectural standpoint, it's an unsafe practice to rely on user input when making decisions. To prevent this, use the current session ID. In this example, the fix will be quite simple:

request.getSession().getId();
Enter fullscreen mode Exit fullscreen mode

A05:2021 — Security Misconfiguration

Security Misconfiguration is a category that includes vulnerabilities caused by security misconfiguration, such as enabling unnecessary ports or services, using insecure settings, or relying on external data to set system properties.

Here's the example:

public class MisconfigurationExample extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req,
                        HttpServletResponse resp) throws IOException {
    String key = req.getParameter("key");
    String value = req.getParameter("value");

    if (key == null || value == null || key.isBlank()) {
      resp.sendError(400, "Invalid key or value");
      return;
    }

    System.setProperty(key, value);
    resp.setContentType("text/plain");
    //....
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5320 Potentially tainted data in the 'value' variable is used in configuration settings. This may cause security issues. MisconfigurationExample.java 22

In the example above, system properties are configured in an unsafe way. Allowing users to control input external data is risky. Here are some reasons:

  • Changing system properties may lead to confidential data leakage and corruption. For example, a user-controlled property that stores the database address could allow access to data not intended for disclosure if the database name is changed.
  • Incorrectly configured system properties may cause the program to crash or destabilize it. For example, if users have access to a property that sets the maximum number of threads, the application could crash or behave unpredictably.

Overall, this approach causes more problems than it solves. However, if it is still necessary to use external data to form system properties, it'd be better to use values from a whitelist:

private static final List<String> ALLOWED_VALUES = List.of(
        "value1",
        "value2",
        "value3"
);

@Override
protected void doPost(HttpServletRequest req,
                      HttpServletResponse resp) throws IOException {
  //....
  if (    key == null || value == null || key.isBlank()
      || !ALLOWED_VALUES.contains(value)
  ) {
    resp.sendError(400, "Invalid key or value");
    return;
  }
  //....
}
Enter fullscreen mode Exit fullscreen mode

A06:2021 — Vulnerable and Outdated Components

As we mentioned earlier, we covered diagnostic rules for 9 out of 10 Top Ten categories. And the only one we don't currently address.

Vulnerable and Outdated Components is a category that includes only one security-related issue: the use of a library or framework version that contains a known vulnerability in the project.

The challenge for us is figuring out the best way to aggregate and track information about vulnerable library versions. Once we implement this functionality, we'll immediately let you know. Keep an eye on our updates and new releases. And of course, we'll share details on the blog, so stay subscribed.

A07:2021 — Identification and Authentication Failures

Identification and Authentication failures is a category that includes a group of vulnerabilities related to errors in session management or user authentication. They can compromise passwords, security keys, or session tokens, and may give an opportunity to steal identification data.

Here's the example:

@Configuration
public class CorsConfig {
  @Bean
  public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")          // <=
                .allowedMethods("*")
                .allowCredentials(true);
      }
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5325 Setting the value of the 'Access-Control-Allow-Origin' header to '*' is potentially insecure. CorsConfig.java 16

Here's an unsafe CORS configuration, where the * value in the Access-Control-Allow-Origin header allows requests from any origin, giving external hosts access to cross-domain content loaded in the browser. You can read more about this here.

Here are just some of the possible consequences of an unsafe configuration:

  • Private data can be disclosed when users move from the corporate network to the public internet.
  • Attackers can impersonate the API of a web app by exposing the users' browser data (such as cookies).
  • If the website is vulnerable to XSS, the consequences of an attack can be more serious.

Since we've touched on CORS, look at another unsafe pattern from this category:

public class NaiveCorsFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, 
                       ServletResponse response,
                       FilterChain chain) throws .... {

    HttpServletRequest req  = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    String origin = req.getParameter("origin");

    res.setHeader("Access-Control-Allow-Origin", 
                  origin);
    res.setHeader("Access-Control-Allow-Credentials", 
                  "true");
    res.setHeader("Access-Control-Allow-Methods", 
                  "GET,POST,PUT,DELETE");
    res.setHeader("Access-Control-Allow-Headers", 
                  "Content-Type,Authorization");

    chain.doFilter(request, response);
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5323 Potentially tainted data in the 'origin' variable is used to define the 'Access-Control-Allow-Origin' header. NaiveCorsFilter.java 25, 22

The CORS header is generated from external input. The potential risks are the same as in the previous example. In both situations, the best practice is to allow only requests from a predefined list of trusted domains:

private static final List<String> ALLOWED_HOSTS = List.of(
  "https://allowed-host1.com",
  "https://allowed-host2.com",
  "https://allowed-host3.com"
);

@Override
public void doFilter(ServletRequest request, 
                     ServletResponse response,
                     FilterChain chain) throws .... {
  HttpServletRequest req  = (HttpServletRequest) request;
  HttpServletResponse res = (HttpServletResponse) response;

  String origin = req.getParameter("origin");

  if (ALLOWED_HOSTS.contains(origin)) {
    res.setHeader("Access-Control-Allow-Origin", 
                  origin);
  } else {
    res.setHeader("Access-Control-Allow-Origin", 
                  ALLOWED_HOSTS.getFirst());
  }
  ....
}
Enter fullscreen mode Exit fullscreen mode

A08:2021 — Software and Data Integrity Failures

Software and Data Integrity Failures is a group of vulnerabilities that lead to software integrity failures, such as updates without digital signatures, unsafe deserialization, downloading dependencies from unsafe repositories, etc.

Here's the example:

public class UnsafeDeserializeServlet extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req,
                        HttpServletResponse res) throws .... {

    try (InputStream in = req.getInputStream();
         ObjectInputStream ois = new ObjectInputStream(in)   // <=
    ) {
      Object obj = ois.readObject();       
      // ....
    } catch (ClassNotFoundException e) {
      throw new ServletException("Unknown class during deserialization", e);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5333 Possible insecure deserialization vulnerability. Potentially tainted data in the 'in' variable is used to deserialize an object. UnsafeDeserializeServlet.java 19, 18

Here we have an example of unsafe deserialization. A byte stream is taken from the request, and the object is restored based on it. There's no verification of the object type or whether it's allowed to be deserialized, which gives space for RCE vulnerabilities.

Deserializing user data using native Java mechanisms is not recommended. However, if it's still necessary, one way to mitigate the risk is to verify the class of the object and ensure it's on a whitelist of allowed types before deserializing:

class AllowedClass1 { }
class AllowedClass2 { }

class SecureObjectInputStream extends ObjectInputStream {
  List<String> allowedClasses = List.of(
    AllowedClass1.class.getName(),
    AllowedClass2.class.getName()
  );

  public SecureObjectInputStream(InputStream in) throws .... {
    super(in);
  }

  @Override
  protected Class<?> resolveClass(ObjectStreamClass osc) throws .... {
    if (!allowedClasses.contains(osc.getName())) {
      throw new InvalidClassException("Unauthorized deserialization", 
                                      osc.getName());
    }
    return super.resolveClass(osc);
  }
}

public class DeserializeServlet extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req,
                        HttpServletResponse res) .... {
    try (InputStream in = req.getInputStream();
         ObjectInputStream ois = new SecureObjectInputStream(in)
    ) {
      Object obj = ois.readObject();
      // ....
    } catch (ClassNotFoundException e) {
      throw new ServletException("Unknown class during deserialization", e);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

A09:2021 — Security Logging and Monitoring Failures

Security Logging and Monitoring Failures covers all situations that may:

  • hinder issue detection due to an incorrect logging system;
  • lead to disclosure of information from logs to third parties;
  • allow injections through the logging system.

Here's the example:

public class AccessLogFilter extends OncePerRequestFilter {
  private static final Logger 
                       log = LoggerFactory.getLogger(AccessLogFilter.class);

  @Override
  protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws .... {

    String ip     = request.getRemoteAddr();
    String method = request.getMethod();
    String uri    = request.getRequestURI();
    String qs     = request.getQueryString();

    log.info("Access {} {}{}?{} from {}", 
             method, uri, "?", 
             qs,   // <= 
             ip);

    filterChain.doFilter(request, response);
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5319 Possible log injection. Potentially tainted data in the 'qs' variable is written into logs. AccessLogFilter.java 33, 29

Here's an example showing how logs can be cluttered and false information injected using ordinary queries. The request may include characters representing a line break \n in URL encoding.

If the original URL request is:

/products?category=books%0AERROR:+Inventory+breach+detected
Enter fullscreen mode Exit fullscreen mode

The qs string will be:

category=books\nERROR: Inventory breach detected
Enter fullscreen mode Exit fullscreen mode

As a result, the log message will be:

INFO  Access GET /products?category=books from 10.1.1.5
ERROR: Inventory breach detected
Enter fullscreen mode Exit fullscreen mode

The second line looks like a logger message. In a targeted attack, many such fake outputs could appear, and if a real issue arises, identifying and fixing it becomes much harder.

To prevent this, all external data should be sanitized before entering the logs. It can be implemented manually or via ready-made libraries. For example, using methods from the org.apache.commons.text.StringEscapeUtils class.

A10:2021 — Server-Side Request Forgery (SSRF)

Server-Side Request Forgery (SSRF) is a category that includes vulnerabilities in which a server connects to a remote resource without validating it first. The goals of such an attack can vary:

  • Disclosing information about the internal infrastructure of the targeted system or organization.
  • Stealing confidential data.
  • Sending malicious requests on behalf of the compromised server.

Here's the example:

@RestController
public class PreviewController {
  @GetMapping("/fetch")
  public String fetchUrl(@RequestParam("url") String url) throws Exception {
    URL external = new URL(url);
    HttpURLConnection conn = (HttpURLConnection) external.openConnection();
    conn.setConnectTimeout(2000);
    conn.setReadTimeout(2000);

    BufferedReader br = new BufferedReader(new InputStreamReader(
                                               conn.getInputStream()
                                           ));
    StringBuilder sb = new StringBuilder();
    String line;

    while ((line = br.readLine()) != null) {
      sb.append(line).append('\n');
    }
    br.close();

    return sb.toString();
  }
}
Enter fullscreen mode Exit fullscreen mode

The PVS-Studio warning: V5334 Possible server-side request forgery. Potentially tainted data in the 'external' variable is used to access a remote resource. PreviewController.java 17, 15

The URL from the request immediately establishes a connection without any prior validation. Sensitive information may be exposed if the address looks like this: http://127.0.0.1:8080/admin.

There are many other ways attackers can exploit SSRF. However, the common defense approach is the same: external URLs should be validated:

private static final List<String> ALLOWED_URLS = List.of(
  "https://allowed-domain1.com",
  "https://allowed-domain2.com"
);
@GetMapping("/fetch")
public String fetchUrl(@RequestParam("url") String url) throws Exception {
  if (!ALLOWED_URLS.contains(url)) {
    //....
    return "Invalid url";
  }
  ....
}
Enter fullscreen mode Exit fullscreen mode

Sum-up

We've walked through the OWASP Top Ten categories using simple examples, while also showcasing how our Java analyzer detects such issues as part of its SAST functionality.

Of course, we continue to work with and enhance the analyzer, and try our best to refine the quality and number of diagnostic rules. The best way to see what the analyzer can do is to try it yourself.

If you have any thoughts after reading this article that you would like to share, feel free to share them in the comments—we'd be glad to hear from you and discuss them.

That's it for now. Follow our blog for updates, and see you next time.

Top comments (2)

Collapse
 
tomdanny profile image
Tom Danny

Locksmith presents OWASP Top Ten 2021 explained with simple Java examples and SAST insights, making application security easier to understand. Each vulnerability, from injection flaws to insecure design, is broken down with practical coding illustrations. Static Application Security Testing (SAST) tools highlight risks in real time, helping developers fix issues early. By combining theory with hands-on guidance, this approach equips teams to build safer applications and strengthen cybersecurity practices effectively.

Collapse
 
keithwalker profile image
Keithwalker

Great explanation of OWASP Top Ten with Java examples—super helpful for understanding real-world risks. Security practices like SAST matter no matter what language you use. If you’re exploring secure development, comparing python vs java for web apps is also insightful since both have strong frameworks, but security implementation differs.