Re: Request for Assistance with OWASP ZAP Vulnerabilities in Struts-Based Java Web Application

Shivam Agrahari <[email protected]> Wed, 16 Apr 2025 17:05:36 +0530
Newsgroups gmane.comp.jakarta.struts.user
Message-ID <CAF8rG-sD-sx_ce-xiwkOZnBt-DHEMG2DSQm53iXyV6=nwnrJCA@mail.gmail.com>
Hi,

The application is currently showing the following security vulnerabilities:
Content security Policy (CSP) Header not set (3 instances)-
    GET: http://localhost:8080/favicon.ico
    GET: http://localhost:8080/favicon.test
    GET: http://localhost:8080/favicon.test\

Missing Anti-clickjacking Header (2 instances)-
    GET: http://localhost:8080/favicon.test
    GET: http://localhost:8080/favicon.test\

Cookie without SameSite Attribute (2 instances)
    GET: http://localhost:8080/favicon.test
    GET: http://localhost:8080/favicon.test\

X-Content-Type-Options Header Missing (2 instances)
    GET: http://localhost:8080/favicon.test
    GET: http://localhost:8080/favicon.test\

I’ve made efforts to address these vulnerabilities and have shared the
relevant code snippet below for your reference. The code is intended to
mitigate CSRF and other related issues:

