├── README.md ├── Test.jpg ├── Test.png ├── Test2.png ├── pom.xml ├── springboot-vuldb.iml ├── src └── main │ └── java │ └── me │ └── gv7 │ └── woodpecker │ └── plugin │ ├── Bean │ └── PropertiesBean.java │ ├── InfoDetec │ └── SpringBootInfoDetec.java │ ├── SpringBootActuatorsInfoPlugin.java │ ├── SpringBootInfoDetecPlugin.java │ ├── SpringBootJolokiaPlugin.java │ ├── SpringbootUtils.java │ ├── WoodpeckerPluginManager.java │ ├── exploits │ ├── SpringBootActuatorsInfoExploit.java │ └── SpringBootJolokiaExploit.java │ └── pocs │ └── SpringBootPocs.java └── target └── classes └── me └── gv7 └── woodpecker └── plugin ├── Bean └── PropertiesBean.class ├── SpringBootActuatorsInfoPlugin.class ├── SpringBootJolokiaPlugin.class ├── SpringbootUtils.class ├── WoodpeckerPluginManager.class ├── exploits ├── SpringBootActuatorsInfoExploit.class └── SpringBootJolokiaExploit.class └── pocs └── SpringBootPocs.class /README.md: -------------------------------------------------------------------------------- 1 | # Springboot漏洞全家桶 2 | 3 | - [x] SpringBoot Actuators 信息探测 4 | - [ ] Jolokia 端点 JNDI注入和RMI RCE 5 | - [ ] eureka 端点 XStream反序列化 6 | 7 | ![](Test.jpg) 8 | ![](Test2.png) -------------------------------------------------------------------------------- /Test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/Test.jpg -------------------------------------------------------------------------------- /Test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/Test.png -------------------------------------------------------------------------------- /Test2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/Test2.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | UTF-8 9 | 1.8 10 | 1.8 11 | 12 | 13 | me.gv7.woodpecker 14 | springboot-vuldb 15 | 0.1.1 16 | 17 | 18 | 19 | me.gv7.woodpecker 20 | woodpecker-sdk 21 | 0.1.0.beta5 22 | 23 | 24 | 25 | com.alibaba 26 | fastjson 27 | 1.2.69 28 | 29 | 30 | junit 31 | junit 32 | 4.11 33 | test 34 | 35 | 36 | 37 | net.dongliu 38 | requests 39 | 5.0.8 40 | 41 | 42 | me.gv7.woodpecker 43 | woodpecker-requests 44 | 0.2.0 45 | compile 46 | 47 | 48 | me.gv7.woodpecker 49 | woodpecker-sdk 50 | 0.3.0 51 | compile 52 | 53 | 54 | -------------------------------------------------------------------------------- /springboot-vuldb.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/Bean/PropertiesBean.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin.Bean; 2 | 3 | public class PropertiesBean { 4 | 5 | String jvmName; 6 | String serverPort; 7 | String javaVersion; 8 | String userName; 9 | Boolean haveInfo; 10 | 11 | public Boolean getHaveInfo() { 12 | return haveInfo; 13 | } 14 | 15 | public void setHaveInfo(Boolean haveInfo) { 16 | this.haveInfo = haveInfo; 17 | } 18 | 19 | public String getJvmName() { 20 | return jvmName; 21 | } 22 | 23 | public void setJvmName(String jvmName) { 24 | this.jvmName = jvmName; 25 | } 26 | 27 | public String getServerPort() { 28 | return serverPort; 29 | } 30 | 31 | public void setServerPort(String serverPort) { 32 | this.serverPort = serverPort; 33 | } 34 | 35 | public String getJavaVersion() { 36 | return javaVersion; 37 | } 38 | 39 | public void setJavaVersion(String javaVersion) { 40 | this.javaVersion = javaVersion; 41 | } 42 | 43 | 44 | public String getUserName() { 45 | return userName; 46 | } 47 | 48 | public void setUserName(String userName) { 49 | this.userName = userName; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/InfoDetec/SpringBootInfoDetec.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin.InfoDetec; 2 | 3 | import me.gv7.woodpecker.plugin.*; 4 | import me.gv7.woodpecker.plugin.Bean.PropertiesBean; 5 | import me.gv7.woodpecker.requests.RawResponse; 6 | import me.gv7.woodpecker.requests.Requests; 7 | 8 | import java.util.*; 9 | 10 | public class SpringBootInfoDetec implements InfoDetector { 11 | 12 | List pointListV1 = new ArrayList<>(); 13 | List pointListV2 = new ArrayList<>(); 14 | Map h2Headers = new HashMap<>(); 15 | boolean SpringbootVersionV1 = false; 16 | String[] basicPoint = new String[]{"cloudfoundryapplication","hystrix.stream" }; 17 | PropertiesBean properties; 18 | 19 | public SpringBootInfoDetec(){ 20 | h2Headers.put("Cache-Control", "max-age=0"); 21 | 22 | pointListV1.add("autoconfig"); 23 | pointListV1.add("heapdump"); 24 | pointListV1.add("dump"); 25 | pointListV1.add("mappings"); 26 | pointListV1.add("auditevents"); 27 | pointListV1.add("beans"); 28 | pointListV1.add("health"); 29 | pointListV1.add("configprops"); 30 | pointListV1.add("info"); 31 | pointListV1.add("loggers"); 32 | pointListV1.add("threaddump"); 33 | pointListV1.add("metrics"); 34 | pointListV1.add("trace"); 35 | pointListV1.add("env/spring.jmx.enabled"); 36 | 37 | 38 | pointListV2.add("actuator/auditevents"); 39 | pointListV2.add("actuator/beans"); 40 | pointListV2.add("actuator/health"); 41 | pointListV2.add("actuator/conditions"); 42 | pointListV2.add("actuator/configprops"); 43 | pointListV2.add("actuator/info"); 44 | pointListV2.add("actuator/loggers"); 45 | pointListV2.add("actuator/threaddump"); 46 | pointListV2.add("actuator/metrics"); 47 | pointListV2.add("actuator/httptrace"); 48 | pointListV2.add("actuator/mappings"); 49 | pointListV2.add("actuator/jolokia"); 50 | pointListV2.add("actuator/hystrix.stream"); 51 | pointListV2.add("actuator/env/spring.jmx.enabled"); 52 | 53 | pointListV2.add("monitor/auditevents"); 54 | pointListV2.add("monitor/beans"); 55 | pointListV2.add("monitor/conditions"); 56 | pointListV2.add("monitor/configprops"); 57 | pointListV2.add("monitor/env"); 58 | pointListV2.add("monitor/info"); 59 | pointListV2.add("monitor/loggers"); 60 | pointListV2.add("monitor/heapdump"); 61 | pointListV2.add("monitor/threaddump"); 62 | pointListV2.add("monitor/metrics"); 63 | pointListV2.add("monitor/scheduledtasks"); 64 | pointListV2.add("monitor/httptrace"); 65 | pointListV2.add("monitor/mappings"); 66 | pointListV2.add("monitor/jolokia"); 67 | pointListV2.add("monitor/hystrix.stream"); 68 | } 69 | 70 | @Override 71 | public String getInfoDetectorTabCaption() { 72 | return "SpringBoot Actuators Info探测"; 73 | } 74 | 75 | @Override 76 | public IArgsUsageBinder getInfoDetectorCustomArgs() { 77 | return null; 78 | } 79 | 80 | @Override 81 | public LinkedHashMap doDetect(ITarget target, Map customArgs, IResultOutput result) throws Throwable { 82 | LinkedHashMap infos = new LinkedHashMap(); 83 | 84 | String address = target.getRootAddress(); 85 | try { 86 | if (SpringbootUtils.SpringbootCheck(address)){ 87 | result.successPrintln("检测到springboot 404特征!"); 88 | } 89 | 90 | if (SpringbootUtils.check404(address)){ 91 | result.errorPrintln("默认404页面返回200,无法准确爆破!"); 92 | } 93 | }catch (Exception e){ 94 | result.errorPrintln(e.toString()); 95 | } 96 | checkActuatorPointV1(address, result, infos); 97 | checkEnvPointV1(address, result, infos); 98 | if (!SpringbootVersionV1){ 99 | checkActuatorPointV2(address, result, infos); 100 | checkEnvPointV2(address, result, infos); 101 | } 102 | checkJolokiaActuatorPoint(address, result, infos); 103 | checkJolokiaListPoint(address, result, infos); 104 | checkBasicPoint(address, result, infos); 105 | checkH2(address, result, infos); 106 | checkJenkins(address, result, infos); 107 | result.infoPrintln("检测流程结束"); 108 | 109 | return infos; 110 | } 111 | 112 | // Spring Boot env端点存在环境属性覆盖和XStream反序列化漏洞 113 | private void checkEnvPointV1(String addr,IResultOutput result,LinkedHashMap infos){ 114 | final String url = addr+"env"; 115 | result.infoPrintln("正在检测:\t"+url); 116 | RawResponse response = getResponse(url); 117 | String resp = SpringbootUtils.scannerOutput(new Scanner(response.body())); 118 | if (response.statusCode() ==200){ 119 | SpringbootVersionV1 = true; 120 | result.successPrintln("检测到env端点,Springboot 1.x: "+ url); 121 | infos.put(url, "Success"); 122 | properties = SpringbootUtils.EnvParser(resp); 123 | parseProperties(properties, result); 124 | if (resp.contains("spring.cloud.bootstrap.location")){ 125 | result.successPrintln(" [*]检测到spring.cloud.bootstrap.location属性,可进行环境属性覆盖RCE!"); 126 | }else if(resp.contains("eureka.client.serviceUrl.defaultZone")){ 127 | result.successPrintln(" [*]检测到eureka.client.serviceUrl.defaultZone属性,可进行XStream反序列化RCE!"); 128 | }else if (resp.contains("spring.h2.console.enabled")){ 129 | result.successPrintln(" [*]检测到配置了H2 console属性,可能可以进行h2反序列化RCE!"); 130 | } 131 | } 132 | } 133 | 134 | // Spring Boot 2.x版本存在H2配置不当导致的RCE,目前非正则判断,测试阶段 135 | private void checkEnvPointV2(String addr,IResultOutput result,LinkedHashMap infos){ 136 | final String url = addr+"actuator/env"; 137 | result.infoPrintln("正在检测:\t"+url); 138 | RawResponse response = getResponse(url); 139 | String resp = SpringbootUtils.scannerOutput(new Scanner(response.body())); 140 | //String resp = response.readToText(); 141 | if (response.statusCode() ==200){ 142 | infos.put(url, "Success"); 143 | result.successPrintln("检测到 env端点,Springboot 2.x: "+ url); 144 | properties = SpringbootUtils.EnvParser(resp); 145 | parseProperties(properties, result); 146 | if (resp.contains("spring.cloud.bootstrap.location")){ 147 | result.successPrintln(" [*]检测到spring.cloud.bootstrap.location属性,可进行环境属性覆盖RCE!"); 148 | }else if(resp.contains("eureka.client.serviceUrl.defaultZone")){ 149 | result.successPrintln(" [*]检测到eureka.client.serviceUrl.defaultZone属性,可进行XStream反序列化RCE!"); 150 | }else if (resp.contains("spring.h2.console.enabled")){ 151 | result.successPrintln(" [*]检测到配置了H2 console属性,可能可以进行h2反序列化RCE!"); 152 | } 153 | } 154 | RawResponse h2Response = Requests.post(addr + "actuator/restart") 155 | .verify(false) 156 | .headers(h2Headers) 157 | .send(); 158 | result.infoPrintln("正在检测:\t"+addr + "actuator/restart"); 159 | if (h2Response.statusCode() == 200){ 160 | infos.put(addr + "actuator/restart", "Success"); 161 | result.successPrintln(" [*]检测到env restart端点,可进行H2 RCE!"); 162 | } 163 | } 164 | 165 | // Spring Boot 1.x版本端点在根URL下注册。 166 | private void checkActuatorPointV1(String addr,IResultOutput result,LinkedHashMap infos){ 167 | for (String point: pointListV1){ 168 | if (checkPoint(addr, point, result, infos)){ 169 | SpringbootVersionV1 = true; 170 | } 171 | } 172 | } 173 | 174 | // Spring Boot 2.x版本端点移动到/actuator/路径 175 | private void checkActuatorPointV2(String addr,IResultOutput result,LinkedHashMap infos){ 176 | for (String point: pointListV1){ 177 | checkPoint(addr, point, result, infos); 178 | } 179 | } 180 | 181 | private void checkBasicPoint(String addr,IResultOutput result,LinkedHashMap infos){ 182 | for (String point: basicPoint){ 183 | checkPoint(addr, point, result, infos); 184 | } 185 | } 186 | 187 | private void checkJolokiaListPoint(String addr,IResultOutput result,LinkedHashMap infos){ 188 | final String url = addr+"jolokia/list"; 189 | RawResponse response = getResponse(url); 190 | result.infoPrintln("正在检测:\t"+url); 191 | checkJolokiaPoint(response, url, result, infos); 192 | } 193 | 194 | private void checkJolokiaActuatorPoint(String addr, IResultOutput result,LinkedHashMap infos){ 195 | final String url = addr+"actuator/jolokia/list"; 196 | RawResponse response = getResponse(url); 197 | result.infoPrintln("正在检测:\t"+url); 198 | checkJolokiaPoint(response, url, result, infos); 199 | } 200 | 201 | private void checkH2(String addr, IResultOutput result,LinkedHashMap infos){ 202 | final String h2Url1 = addr+"h2"; 203 | final String h2Url2 = addr+"h2-console"; 204 | result.infoPrintln("正在检测:\t"+h2Url1); 205 | result.infoPrintln("正在检测:\t"+h2Url2); 206 | if(getResponse(h2Url1).statusCode()==200){ 207 | infos.put(h2Url1, "Success"); 208 | result.successPrintln("[*]检测到存在h2 console!"); 209 | result.successPrintln(h2Url1); 210 | }else if(getResponse(h2Url2).statusCode()==200){ 211 | infos.put(h2Url2, "Success"); 212 | result.successPrintln("[*]检测到存在h2 console!"); 213 | result.successPrintln(h2Url2); 214 | } 215 | } 216 | 217 | private void checkJenkins(String addr, IResultOutput result,LinkedHashMap infos) { 218 | final String jenkinsUrl = addr+"jenkins"; 219 | result.infoPrintln("正在检测:\t"+jenkinsUrl); 220 | if (getResponse(jenkinsUrl).statusCode()==200){ 221 | infos.put(jenkinsUrl, "Success"); 222 | result.successPrintln("[*]检测到存在Jenkins!"); 223 | result.successPrintln(jenkinsUrl); 224 | } 225 | } 226 | 227 | private void checkJolokiaPoint(RawResponse response,String url,IResultOutput result, LinkedHashMap infos){ 228 | if (response.statusCode() ==200){ 229 | String resp = SpringbootUtils.scannerOutput(new Scanner(response.body())); 230 | result.successPrintln("检测到 jolokia端点: "+ url); 231 | infos.put(url, "success"); 232 | if (resp.contains("reloadByURL")){ 233 | result.successPrintln(" [*]检测到reloadByURL方法,可进行XXE/RCE!"); 234 | }else if(resp.contains("createJNDIRealm")){ 235 | result.successPrintln(" [*]检测到createJNDIRealm方法,可进行JNDI注入!"); 236 | } 237 | } 238 | } 239 | 240 | private boolean checkPoint(String addr,String point,IResultOutput result, LinkedHashMap infos){ 241 | final String url = addr+point; 242 | RawResponse response = getResponse(url); 243 | result.infoPrintln("正在检测:\t"+url); 244 | if (response.statusCode()==200){ 245 | if (SpringbootUtils.checkPoint(url, response)){ 246 | result.successPrintln("检测到 "+ point +"端点,已做验证: "+ url); 247 | infos.put(url, "success"); 248 | return true; 249 | }else { 250 | infos.put(url, "maybe"); 251 | result.infoPrintln("检测到 "+ point +"端点,请手工验证: "+ url); 252 | } 253 | 254 | } 255 | return false; 256 | } 257 | 258 | private RawResponse getResponse(String url){ 259 | return Requests.get(url).verify(false).timeout(10000).send(); 260 | } 261 | private void parseProperties(PropertiesBean properties, IResultOutput result){ 262 | if (properties.getHaveInfo()){ 263 | result.infoPrintln("\tJVM信息:\t\t"+properties.getJvmName()); 264 | result.infoPrintln("\t端口信息:\t\t"+properties.getServerPort()); 265 | result.infoPrintln("\tJava版本:\t\t"+properties.getJavaVersion()); 266 | result.infoPrintln("\t用户名:\t\t"+properties.getUserName()); 267 | } 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/SpringBootActuatorsInfoPlugin.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin; 2 | 3 | import me.gv7.woodpecker.plugin.exploits.SpringBootActuatorsInfoExploit; 4 | import me.gv7.woodpecker.plugin.pocs.SpringBootPocs; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class SpringBootActuatorsInfoPlugin implements IVulPlugin { 10 | public static IVulPluginCallbacks callbacks; 11 | public static IPluginHelper pluginHelper; 12 | 13 | @Override 14 | public void VulPluginMain(IVulPluginCallbacks vulPluginCallbacks) { 15 | SpringBootActuatorsInfoPlugin.callbacks = vulPluginCallbacks; 16 | SpringBootActuatorsInfoPlugin.pluginHelper = callbacks.getPluginHelper(); 17 | 18 | callbacks.setVulPluginName("Springboot接口探测"); 19 | callbacks.setVulPluginVersion("0.1.0"); 20 | callbacks.setVulPluginAuthor("notyeat"); 21 | callbacks.setVulCVSS(9.5); 22 | callbacks.setVulName("Springboot接口探测"); 23 | callbacks.setVulDescription("接口信息泄露,某些敏感接口会造成RCE"); 24 | callbacks.setVulCategory("RCE"); 25 | callbacks.setVulProduct("SpringBoot"); 26 | callbacks.setVulId("woodpecker-2020-1021"); 27 | callbacks.setVulSeverity("high"); 28 | 29 | List exploitList = new ArrayList<>(); 30 | exploitList.add(new SpringBootActuatorsInfoExploit()); 31 | callbacks.registerExploit(exploitList); 32 | 33 | final List payloadGeneratorList = new ArrayList(); 34 | callbacks.registerPayloadGenerator(payloadGeneratorList); 35 | callbacks.registerPoc(new SpringBootPocs()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/SpringBootInfoDetecPlugin.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin; 2 | 3 | import me.gv7.woodpecker.plugin.InfoDetec.SpringBootInfoDetec; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class SpringBootInfoDetecPlugin implements InfoDetectorPlugin{ 9 | 10 | IPluginHelper pluginHelper; 11 | 12 | @Override 13 | public void InfoDetectorPluginMain(InfoDetectorPluginCallbacks callbacks) { 14 | pluginHelper = callbacks.getPluginHelper(); 15 | callbacks.setInfoDetectorPluginAuthor("Ppsoft1991"); 16 | callbacks.setInfoDetectorPluginName("SpringBoot漏洞检测"); 17 | callbacks.setInfoDetectorPluginDescription("通过检测路径来判断Springboot漏洞"); 18 | callbacks.setInfoDetectorPluginVersion("woodpecker-2020-1021"); 19 | List infoDetecList = new ArrayList<>(); 20 | 21 | infoDetecList.add(new SpringBootInfoDetec()); 22 | callbacks.registerInfoDetector(infoDetecList); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/SpringBootJolokiaPlugin.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin; 2 | 3 | import me.gv7.woodpecker.plugin.exploits.SpringBootJolokiaExploit; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class SpringBootJolokiaPlugin implements IVulPlugin { 9 | public static IVulPluginCallbacks callbacks; 10 | public static IPluginHelper pluginHelper; 11 | 12 | @Override 13 | public void VulPluginMain(IVulPluginCallbacks vulPluginCallbacks) { 14 | SpringBootJolokiaPlugin.callbacks = vulPluginCallbacks; 15 | SpringBootJolokiaPlugin.pluginHelper = callbacks.getPluginHelper(); 16 | callbacks.setVulPluginName("SpringBoot Jolokia 反序列化"); 17 | callbacks.setVulPluginVersion("0.1.0"); 18 | callbacks.setVulPluginAuthor("Frost Blue"); 19 | callbacks.setVulCVSS(9.5); 20 | callbacks.setVulName("SpringBoot Jolokia 反序列化"); 21 | callbacks.setVulDescription("反序列化可直接造成RCE"); 22 | callbacks.setVulCategory("RCE"); 23 | callbacks.setVulProduct("SpringBoot"); 24 | callbacks.setVulSeverity("high"); 25 | 26 | List exploitList = new ArrayList(); 27 | exploitList.add(new SpringBootJolokiaExploit()); 28 | callbacks.registerExploit(exploitList); 29 | 30 | final List payloadGeneratorList = new ArrayList(); 31 | //payloadGeneratorList.add(new SeeyonHOSUploadPayloadGenerate()); 32 | //callbacks.registerPoc(new SeeyonPoc()); 33 | callbacks.registerPayloadGenerator(payloadGeneratorList); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/SpringbootUtils.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import me.gv7.woodpecker.plugin.Bean.PropertiesBean; 7 | import me.gv7.woodpecker.requests.RawResponse; 8 | import me.gv7.woodpecker.requests.Requests; 9 | 10 | import java.util.Map; 11 | import java.util.Scanner; 12 | import java.util.UUID; 13 | 14 | public class SpringbootUtils { 15 | 16 | public static boolean SpringbootCheck(String addr){ 17 | final String url = addr+"404"; 18 | RawResponse response = Requests.get(url).verify(false).timeout(10000).send(); 19 | final int statusCode = response.statusCode(); 20 | final String respText = response.readToText(); 21 | if (statusCode == 404 || statusCode == 403){ 22 | return respText.contains("Whitelabel Error Page") || respText.contains("There was an unexpected error"); 23 | } 24 | return false; 25 | } 26 | 27 | public static boolean check404(String addr){ 28 | final String url = addr + UUID.randomUUID(); 29 | RawResponse response = Requests.get(url).verify(false).timeout(10000).send(); 30 | final int statusCode = response.statusCode(); 31 | return statusCode == 200; 32 | } 33 | 34 | // https://blog.csdn.net/testcs_dn/article/details/79033009/ 35 | public static boolean checkPoint(String url, RawResponse resp){ 36 | String s = resp.readToText(); 37 | // hystrix.stream 38 | if (url.contains("hystrix.stream")){ 39 | return "ping:".contains(s)||"data:".contains(s); 40 | // health 41 | }else if (url.contains("health")){ 42 | try { 43 | Map res = (Map)JSON.parse(s); 44 | return res.containsKey("status")||res.containsKey("diskSpace"); 45 | }catch (Exception e){ 46 | return false; 47 | } 48 | // beans 49 | }else if (url.contains("beans")){ 50 | Map res; 51 | try { 52 | JSONArray objects = JSON.parseArray(s); 53 | for (Object o:objects){ 54 | res = (Map)o; 55 | return res.containsKey("bean")||res.containsKey("scope")||res.containsKey("dependencies"); 56 | } 57 | }catch (Exception e){ 58 | return false; 59 | } 60 | // configprops 61 | }else if (url.contains("configprops")){ 62 | try { 63 | Map res = (Map)JSON.parse(s); 64 | return res.containsKey("configurationPropertiesReportEndpoint"); 65 | }catch (Exception e){ 66 | return false; 67 | } 68 | }else if (url.contains("mappings")){ 69 | return s.contains("bean")||s.contains("method"); 70 | }else if (url.contains("metrics")){ 71 | return s.contains("threads")||s.contains("heap"); 72 | } 73 | return false; 74 | } 75 | 76 | public static String scannerOutput(Scanner scanner){ 77 | StringBuilder builder = new StringBuilder(); 78 | while (scanner.hasNext()){ 79 | builder.append(scanner.nextLine()).append("\n"); 80 | } 81 | return builder.toString(); 82 | } 83 | 84 | public static PropertiesBean EnvParser(String result){ 85 | PropertiesBean propertiesBean = new PropertiesBean(); 86 | try { 87 | JSONObject jsonObject = JSON.parseObject(result); 88 | JSONArray propertySources = jsonObject.getJSONArray("propertySources"); 89 | JSONObject propertySource; 90 | if (propertySources.size() > 0) { 91 | for (int i = 0; i < propertySources.size(); i++) { 92 | propertySource = propertySources.getJSONObject(i); 93 | String name = (String) propertySource.get("name"); 94 | if ("systemProperties".equals(name)) { 95 | JSONObject properties = propertySource.getJSONObject("properties"); 96 | String jvmName = properties.getJSONObject("java.vm.name").getString("value"); 97 | String javaVersion = properties.getJSONObject("java.runtime.version").getString("value"); 98 | String userName = properties.getJSONObject("user.name").getString("value"); 99 | propertiesBean.setHaveInfo(true); 100 | propertiesBean.setJvmName(jvmName); 101 | propertiesBean.setJavaVersion(javaVersion); 102 | propertiesBean.setUserName(userName); 103 | } else if ("server.ports".equals(name)) { 104 | propertiesBean.setHaveInfo(true); 105 | JSONObject properties = propertySource.getJSONObject("properties"); 106 | String serverPort = properties.getJSONObject("local.server.port").getString("value"); 107 | propertiesBean.setServerPort(serverPort); 108 | } 109 | 110 | } 111 | 112 | } 113 | }catch (Exception e){ 114 | propertiesBean.setHaveInfo(false); 115 | } 116 | return propertiesBean; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/WoodpeckerPluginManager.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin; 2 | 3 | public class WoodpeckerPluginManager implements IPluginManager { 4 | public void registerPluginManagerCallbacks(IPluginManagerCallbacks pluginManagerCallbacks) { 5 | //pluginManagerCallbacks.registerVulPlugin(new SpringBootActuatorsInfoPlugin()); 6 | pluginManagerCallbacks.registerInfoDetectorPlugin(new SpringBootInfoDetecPlugin()); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/exploits/SpringBootActuatorsInfoExploit.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin.exploits; 2 | 3 | import me.gv7.woodpecker.plugin.*; 4 | import me.gv7.woodpecker.plugin.Bean.PropertiesBean; 5 | import me.gv7.woodpecker.requests.RawResponse; 6 | import me.gv7.woodpecker.requests.Requests; 7 | 8 | import java.util.*; 9 | 10 | public class SpringBootActuatorsInfoExploit implements IExploit { 11 | List pointListV1 = new ArrayList<>(); 12 | List pointListV2 = new ArrayList<>(); 13 | Map h2Headers = new HashMap<>(); 14 | boolean SpringbootVersionV1 = false; 15 | String[] basicPoint = new String[]{"cloudfoundryapplication","hystrix.stream" }; 16 | PropertiesBean properties; 17 | 18 | public SpringBootActuatorsInfoExploit(){ 19 | h2Headers.put("Cache-Control", "max-age=0"); 20 | 21 | pointListV1.add("autoconfig"); 22 | pointListV1.add("heapdump"); 23 | pointListV1.add("dump"); 24 | pointListV1.add("mappings"); 25 | pointListV1.add("auditevents"); 26 | pointListV1.add("beans"); 27 | pointListV1.add("health"); 28 | pointListV1.add("configprops"); 29 | pointListV1.add("info"); 30 | pointListV1.add("loggers"); 31 | pointListV1.add("threaddump"); 32 | pointListV1.add("metrics"); 33 | pointListV1.add("trace"); 34 | pointListV1.add("env/spring.jmx.enabled"); 35 | 36 | 37 | pointListV2.add("actuator/auditevents"); 38 | pointListV2.add("actuator/beans"); 39 | pointListV2.add("actuator/health"); 40 | pointListV2.add("actuator/conditions"); 41 | pointListV2.add("actuator/configprops"); 42 | pointListV2.add("actuator/info"); 43 | pointListV2.add("actuator/loggers"); 44 | pointListV2.add("actuator/threaddump"); 45 | pointListV2.add("actuator/metrics"); 46 | pointListV2.add("actuator/httptrace"); 47 | pointListV2.add("actuator/mappings"); 48 | pointListV2.add("actuator/jolokia"); 49 | pointListV2.add("actuator/hystrix.stream"); 50 | pointListV2.add("actuator/env/spring.jmx.enabled"); 51 | 52 | pointListV2.add("monitor/auditevents"); 53 | pointListV2.add("monitor/beans"); 54 | pointListV2.add("monitor/conditions"); 55 | pointListV2.add("monitor/configprops"); 56 | pointListV2.add("monitor/env"); 57 | pointListV2.add("monitor/info"); 58 | pointListV2.add("monitor/loggers"); 59 | pointListV2.add("monitor/heapdump"); 60 | pointListV2.add("monitor/threaddump"); 61 | pointListV2.add("monitor/metrics"); 62 | pointListV2.add("monitor/scheduledtasks"); 63 | pointListV2.add("monitor/httptrace"); 64 | pointListV2.add("monitor/mappings"); 65 | pointListV2.add("monitor/jolokia"); 66 | pointListV2.add("monitor/hystrix.stream"); 67 | } 68 | 69 | @Override 70 | public String getExploitTabCaption() { 71 | return "SpringBoot Actuators Info探测"; 72 | } 73 | 74 | @Override 75 | public IArgsUsageBinder getExploitCustomArgs() { 76 | return null; 77 | } 78 | 79 | @Override 80 | public void doExploit(ITarget target, Map customArgs, IResultOutput result) { 81 | String address = target.getRootAddress(); 82 | try { 83 | if (SpringbootUtils.SpringbootCheck(address)){ 84 | result.successPrintln("检测到springboot 404特征!"); 85 | } 86 | 87 | if (SpringbootUtils.check404(address)){ 88 | result.errorPrintln("默认404页面返回200,无法准确爆破!"); 89 | } 90 | }catch (Exception e){ 91 | result.errorPrintln(e.toString()); 92 | } 93 | checkActuatorPointV1(address, result); 94 | checkEnvPointV1(address, result); 95 | if (!SpringbootVersionV1){ 96 | checkActuatorPointV2(address, result); 97 | checkEnvPointV2(address, result); 98 | } 99 | checkJolokiaActuatorPoint(address, result); 100 | checkJolokiaListPoint(address, result); 101 | checkBasicPoint(address, result); 102 | checkH2(address, result); 103 | checkJenkins(address, result); 104 | result.infoPrintln("检测流程结束"); 105 | } 106 | 107 | // Spring Boot env端点存在环境属性覆盖和XStream反序列化漏洞 108 | private void checkEnvPointV1(String addr,IResultOutput result){ 109 | final String url = addr+"env"; 110 | RawResponse response = getResponse(url); 111 | String resp = SpringbootUtils.scannerOutput(new Scanner(response.body())); 112 | if (response.statusCode() ==200){ 113 | SpringbootVersionV1 = true; 114 | result.successPrintln("检测到env端点,Springboot 1.x: "+ url); 115 | properties = SpringbootUtils.EnvParser(resp); 116 | parseProperties(properties, result); 117 | if (resp.contains("spring.cloud.bootstrap.location")){ 118 | result.successPrintln(" [*]检测到spring.cloud.bootstrap.location属性,可进行环境属性覆盖RCE!"); 119 | }else if(resp.contains("eureka.client.serviceUrl.defaultZone")){ 120 | result.successPrintln(" [*]检测到eureka.client.serviceUrl.defaultZone属性,可进行XStream反序列化RCE!"); 121 | }else if (resp.contains("spring.h2.console.enabled")){ 122 | result.successPrintln(" [*]检测到配置了H2 console属性,可能可以进行h2反序列化RCE!"); 123 | } 124 | } 125 | } 126 | 127 | // Spring Boot 2.x版本存在H2配置不当导致的RCE,目前非正则判断,测试阶段 128 | private void checkEnvPointV2(String addr,IResultOutput result){ 129 | final String url = addr+"actuator/env"; 130 | RawResponse response = getResponse(url); 131 | String resp = SpringbootUtils.scannerOutput(new Scanner(response.body())); 132 | //String resp = response.readToText(); 133 | if (response.statusCode() ==200){ 134 | result.successPrintln("检测到 env端点,Springboot 2.x: "+ url); 135 | properties = SpringbootUtils.EnvParser(resp); 136 | parseProperties(properties, result); 137 | if (resp.contains("spring.cloud.bootstrap.location")){ 138 | result.successPrintln(" [*]检测到spring.cloud.bootstrap.location属性,可进行环境属性覆盖RCE!"); 139 | }else if(resp.contains("eureka.client.serviceUrl.defaultZone")){ 140 | result.successPrintln(" [*]检测到eureka.client.serviceUrl.defaultZone属性,可进行XStream反序列化RCE!"); 141 | }else if (resp.contains("spring.h2.console.enabled")){ 142 | result.successPrintln(" [*]检测到配置了H2 console属性,可能可以进行h2反序列化RCE!"); 143 | } 144 | } 145 | RawResponse h2Response = Requests.post(addr + "actuator/restart") 146 | .verify(false) 147 | .headers(h2Headers) 148 | .send(); 149 | if (h2Response.statusCode() == 200){ 150 | result.successPrintln(" [*]检测到env restart端点,可进行H2 RCE!"); 151 | } 152 | } 153 | 154 | // Spring Boot 1.x版本端点在根URL下注册。 155 | private void checkActuatorPointV1(String addr,IResultOutput result){ 156 | for (String point: pointListV1){ 157 | if (checkPoint(addr, point, result)){ 158 | SpringbootVersionV1 = true; 159 | } 160 | } 161 | } 162 | 163 | // Spring Boot 2.x版本端点移动到/actuator/路径 164 | private void checkActuatorPointV2(String addr,IResultOutput result){ 165 | for (String point: pointListV1){ 166 | checkPoint(addr, point, result); 167 | } 168 | } 169 | 170 | private void checkBasicPoint(String addr,IResultOutput result){ 171 | for (String point: basicPoint){ 172 | checkPoint(addr, point, result); 173 | } 174 | } 175 | 176 | private void checkJolokiaListPoint(String addr,IResultOutput result){ 177 | final String url = addr+"jolokia/list"; 178 | RawResponse response = getResponse(url); 179 | checkJolokiaPoint(response, url, result); 180 | } 181 | 182 | private void checkJolokiaActuatorPoint(String addr, IResultOutput result){ 183 | final String url = addr+"actuator/jolokia/list"; 184 | RawResponse response = getResponse(url); 185 | checkJolokiaPoint(response, url, result); 186 | } 187 | 188 | private void checkH2(String addr, IResultOutput result){ 189 | final String h2Url1 = addr+"h2"; 190 | final String h2Url2 = addr+"h2-console"; 191 | if(getResponse(h2Url1).statusCode()==200){ 192 | result.successPrintln("[*]检测到存在h2 console!"); 193 | result.successPrintln(h2Url1); 194 | }else if(getResponse(h2Url2).statusCode()==200){ 195 | result.successPrintln("[*]检测到存在h2 console!"); 196 | result.successPrintln(h2Url2); 197 | } 198 | } 199 | 200 | private void checkJenkins(String addr, IResultOutput result) { 201 | final String jenkinsUrl = addr+"jenkins"; 202 | if (getResponse(jenkinsUrl).statusCode()==200){ 203 | result.successPrintln("[*]检测到存在Jenkins!"); 204 | result.successPrintln(jenkinsUrl); 205 | } 206 | } 207 | 208 | private void checkJolokiaPoint(RawResponse response,String url,IResultOutput result){ 209 | if (response.statusCode() ==200){ 210 | String resp = SpringbootUtils.scannerOutput(new Scanner(response.body())); 211 | result.successPrintln("检测到 jolokia端点: "+ url); 212 | if (resp.contains("reloadByURL")){ 213 | result.successPrintln(" [*]检测到reloadByURL方法,可进行XXE/RCE!"); 214 | }else if(resp.contains("createJNDIRealm")){ 215 | result.successPrintln(" [*]检测到createJNDIRealm方法,可进行JNDI注入!"); 216 | } 217 | } 218 | } 219 | 220 | private boolean checkPoint(String addr,String point,IResultOutput result){ 221 | final String url = addr+point; 222 | RawResponse response = getResponse(url); 223 | if (response.statusCode()==200){ 224 | if (SpringbootUtils.checkPoint(url, response)){ 225 | result.successPrintln("检测到 "+ point +"端点,已做验证: "+ url); 226 | return true; 227 | }else { 228 | result.infoPrintln("检测到 "+ point +"端点,请手工验证: "+ url); 229 | } 230 | 231 | } 232 | return false; 233 | } 234 | 235 | private RawResponse getResponse(String url){ 236 | return Requests.get(url).verify(false).timeout(10000).send(); 237 | } 238 | private void parseProperties(PropertiesBean properties, IResultOutput result){ 239 | if (properties.getHaveInfo()){ 240 | result.infoPrintln("\tJVM信息:\t\t"+properties.getJvmName()); 241 | result.infoPrintln("\t端口信息:\t\t"+properties.getServerPort()); 242 | result.infoPrintln("\tJava版本:\t\t"+properties.getJavaVersion()); 243 | result.infoPrintln("\t用户名:\t\t"+properties.getUserName()); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/exploits/SpringBootJolokiaExploit.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin.exploits; 2 | 3 | import me.gv7.woodpecker.plugin.*; 4 | import java.util.Map; 5 | 6 | public class SpringBootJolokiaExploit implements IExploit { 7 | @Override 8 | public String getExploitTabCaption() { 9 | return "SpringBoot Jolokia RCE"; 10 | } 11 | 12 | @Override 13 | public IArgsUsageBinder getExploitCustomArgs() { 14 | return null; 15 | } 16 | 17 | @Override 18 | public void doExploit(ITarget target, Map customArgs, IResultOutput result) { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/gv7/woodpecker/plugin/pocs/SpringBootPocs.java: -------------------------------------------------------------------------------- 1 | package me.gv7.woodpecker.plugin.pocs; 2 | 3 | import me.gv7.woodpecker.plugin.*; 4 | import me.gv7.woodpecker.plugin.exploits.SpringBootActuatorsInfoExploit; 5 | import net.dongliu.requests.Requests; 6 | 7 | public class SpringBootPocs implements IPoc { 8 | @Override 9 | public IScanResult doVerify(ITarget target, IResultOutput resultOutput) { 10 | IScanResult result = SpringBootActuatorsInfoPlugin.pluginHelper.createScanResult(); 11 | final String[] actuator = {"actuator/env", "env"}; 12 | if (SpringbootUtils.SpringbootCheck(target.getAddress())){ 13 | for (String a:actuator){ 14 | String url = target.getAddress()+a; 15 | if (Requests.post(url).verify(false).send().statusCode()==200){ 16 | resultOutput.infoPrintln("[+] "+url+"存在env端点!\n"); 17 | } 18 | } 19 | result.setExists(true); 20 | } 21 | return result; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/Bean/PropertiesBean.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/Bean/PropertiesBean.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/SpringBootActuatorsInfoPlugin.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/SpringBootActuatorsInfoPlugin.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/SpringBootJolokiaPlugin.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/SpringBootJolokiaPlugin.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/SpringbootUtils.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/SpringbootUtils.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/WoodpeckerPluginManager.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/WoodpeckerPluginManager.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/exploits/SpringBootActuatorsInfoExploit.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/exploits/SpringBootActuatorsInfoExploit.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/exploits/SpringBootJolokiaExploit.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/exploits/SpringBootJolokiaExploit.class -------------------------------------------------------------------------------- /target/classes/me/gv7/woodpecker/plugin/pocs/SpringBootPocs.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woodpecker-appstore/springboot-vuldb/838f70e35049d68a84f6f3cd46b941865e6c8a4a/target/classes/me/gv7/woodpecker/plugin/pocs/SpringBootPocs.class --------------------------------------------------------------------------------