You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@myfaces.apache.org by "Kyle Stiemann (Jira)" <de...@myfaces.apache.org> on 2021/02/11 03:28:00 UTC
[jira] [Created] (TRINIDAD-2567) Trinidad secret generation is not
thread-safe
Kyle Stiemann created TRINIDAD-2567:
---------------------------------------
Summary: Trinidad secret generation is not thread-safe
Key: TRINIDAD-2567
URL: https://issues.apache.org/jira/browse/TRINIDAD-2567
Project: MyFaces Trinidad
Issue Type: Bug
Components: Components, Facelets, Infrastructure, Plugins
Affects Versions: 2.2.1-core
Reporter: Kyle Stiemann
Sending multiple requests in rapid succession to a Trinidad application that has just started will cause multiple secret keys to be generated. If multiple {{POST}} s are sent, all but 1 will fail with a {{ViewExpiredException}}. Trinidad generates the secret keys in {{StateUtils}} somewhat like this:
{code}
private static SecretKey getSecret(ExternalContext ctx) {
SecretKey secretKey = (SecretKey) ctx.getApplicationMap().get(INIT_SECRET_KEY_CACHE);
if (secretKey == null) {
secretKey = createSecretKey(KeyGenerator.getInstance(getAlgorithm(ctx)).generateKey().getEncoded());
ctx.getApplicationMap().put(INIT_SECRET_KEY_CACHE, secretKey);
}
return secretKey;
}
{code}
{{FormRenderer}} calls {{ViewHandler.writeState()}} which calls the {{StateUtils.getSecret()}} method on each request. If more than 1 request calls {{getSecret()}} before the secret is set in {{INIT_SECRET_KEY_CACHE}}, each call to {{getSecret()}} has the chance to see a {{null}} value for {{INIT_SECRET_KEY_CACHE}}, generate a new secret, and replace any existing secret. Any view state that was generated using the discarded secrets will be unusable and cause a {{ViewExpiredException}}.
h2. Workarounds
The simplest workaround is to set values for the secrets as {{init-param}} s: https://cwiki.apache.org/confluence/display/MYFACES2/Secure+Your+Application. For example, in the {{web.xml}} (*note that the provided values are examples and should not be used in a production application*):
{code:xml}
<context-param>
<param-name>org.apache.myfaces.SECRET</param-name>
<!-- Decodes to TEST_KEY -->
<param-value>VEVTVF9LRVk=</param-value>
</context-param>
<context-param>
<param-name>org.apache.myfaces.MAC_SECRET</param-name>
<!-- Decodes to TRINIDAD_TEST_MAC_SECRET -->
<param-value>VFJJTklEQURfVEVTVF9NQUNfU0VDUkVU</param-value>
</context-param>
{code}
h2. Steps to Reproduce:
# Create 1 WAR with a simple {{ping.xhtml}} endpoint:
{code:xml}
<html
xmlns:h="http://java.sun.com/jsf/html"
xmlns="http://www.w3.org/1999/xhtml">
<h:head/>
<h:body>
<code>pong</code>
</h:body>
</html>
{code}
# Create anther Trinidad WAR with the following view and bean:
*{{hello.xhtml}}:*
{code:xml}
<?xml version="1.0" encoding="UTF-8"?>
<tr:document
xmlns:tr="http://myfaces.apache.org/trinidad"
title="hello">
<tr:form id="form">
<tr:inputText id="name" label="Please enter your name:" required="true" value="#{helloBean.name}" />
<tr:commandButton id="submitName" text="Submit" /><br />
<tr:outputText value="Hello #{helloBean.name}!" />
</tr:form>
</tr:document>
{code}
*{{HelloBean.java}}:*
{code:java}
@ManagedBean
@RequestScoped
public final class HelloBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
{code}
# Start up an app server like Tomcat with both WARs deployed.
# Using a script:
## {{GET}} the {{ping.xhtml}} endpoint to cause the app server to initialize.
## {{GET}} the {{hello.xhtml}} endpoint to obtain the view state and session id.
## Use the view state and session id to {{POST}} a name to the {{hello.xhtml}} form.
## Repeat the {{GET}} and {{POST}} 5 times in rapid succession with different sessions.
Here's an example {{bash}} script which uses {{curl}} to execute the above steps:
{code:sh}
#!/bin/bash
sendPost() {
ENCODED_VIEW_STATE="$(curl -s --cookie-jar /tmp/cookie-jar-$1 --cookie /tmp/cookie-jar-$1 \
'http://localhost:8080/trinidad-2.2/faces/hello.xhtml' | \
tr -d '\n' | sed 's/.*name="javax.faces.ViewState".*value="\([^"][^"]*\)".*/\1/' | \
sed -e 's|/|\%2F|g' -e 's/+/%2B/g' -e 's/=/%3D/g')"
curl --cookie-jar /tmp/cookie-jar-$1 --cookie /tmp/cookie-jar-$1 \
-d "javax.faces.ViewState=$ENCODED_VIEW_STATE&org.apache.myfaces.trinidad.faces.FORM=form&source=submitName&name=Test" \
-X POST 'http://localhost:8080/trinidad-2.2/faces/hello.xhtml'
}
rm /tmp/cookie-jar*; until curl -s 'http://localhost:8080/other-app/ping.xhtml'; do :; done &&
for i in {1..5}; do sendPost $i & done
{code}
h3. Result:
If the bug still exists, 4 of the 5 {{POST}} s will fail with a {{ViewExpiredException}}:
{code:html}
<!doctype html>
<html lang="en">
<head><title>HTTP Status 500 – Internal Server Error</title>
<style type="text/css">body {
font-family: Tahoma, Arial, sans-serif;
}
h1, h2, h3, b {
color: white;
background-color: #525D76;
}
h1 {
font-size: 22px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 14px;
}
p {
font-size: 12px;
}
a {
color: black;
}
.line {
height: 1px;
background-color: #525D76;
border: none;
}</style>
</head>
<body><h1>HTTP Status 500 – Internal Server Error</h1>
<hr class="line"/>
<p><b>Type</b> Exception Report</p>
<p><b>Message</b> viewId:/hello.xhtml - View /hello.xhtml could not be restored.</p>
<p><b>Description</b> The server encountered an unexpected condition that prevented it from fulfilling the request.</p>
<p><b>Exception</b></p>
<pre>javax.servlet.ServletException: viewId:/hello.xhtml - View /hello.xhtml could not be restored.
javax.faces.webapp.FacesServlet.service(FacesServlet.java:671)
org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:350)
org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:226)
org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
</pre>
<p><b>Root Cause</b></p>
<pre>javax.faces.application.ViewExpiredException: viewId:/hello.xhtml - View /hello.xhtml could not be restored.
com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:212)
com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:123)
com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:198)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:658)
org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:350)
org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:226)
org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
</pre>
<p><b>Note</b> The full stack trace of the root cause is available in the server logs.</p>
<hr class="line"/>
<h3>Apache Tomcat/9.0.39</h3></body>
</html>
{code}
If the bug is fixed, all 5 requests will succeed and show "Hello Test!" due to a successful {{form}} {{POST}}.
--
This message was sent by Atlassian Jira
(v8.3.4#803005)