@Override
public String intercept(ActionInvocation ai) throws Exception {
final ActionContext ac = ai.getInvocationContext();
HttpServletResponse myresponse = ac.getServletResponse();
HttpSession session =
org.apache.struts2.ServletActionContext.getRequest().getSession();
final String jsessionid = session.getId();
String cookieValue = "JSESSIONID=" + jsessionid + "; Path=/test;  HttpOnly;
SameSite=Strict";
myresponse.setHeader("Set-Cookie", cookieValue);
myresponse.setHeader("X-Frame-Options", "SAMEORIGIN");
myresponse.setHeader("X-Content-Type-Options", "nosniff");
myresponse.setHeader("Content-Security-Policy", "default-src 'self';
img-src 'self'; frame-src 'self';  connect-src 'self'; frame-ancestors
'self'; font-src 'self'; base-uri 'self'; form-action 'self';
 'unsafe-inline' 'unsafe-eval'  prefetch-src 'none'; manifest-src 'none';
object-src 'self'; media-src 'none'; ");
String resultString = ai.invoke();
System.out.println("before result");
return resultString;
}

Pls suggest necessary improvements.

Regards,
Shivam

 test.war
<https://drive.google.com/open?id=1ChtdOQKVdehi27j0Q-xth0w_9H7qD3wg>

On Wed, Apr 16, 2025 at 4:17 PM Lukasz Lenart <[email protected]>
wrote:

> śr., 16 kwi 2025 o 07:30 Shivam Agrahari
> <[email protected]> napisał(a):
> > Could you please advise on how to resolve these issues? For your
> reference, I have attached the WAR file of the test project along with a
> few relevant pages.
>
> The best option is to read through OWASP recommendations PDFs and
> apply them one by one
>
> Cheers
> Łukasz
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>
>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
CspInterceptor.java.txt (text/plain, 1.7 KB)
package org.paradyne.lib;

import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ActionContext;
import org.apache.struts2.interceptor.Interceptor;

public class CspInterceptor implements Interceptor {

	private static final long serialVersionUID = -4628112275531928102L;

	@Override
	public void destroy() {
		System.out.println("CSP interceptor destroy  method!!!!!");

	}

	@Override
	public void init() {
		System.out.println("CSP interceptor init method!!!!!");
	}

	@Override
	public String intercept(ActionInvocation ai) throws Exception {
		System.out.println("Before actionInvocation.invoke() called!!!!");

		final ActionContext ac = ai.getInvocationContext();
		HttpServletResponse myresponse = ac.getServletResponse();
		HttpSession session = org.apache.struts2.ServletActionContext.getRequest().getSession();
		final String jsessionid = session.getId();
		String cookieValue = "JSESSIONID=" + jsessionid + "; Path=/test;  HttpOnly; SameSite=Strict";
		myresponse.setHeader("Set-Cookie", cookieValue);
		myresponse.setHeader("X-Frame-Options", "SAMEORIGIN");
		myresponse.setHeader("X-Content-Type-Options", "nosniff");
		myresponse.setHeader("Content-Security-Policy", "default-src 'self'; img-src 'self'; frame-src 'self';  connect-src 'self'; frame-ancestors 'self'; font-src 'self'; base-uri 'self'; form-action 'self';  'unsafe-inline' 'unsafe-eval'  prefetch-src 'none'; manifest-src 'none'; object-src 'self'; media-src 'none'; ");
		String resultString = ai.invoke();
		System.out.println("before result");
		return resultString;
	}
}
login.jsp.txt (text/plain, 1.4 KB)
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<s:form method="post" validate="true" theme="simple" name="form" action="Login"
	id="paraFrm">
	<s:token />

	<table style="margin-left: 40%; margin-top: 20%;">

		<tr>
			<td style="color: maroon;">User Name :</td>
			<td><s:textfield
					cssStyle="width:100%;text-align:LEFT;font-size: 12PX;background-color: #FDEBD0;"
					name="username" /></td>
		</tr>
		<tr>
			<td style="color: maroon;">Password :</td>
			<td><s:password
					cssStyle="width:100%;text-align:LEFT;font-size: 12PX;background-color: #FDEBD0;"
					name="passwd" /></td>
		</tr>
		<tr>
			<td align="center">&nbsp;</td>
		</tr>
		<tr>
			<td colspan="2"><s:submit cssClass="pagebutton" 
					cssStyle="width:100%" value="Submit" onclick="return callSubmit();" /></td>
		</tr>

	</table>

</s:form>




<script>
  	function callSubmit(){
  		var username = document.getElementById('paraFrm_username').value;
  		var passwd 	 = document.getElementById('paraFrm_passwd').value;
  		if(username==""||passwd==""){
  			alert("User Id or password can not be blank");
  		}
  		else{
  			document.getElementById('paraFrm').target="_self";
			document.getElementById('paraFrm').action="submitpage";
			document.getElementById('paraFrm').submit();
  			return true;
  		}
  	}
 
  	</script>
LoginAction.java.txt (text/plain, 2.3 KB)
package org.struts.action.common;

import org.apache.struts2.ActionSupport;
import org.apache.struts2.ModelDriven;
import org.apache.struts2.Preparable;
import org.apache.struts2.action.ServletContextAware;
import org.apache.struts2.action.ServletRequestAware;
import org.apache.struts2.action.ServletResponseAware;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;

import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.parameter.StrutsParameter;
import org.paradyne.bean.common.LoginBean;

import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Namespace("/common")
@Action("Login")
@Results({
	@Result(name = "success", 				location = "login.Login",			type = "tiles"),
	@Result(name = "welcomepage", 			location = "login.successpage",		type = "tiles"),
	@Result(name = "input", 				location = "login.Login",			type = "tiles")
})
public class LoginAction extends ActionSupport implements
ModelDriven<Object>, Preparable, ServletRequestAware,ServletResponseAware, ServletContextAware{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;  
	LoginBean bean;
	@StrutsParameter(depth = 1)
	@Override
	public Object getModel() {
		// TODO Auto-generated method stub
		return bean;
	}
	
	@Action("loginpage")
	public String loginpage() {
		return "input";
	}
	
	@Action("submitpage")
	public String submitpage() {
		System.out.println("password is:"+bean.getUsername());
		System.out.println("user id is:"+bean.getPasswd());
		if(bean.getUsername().equals("aa")&&bean.getPasswd().equals("aa")) {
			return "welcomepage";
		}
		return "input";
	}

	@Override
	public void withServletResponse(HttpServletResponse arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void withServletRequest(HttpServletRequest arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void prepare() throws Exception {
		// TODO Auto-generated method stub
		bean=new LoginBean();
	}

	@Override
	public void withServletContext(ServletContext arg0) {
		// TODO Auto-generated method stub
		
	}

	
}
LoginBean.java.txt (text/plain, 705 B)
package org.paradyne.bean.common;

import java.io.Serializable;

import org.apache.struts2.interceptor.parameter.StrutsParameter;


public class LoginBean implements Serializable{


	private static final long serialVersionUID = 1L;

	private String username="";
	private String passwd="";
	


	public String getPasswd() {
		return passwd;
	}
	public String getUsername() {
		return username;
	}
	@StrutsParameter(depth=1)
	public void setUsername(String username) {
		this.username = username;
	}
	@StrutsParameter(depth=1)
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public static long getSerialversionuid() {
		return serialVersionUID;
	}
	
}
struts.xml.txt (text/plain, 1.2 KB)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
    "struts-6.5.dtd">

<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.mapper.action.prefix.enabled" value="true"/>
	<constant name="struts.custom.i18n.resources" value="globalMessages" />		   
	<constant name="struts.multipart.maxSize" value="100000000" />
	<constant name="struts.allowlist.packageNames" value="org.paradyne.bean.common" />
 
	<constant name="struts.convention.default.parent.package" value="default" /> 
	  <package name="default" extends="struts-default">
	  <result-types>
			  	<result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult"/>
			</result-types>
			
			<interceptors>
	   			<interceptor name="CSPInterceptor"   class="org.paradyne.lib.CspInterceptor"/>
	   			<interceptor-stack name="customStack">
	   			<interceptor-ref name="defaultStack" />
	   			<interceptor-ref name="CSPInterceptor" />	   			
	   			</interceptor-stack>	 
		   </interceptors>   
		   <default-interceptor-ref name="customStack" />
		   
		   
	  	</package>
</struts>
web.xml.txt (text/plain, 1.7 KB)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://jakarta.ee/xml/ns/jakartaee" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" id="WebApp_ID" version="6.0">  
  <display-name>pims</display-name>
 
   <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
  </filter>
  <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <listener>
        <listener-class>
            org.apache.struts2.tiles.StrutsTilesListener
        </listener-class>
    </listener>
    
  <context-param>
        <param-name>org.apache.tiles.definition.DefinitionsFactory.DEFINITIONS_CONFIG</param-name>
        <param-value>/WEB-INF/tiles.xml</param-value>
    </context-param>
  
  
  <welcome-file-list>
  <welcome-file>/WEB-INF/home.jsp</welcome-file>
  </welcome-file-list>
  
  <error-page>
        <error-code>401</error-code>
        <location>/error401.jsp</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/error500.jsp</location>
    </error-page>
    
     <error-page>
        <error-code>404</error-code>
        <location>/error404.jsp</location>
    </error-page>
     <error-page>
        <error-code>408</error-code>
        <location>/error408.jsp</location>
    </error-page>
    <session-config>
  <cookie-config>
  <http-only>true</http-only>
  <secure>true</secure>
  </cookie-config>
  <tracking-mode>COOKIE</tracking-mode>
  </session-config>
  
</web-app>