In this tutorial, we will show you the step by step guide to fixing each of the OWASP top 10 vulnerabilities in Java web application that builds by Spring Boot, MVC, Data, and Security. We will start from the web application development, deployment, penetration testing, and fix the vulnerabilities issue based on OWASP top ten vulnerabilities.
This tutorial divided into several steps:
- Step #1: Download Existing Spring Boot, MVC, Data and Security Web Application
- Step #2: Deploy Web Application to VPS
- Step #3: Scan using OWASP ZAP on Basis Web Application
- Step #4: Fix the Vulnerabilities Issues
- Step #5: Re-Testing The Web Application
The following tools, frameworks, libraries, and modules are required for this tutorial:
- Spring Boot, MVC, Data, Security, Thymeleaf, and H2 Database
- Web Server (Nginx and Tomcat7)
- OWASP ZAP application
- Terminal or CMD
- Text Editor or IDE
Let's get started with the main steps!
Step #1: Download Existing Spring Boot, MVC, Data and Security Web Application
We will use the existing Spring Boot, MVC, Data, and Security Web Application that previously created in our other tutorial. Clone this GitHub source.
git clone https://github.com/didinj/spring-boot-security-user-role-login-eclipse.git mynotes
Build this Spring Boot application by type this command inside this application folder.
cd mynotes
./gradlew build
That Gradle builds command will create a .war file in the build/libs/ folder.
Step #2: Deploy Web Application to VPS
We will use Tomcat 9 and Nginx as a reverse proxy for Tomcat 9. Make sure you have installed both of them. We are installing both the HTTP server and Container in Ubuntu VPS. We already make a tutorial for installing Nginx and Tomcat on Ubuntu VPS. The Tomcat version is different but the configuration remain the same.
Next, transfer the war file to your Ubuntu VPS.
scp NetBeansProjects/mynotes/build/libs/mynotes-0.0.1-SNAPSHOT.war [email protected]:~/
Connect to the VPS using SSH.
ssh [email protected]
Enter the password that you are using for the server user. Copy the transferred war file to the Tomcat webapps folder.
sudo cp mynotes-0.0.1-SNAPSHOT.war /opt/tomcat/webapps/
Open the Tomcat 9 server.xml with a text editor.
sudo nano /opt/tomcat/conf/server.xml
Then replace the following tags.
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
With these tags to move the current document folder to the new web application.
<Host name="localhost" appBase="webapps" unpackWars="true" autoDeploy="true">
<Logger className="org.apache.catalina.logger.FileLogger" directory="logs" prefix="virtual_log." suff$
<Context path="" docBase="/opt/tomcat/webapps/mynotes-0.0.1-SNAPSHOT" debug="0" reloadable="true" />
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="virtual_log." s$
</Host>
Next, open the Nginx configuration file using a text editor.
sudo nano /etc/nginx/sites-enabled/default
Then change the server root document to this line.
server {
...
root /opt/tomcat/webapps/mynotes-0.0.1-SNAPSHOT;
}
Restart Tomcat and Nginx server.
sudo systemctl restart tomcat
sudo service nginx restart
Now, the web application should accessible from other computers using http://ip_address. For example, we are using IP address 192.168.0.100, on the browser go to this http://192.168.0.100, and here's the web application looks like.
Step #3: Scan using OWASP ZAP on Basis Web Application
We will scan this basic Spring Boot, MVC, Data, Security web application to find the vulnerabilities. For that, install the OWASP ZAP application (not working on MACOS Catalina) then install it on your computer. Start the OWASP ZAP application, and you will get this application like this.
Before running an attack on the website, set the OWASP ZAP to the maximum attack by open the Analyse menu -> Scan Policy Manager.
Click modify then set Policy like this screenshot then click OK button.
Choose the Automated Scan button on the main panel. Then fill the URL to attack field with "http://192.168.0.1" and leave other options as default.
Click the Attack button to start scanning. After scanning is complete, it will show alerts in the bottom panel.
You can click the attack button again to make sure get more vulnerabilities results. To see the full details scan results, open the Report menu then Generate an HTML report. Save the HTML file to your desired location then open it in the browser. The result should be like this.
Step #4: Fix the Vulnerabilities Issues
As you can see in the reports, there is 1 alert with a high-risk level and 5 alerts with the low-risk level. It also has a solution for them. Let's fix those issues one by one.
Fixing SQLInjection
SQL injection vulnerability found on the Signup form is more precisely in the email field. To find out, first, let's check the code of AuthController.java in the Spring Boot application.
...
@Controller
public class AuthController {
@Autowired
private CustomUserDetailsService userService;
...
@RequestMapping(value = "/signup", method = RequestMethod.POST)
public ModelAndView createNewUser(@Valid User user, BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
User userExists = userService.findUserByEmail(user.getEmail());
if (userExists != null) {
bindingResult.rejectValue("email", "error.user",
"There is already a user registered with the username provided");
}
if (bindingResult.hasErrors()) {
modelAndView.setViewName("signup");
} else {
userService.saveUser(user);
modelAndView.addObject("successMessage", "User has been registered successfully");
modelAndView.addObject("user", new User());
modelAndView.setViewName("login");
}
return modelAndView;
}
}
In the Signup method, there is a method to find the user by email from the UserDetailsService.
User userExists = userService.findUserByEmail(user.getEmail());
Next, check the CustomUserDetailsService.java for the findUserByEmail() method.
public User findUserByEmail(String email) {
return userRepository.findByEmail(email);
}
That method use findByEmail from UserRepository.java that extends JpaRepository.
User findByEmail(final String email);
So, it's very clear that we use a standard query with a clear text parameter which is too easy for attackers to attack using SQL injection. The findByEmail translated from this query.
select u from User u where u.email = ?1
So, we need to change the Spring Data JPA Named Parameters like this.
@Query("SELECT u FROM User u WHERE u.email = :email")
User findUserByEmail(@Param("email") String email);
Don't forget to organize all the required imports. Also, change the method in CustomUserDetailsService.java.
public User findUserByEmail(String email) {
return userRepository.findUserByEmail(email);
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findUserByEmail(email);
if (user != null) {
List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
return buildUserForAuthentication(user, authorities);
} else {
throw new UsernameNotFoundException("username not found");
}
}
Apply this way to all of class and methods that use standard JPQL. In this example, there is NoteRepository.java.
@Query("SELECT n FROM Notes n WHERE n.title = :title")
Notes findByTitle(@Param("title") String title);
And RoleRepository.java.
@Query("SELECT r FROM Role r WHERE r.role = :role")
Role findByRole(@Param("role") String role);
Fix Absence of Anti-CSRF Tokens
In the Spring Security application, CSRF protection is enabled by default. In this application, the CSRF protection disabled. For that, remove the http.csrf().disabled() in the WebSecurityConfig.java. So, the configuration looks like this.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").hasAuthority("ADMIN").antMatchers("/h2/console").permitAll()
.antMatchers("/login").permitAll().antMatchers("/signup").permitAll().antMatchers("/notes")
.hasAuthority("ADMIN").antMatchers("/notes/**").hasAuthority("ADMIN").anyRequest().authenticated().and()
.formLogin().successHandler(customizeAuthenticationSuccessHandler).loginPage("/login")
.failureUrl("/login?error=true").usernameParameter("email").passwordParameter("password").and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/").and()
.exceptionHandling();
In the HTML template that we are using Thymeleaf, check if there's no Thymeleaf implementation on each form. In the login and signup form, we found the unnecessary form that not using Thymeleaf th:action. For that, we are replace that from with <div> and <a> tag.
login.html
<div class="form-signin">
<a class="btn btn-md btn-success btn-block" th:href="@{/signup}">Signup Here</a>
</div>
signup.html
<div class="form-signin">
<a class="btn btn-md btn-success btn-block" th:href="@{/login}">Sign In</a>
</div>
Also, in show.html, the form not using th:action tag. For that, add th:action for that form.
<form th:action="@{/notes/delete}">
<input type="hidden" name="id" th:value="${note.id}" />
<h2><input type="submit" class="btn btn-danger" value="Delete" onclick="return confirm('Are you sure?');" />
<a th:href="@{'/notes/edit/' + ${note.id}}" class="btn btn-warning"><i class="fas fa-edit"></i> Edit</a></h2>
</form>
Fixing Cookie Without SameSite Attribute, Cookie Without Secure Flag, and Incomplete or No Cache-control and Pragma HTTP Header Set
Implementation of fixing cookie with the same-site attribute, Cookie Without Secure Flag, and Incomplete or No Cache-control and Pragma HTTP Header Set requires to serve web application using HTTPS and Nginx ModSecurity module. For that, make sure the web application using TLS/SSL certificate and serve over HTTPS. To get TLS/SSL certificate, you must have your own domain.
To install the ModSecurity module for Nginx, you can find the guide from their official site https://www.nginx.com/blog/compiling-and-installing-modsecurity-for-open-source-nginx/. Also, install the required modules fo HTTPS, ModSecurity, and Cookies Control by reconfiguring the Nginx source.
sudo ./configure --user=www-data --group=www-data --with-pcre-jit --with-debug --with-http_ssl_module --with-http_realip_module --with-http_v2_module --add-module=../ModSecurity-nginx --add-module=../nginx_cookie_flag_module
sudo make
sudo make install
You can get the final Nginx configuration from our GitHub repo.
Step #5: Re-Testing The Web Application
To re-testing, the web application using the OWASP ZAP application, do the same step as the previous OWASP ZAP scan. And here the new results.
As you see in the OWASP ZAP result, there's no alert found. That's means, all of the vulnerabilities issues found by OWASP ZAP already fixed.
In conclusion, to make a secure web application, we need to configure all aspects of the live or production web application. They are web application codes, container servers, and HTTP servers. Keep in mind that HTTPS is a mandatory requirement for a web application that accessible to the public.
That it's, Fixing OWASP Top 10 In Spring Boot, MVC, Data, and Security. You can get the full fixed Spring Boot application source code from our GitHub.
That just the basic. If you need more deep learning about Java and Spring Framework you can take the following cheap course:
- Java basics, Java in Use //Complete course for beginners
- Java Programming: Master Basic Java Concepts
- Master Java Web Services and REST API with Spring Boot
- JDBC Servlets and JSP - Java Web Development Fundamentals
- The Complete Java Web Development Course
- Spring MVC For Beginners: Build Java Web App in 25 Steps
- Practical RESTful Web Services with Java EE 8 (JAX-RS 2.1)
Thanks!