Struts2 token not only effectively prevents the form from repeating the submission, but also can be CSRF verified.
CSRF attack principles such as:
CSRF attack schematic diagram
In fact, B may also be a benign website, just hijacked by hacker XSS. User is really wronged AH: I did not go to a mess of the site, how still in the recruit?
Struts2 token Check principle:
STRUTS2 token verification schematic diagram
Against the CSRF attack schematic, you can see that although a.jsp returns the token value to the browser, B is unable to get the specific value of the token, or B can only get a token by going to request a, but this loses the purpose of falsifying the user's request because the session is inconsistent. So b.action no longer handles this bogus request, truncating the CSRF process as shown:
Struts2 token truncates the CSRF attack schematic diagram
Understand the principle, then work it. As a result, many requests for non-form form submissions were successfully intercepted, such as the original AJAX request, the page's href request, and the DWR request, which did not work properly-removing the token validation of the requested action would work, but this gave the hacker the opportunity to take advantage of it.
So how do you get other requests outside the form form to pass STRUTS2 tokens?
Let's continue in-depth understanding of the Struts2 token verification principle.
Take the latest version of 2.3.20 as an example. We <interceptor-ref name= "token" ></interceptor-ref> tag in struts.xml for a action, in fact it is configured Org.apache.struts2.interceptor.TokenInterceptor interceptors. View its dointercept source code:
Protected String dointercept (actioninvocation invocation) throws Exception {if (log.isdebugenabled ()) {Log.debug (" Intercepting invocation to check for valid transaction token. "); See ww-2902:we need to use the real HttpSession here, as opposed to the Map//that wraps the session, because a new wrap is created on every requesthttpsession session = Servletactioncontext.getrequest (). GetSession (True); synchronized ( session) {if (! Tokenhelper.validtoken ()) {return Handleinvalidtoken (invocation);}} return Handlevalidtoken (invocation);}
It calls the Validtoken method of Org.apache.struts2.util.TokenHelper, its source code:
public static Boolean Validtoken () {String tokenname = Gettokenname (); if (tokenname = = null) {if (log.isdebugenabled ()) {L Og.debug ("No token name found-Invalid token");} return false;} String token = GetToken (tokenname), if (token = = null) {if (log.isdebugenabled ()) {Log.debug ("No token found for token name "+tokenname+", Invalid token ");} return false;} Map session = Actioncontext.getcontext (). GetSession (); String sessiontoken = (string) session.get (Tokenname), if (!token.equals (Sessiontoken)) {if (log.iswarnenabled ()) { Log.warn (Localizedtextutil.findtext (Tokenhelper.class, "Struts.internal.invalid.token", Actioncontext.getcontext (). GetLocale (), "Form token {0} does not match the session token {1}.", New Object[]{token, Sessiontoken});} return false;} Remove the token so it won ' t is used Againsession.remove (Tokenname); return true;}
It calls its own gettokenname () to get the Tokenname value, and then uses its own getToken (Tokenname) to get the value of the token parameter requested by the client, and then compares it with the value saved in the session.
View Gettokenname () source code:
public static String Gettokenname () {Map params = Actioncontext.getcontext (). GetParameters (); if (!params.containskey ( Token_name_field) {if (log.iswarnenabled ()) {Log.warn ("Could not the Find token NAME in params.");} return null;} String[] Tokennames = (string[]) params.get (Token_name_field); String tokenname;if ((tokennames = = null) | | (Tokennames.length < 1)) {if (log.iswarnenabled ()) {Log.warn ("Got a null or empty token name."); return null;} Tokenname = Tokennames[0];return tokenname;}
Token_name_field is the static variable of org.apache.struts2.util.TokenHelper, the value is "struts.token.name", the function of Gettokenname method is to get the client to pass over The value of the Struts.token.name parameter. Validtoken get this value, continue to call Gettoken,gettoken source code according to this value:
public static string GetToken (String tokenname) {if (tokenname = = null) {return null;} Map params = Actioncontext.getcontext (). GetParameters (); String[] tokens = (string[]) params.get (tokenname); String token;if ((tokens = = null) | | (Tokens.length < 1)) {if (log.iswarnenabled ()) {Log.warn ("Could not the Find token mapped to token name" + tokenname);} return null;} token = Tokens[0];return token;}
It is based onThe value of the Struts.token.name parameter, such as "token" (this value can be configured in the token tag of the JSP, such as <s:token name= "token"/>, the default is token), get the client The value of the token parameter, which is then returned to the Validtoken method.
The struts action that was intercepted with the token verification using Chrome's developer tools confirms the above statement:
The value of the intercepted Struts.token.name
Principles are understood, the next thing seems to be the logical.
$.ajax calls support Struts2 token:
<script>var Strutstoken = "<s:property value=" #session [' Struts.tokens.token '] "/>"; var token = { " Struts.token.name ": Token", "token": Strutstoken};$.ajax ({ URL: '/endpoint ', Data:token, DataType: ' Jsonp ', cache:true, success:function () {console.log (' success ');}, error:function () { Console.log (' failure '); }});</script>
Page href Request Support Struts2 token:
<td><a href= "Findsigledetail.action? Transid=${value.transid}&struts.token.name=token&token=<s:property value= "#session [' Struts.tokens.token '] "/>" title= "edit meta" > View Details < /a></td>
References
- Http://www.journaldev.com/2281/struts2-token-interceptor-to-handle-double-form-submission-problem
- Http://stackoverflow.com/questions/19919522/how-to-pass-a-struts2-token-via-ajax-without-a-form
CSRF principle and Struts2 token verification Defense Raiders analysis