mirror of
https://github.com/qi4L/JYso.git
synced 2026-09-22 06:50:42 +08:00
perf:前端大改1
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"liquid-glass-react": "^1.1.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.28.0"
|
||||
|
||||
@@ -44,6 +44,14 @@ export function startServers(data) {
|
||||
return api.post('/servers/start', data)
|
||||
}
|
||||
|
||||
export function stopServer(server) {
|
||||
return api.post('/servers/stop', { server })
|
||||
}
|
||||
|
||||
export function toggleServer(server) {
|
||||
return api.post('/servers/toggle', { server })
|
||||
}
|
||||
|
||||
export function getGadgets() {
|
||||
return api.get('/gadgets')
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
--card-radius: 24px;
|
||||
--btn-radius: 13px;
|
||||
--input-radius: 13px;
|
||||
--card-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
--card-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.04), 0 2px 6px rgba(0, 0, 0, 0.06), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
|
||||
--card-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.05), 0 3px 10px rgba(0, 0, 0, 0.07), 0 0 0 1px rgba(0, 0, 0, 0.04);
|
||||
--card-bg: rgba(255, 255, 255, 0.72);
|
||||
--font-mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace;
|
||||
--transition-fast: 200ms cubic-bezier(0.22, 0.1, 0.22, 1);
|
||||
--transition-fluid: 420ms cubic-bezier(0.22, 0.1, 0.22, 1);
|
||||
@@ -50,6 +54,10 @@
|
||||
--success-bg: rgba(48, 209, 88, 0.10);
|
||||
--danger: #FF453A;
|
||||
--danger-bg: rgba(255, 69, 58, 0.08);
|
||||
--card-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.20), 0 1px 2px rgba(0, 0, 0, 0.30);
|
||||
--card-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.24), 0 2px 6px rgba(0, 0, 0, 0.32), 0 0 0 0.5px rgba(255, 255, 255, 0.06);
|
||||
--card-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.28), 0 3px 10px rgba(0, 0, 0, 0.36), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
--card-bg: rgba(28, 28, 30, 0.68);
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -171,6 +179,89 @@ code, pre, .mono {
|
||||
|
||||
.glass-card {
|
||||
padding: 28px;
|
||||
background: var(--card-bg);
|
||||
backdrop-filter: blur(28px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(180%);
|
||||
border-radius: var(--card-radius);
|
||||
box-shadow: var(--card-shadow-md);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: background var(--transition-fluid), box-shadow var(--transition-fluid), border-color var(--transition-fluid), transform var(--transition-fluid);
|
||||
}
|
||||
|
||||
.mode-segment-control {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 3px;
|
||||
gap: 2px;
|
||||
transition: background var(--transition-fluid), border-color var(--transition-fluid);
|
||||
}
|
||||
|
||||
.mode-segment-control::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: calc(50% - 4px);
|
||||
height: calc(100% - 6px);
|
||||
background: var(--accent);
|
||||
border-radius: 10px;
|
||||
transition: transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
box-shadow: 0 2px 12px var(--accent-glow);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.mode-segment-control.gadget-mode::before {
|
||||
transform: translateX(calc(100% + 2px));
|
||||
}
|
||||
|
||||
.mode-segment-btn {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
padding: 7px 16px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: color 320ms cubic-bezier(0.22, 0.1, 0.22, 1);
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.2px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mode-segment-btn:active {
|
||||
transform: scale(0.96);
|
||||
transition: transform 100ms cubic-bezier(0.22, 0.1, 0.22, 1);
|
||||
}
|
||||
|
||||
.mode-segment-btn.active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.section-enter {
|
||||
animation: sectionSlideIn 520ms cubic-bezier(0.22, 0.1, 0, 1) both;
|
||||
}
|
||||
|
||||
.section-enter:nth-child(2) { animation-delay: 40ms; }
|
||||
.section-enter:nth-child(3) { animation-delay: 80ms; }
|
||||
|
||||
@keyframes sectionSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px) scale(0.985);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.glass-card h2 {
|
||||
@@ -201,6 +292,28 @@ code, pre, .mono {
|
||||
transition: background var(--transition-fluid), border-color var(--transition-fluid);
|
||||
}
|
||||
|
||||
.status-clickable {
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-clickable:hover {
|
||||
background: var(--bg-primary);
|
||||
border-color: var(--accent);
|
||||
box-shadow: var(--card-shadow-sm);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.status-clickable:active {
|
||||
transform: scale(0.98);
|
||||
transition: transform 100ms ease;
|
||||
}
|
||||
|
||||
.status-toggling {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
@@ -389,6 +502,78 @@ code, pre, .mono {
|
||||
|
||||
.control-bar .btn { padding: 8px 16px; font-size: 13px; }
|
||||
|
||||
.tab-segment-control {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 11px;
|
||||
padding: 3px;
|
||||
gap: 2px;
|
||||
transition: background var(--transition-fluid), border-color var(--transition-fluid);
|
||||
}
|
||||
|
||||
.tab-segment-control::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: calc(50% - 4px);
|
||||
height: calc(100% - 6px);
|
||||
background: var(--accent);
|
||||
border-radius: 9px;
|
||||
transition: transform 380ms cubic-bezier(0.34, 1.50, 0.64, 1);
|
||||
box-shadow: 0 2px 10px var(--accent-glow);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.tab-segment-control.config-tab::before {
|
||||
transform: translateX(calc(100% + 2px));
|
||||
}
|
||||
|
||||
.tab-segment-btn {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 6px 20px;
|
||||
border: none;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: color 280ms cubic-bezier(0.22, 0.1, 0.22, 1);
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.2px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.tab-segment-btn:active {
|
||||
transform: scale(0.96);
|
||||
transition: transform 100ms cubic-bezier(0.22, 0.1, 0.22, 1);
|
||||
}
|
||||
|
||||
.tab-segment-btn.active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.tab-content-enter {
|
||||
animation: tabContentSlide 380ms cubic-bezier(0.22, 0.1, 0, 1) both;
|
||||
}
|
||||
|
||||
@keyframes tabContentSlide {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(6px) scale(0.99);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.config-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react'
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import LiquidGlass from 'liquid-glass-react'
|
||||
import { useTheme } from '../context/ThemeContext'
|
||||
import {
|
||||
getStatus, startServers as apiStartServers, getGadgets,
|
||||
getStatus, toggleServer, getGadgets,
|
||||
generatePayload as apiGenerate, updateConfig, setAuthToken
|
||||
} from '../api'
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate()
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
const containerRef = useRef(null)
|
||||
const [mode, setMode] = useState('jndi')
|
||||
const [animKey, setAnimKey] = useState(0)
|
||||
const [status, setStatus] = useState({})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [msg, setMsg] = useState({ success: '', error: '' })
|
||||
const [activeTab, setActiveTab] = useState('servers')
|
||||
const [toggling, setToggling] = useState(null)
|
||||
const [gadgets, setGadgets] = useState([])
|
||||
const [gadgetSearch, setGadgetSearch] = useState('')
|
||||
const [selectedGadget, setSelectedGadget] = useState('')
|
||||
const [payloadCmd, setPayloadCmd] = useState('')
|
||||
const [payloadResult, setPayloadResult] = useState('')
|
||||
|
||||
const [serverCfg, setServerCfg] = useState({
|
||||
ldap: false, ldaps: false, http: false, rmi: false
|
||||
})
|
||||
|
||||
const [configForm, setConfigForm] = useState({
|
||||
ip: '', ldapPort: 1389, ldapsPort: 1669, httpPort: 3456, rmiPort: 1099, codeBase: ''
|
||||
ip: '', ldapPort: 1389, ldapsPort: 1669, httpPort: 3456, rmiPort: 1099,
|
||||
AESkey: '123', user: '', PASSWD: '', TLSProxy: false, keyPass: '', certFile: ''
|
||||
})
|
||||
|
||||
const filteredGadgets = useMemo(() => {
|
||||
@@ -34,6 +31,13 @@ export default function Dashboard() {
|
||||
return gadgets.filter(g => g.name.toLowerCase().includes(gadgetSearch.toLowerCase()))
|
||||
}, [gadgetSearch, gadgets])
|
||||
|
||||
const switchMode = useCallback((next) => {
|
||||
if (next === mode) return
|
||||
setMode(next)
|
||||
setAnimKey(k => k + 1)
|
||||
setMsg({ success: '', error: '' })
|
||||
}, [mode])
|
||||
|
||||
async function loadStatus() {
|
||||
try {
|
||||
const res = await getStatus()
|
||||
@@ -45,7 +49,12 @@ export default function Dashboard() {
|
||||
ldapsPort: res.data.ldapsPort || 1669,
|
||||
httpPort: res.data.httpPort || 3456,
|
||||
rmiPort: res.data.rmiPort || 1099,
|
||||
codeBase: res.data.codeBase || ''
|
||||
AESkey: res.data.AESkey || '123',
|
||||
user: res.data.user || '',
|
||||
PASSWD: res.data.PASSWD || '',
|
||||
TLSProxy: res.data.TLSProxy || false,
|
||||
keyPass: res.data.keyPass || '',
|
||||
certFile: res.data.certFile || ''
|
||||
}))
|
||||
} catch (e) {
|
||||
if (e.response && e.response.status === 401) {
|
||||
@@ -62,24 +71,18 @@ export default function Dashboard() {
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
async function handleStartServers() {
|
||||
setLoading(true)
|
||||
async function handleToggleServer(server) {
|
||||
setToggling(server)
|
||||
setMsg({ success: '', error: '' })
|
||||
try {
|
||||
const res = await apiStartServers({
|
||||
ldap: serverCfg.ldap, ldaps: serverCfg.ldaps,
|
||||
http: serverCfg.http, rmi: serverCfg.rmi,
|
||||
ip: configForm.ip, ldapPort: configForm.ldapPort,
|
||||
ldapsPort: configForm.ldapsPort, httpPort: configForm.httpPort,
|
||||
rmiPort: configForm.rmiPort
|
||||
})
|
||||
const res = await toggleServer(server)
|
||||
if (res.data.success) {
|
||||
setMsg({ success: 'Servers starting: ' + (res.data.started.join(', ') || 'none'), error: '' })
|
||||
setMsg({ success: server.toUpperCase() + ' ' + (res.data.running ? 'started' : 'stopped'), error: '' })
|
||||
setStatus(res.data.status)
|
||||
}
|
||||
setTimeout(loadStatus, 2000)
|
||||
} catch (e) {
|
||||
setMsg({ success: '', error: 'Failed to start servers' })
|
||||
} finally { setLoading(false) }
|
||||
setMsg({ success: '', error: 'Failed to toggle ' + server.toUpperCase() })
|
||||
} finally { setToggling(null) }
|
||||
}
|
||||
|
||||
async function handleSaveConfig() {
|
||||
@@ -118,27 +121,31 @@ export default function Dashboard() {
|
||||
|
||||
useEffect(() => { loadStatus(); loadGadgets() }, [])
|
||||
|
||||
const liquidProps = {
|
||||
mouseContainer: containerRef,
|
||||
elasticity: 0.07,
|
||||
blurAmount: 0.042,
|
||||
saturation: 120,
|
||||
displacementScale: 36,
|
||||
cornerRadius: 24,
|
||||
overLight: theme === 'light',
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<div>
|
||||
<div className="dashboard-bg" />
|
||||
|
||||
<div className="page-shell">
|
||||
<LiquidGlass {...liquidProps} style={{ marginBottom: 20 }}>
|
||||
<div className="glass-card" style={{ marginBottom: 20 }}>
|
||||
<div className="header">
|
||||
<h1>
|
||||
<span className="logo-dot" />
|
||||
JYso
|
||||
</h1>
|
||||
<div className={'mode-segment-control' + (mode === 'gadget' ? ' gadget-mode' : '')}>
|
||||
<button
|
||||
className={'mode-segment-btn' + (mode === 'jndi' ? ' active' : '')}
|
||||
onClick={() => switchMode('jndi')}
|
||||
>
|
||||
JNDI EXP
|
||||
</button>
|
||||
<button
|
||||
className={'mode-segment-btn' + (mode === 'gadget' ? ' active' : '')}
|
||||
onClick={() => switchMode('gadget')}
|
||||
>
|
||||
Gadget
|
||||
</button>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
<button
|
||||
className="theme-toggle"
|
||||
@@ -156,136 +163,130 @@ export default function Dashboard() {
|
||||
<button className="logout-btn" onClick={logout}>Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</LiquidGlass>
|
||||
</div>
|
||||
|
||||
{msg.success && <div className="success-msg">{msg.success}</div>}
|
||||
{msg.error && <div className="error-msg">{msg.error}</div>}
|
||||
{msg.success && <div className="success-msg section-enter">{msg.success}</div>}
|
||||
{msg.error && <div className="error-msg section-enter">{msg.error}</div>}
|
||||
|
||||
<LiquidGlass {...liquidProps} style={{ marginBottom: 20 }}>
|
||||
<div className="glass-card">
|
||||
<h2>Server Status</h2>
|
||||
<div className="status-grid">
|
||||
<div className="status-item">
|
||||
<span className="status-label">LDAP ({status.ldapPort})</span>
|
||||
<span className={'status-value ' + (status.ldapRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.ldapRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">LDAPS ({status.ldapsPort})</span>
|
||||
<span className={'status-value ' + (status.ldapsRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.ldapsRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">HTTP ({status.httpPort})</span>
|
||||
<span className={'status-value ' + (status.httpRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.httpRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">RMI ({status.rmiPort})</span>
|
||||
<span className={'status-value ' + (status.rmiRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.rmiRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">IP Address</span>
|
||||
<span className="status-value" style={{ color: 'var(--accent)' }}>{status.ip || '0.0.0.0'}</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">Version</span>
|
||||
<span className="status-value" style={{ color: 'var(--accent)' }}>{status.version || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LiquidGlass>
|
||||
|
||||
<LiquidGlass {...liquidProps}>
|
||||
<div className="glass-card">
|
||||
<h2>Controls</h2>
|
||||
<div className="control-bar">
|
||||
<button className="btn btn-primary" style={{ width: 'auto' }} onClick={loadStatus}>
|
||||
Refresh Status
|
||||
</button>
|
||||
<button
|
||||
className={'btn btn-secondary' + (activeTab === 'servers' ? ' active-tab' : '')}
|
||||
onClick={() => setActiveTab('servers')}>Servers</button>
|
||||
<button
|
||||
className={'btn btn-secondary' + (activeTab === 'config' ? ' active-tab' : '')}
|
||||
onClick={() => setActiveTab('config')}>Configuration</button>
|
||||
<button
|
||||
className={'btn btn-secondary' + (activeTab === 'payload' ? ' active-tab' : '')}
|
||||
onClick={() => setActiveTab('payload')}>Payload Generator</button>
|
||||
</div>
|
||||
|
||||
{activeTab === 'servers' && (
|
||||
<div>
|
||||
<div className="grid-2">
|
||||
<label className="form-group">
|
||||
<input type="checkbox" checked={serverCfg.ldap}
|
||||
onChange={e => setServerCfg({ ...serverCfg, ldap: e.target.checked })} /> LDAP
|
||||
</label>
|
||||
<label className="form-group">
|
||||
<input type="checkbox" checked={serverCfg.ldaps}
|
||||
onChange={e => setServerCfg({ ...serverCfg, ldaps: e.target.checked })} /> LDAPS
|
||||
</label>
|
||||
<label className="form-group">
|
||||
<input type="checkbox" checked={serverCfg.http}
|
||||
onChange={e => setServerCfg({ ...serverCfg, http: e.target.checked })} /> HTTP
|
||||
</label>
|
||||
<label className="form-group">
|
||||
<input type="checkbox" checked={serverCfg.rmi}
|
||||
onChange={e => setServerCfg({ ...serverCfg, rmi: e.target.checked })} /> RMI
|
||||
</label>
|
||||
<div key={animKey}>
|
||||
{mode === 'jndi' && (
|
||||
<>
|
||||
<div className="glass-card section-enter" style={{ marginBottom: 20 }}>
|
||||
<h2>Server Status</h2>
|
||||
<div className="status-grid">
|
||||
<div className={'status-item status-clickable' + (toggling === 'ldap' ? ' status-toggling' : '')}
|
||||
onClick={() => handleToggleServer('ldap')}
|
||||
title="Click to toggle LDAP server">
|
||||
<span className="status-label">LDAP ({status.ldapPort})</span>
|
||||
<span className={'status-value ' + (status.ldapRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.ldapRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={'status-item status-clickable' + (toggling === 'ldaps' ? ' status-toggling' : '')}
|
||||
onClick={() => handleToggleServer('ldaps')}
|
||||
title="Click to toggle LDAPS server">
|
||||
<span className="status-label">LDAPS ({status.ldapsPort})</span>
|
||||
<span className={'status-value ' + (status.ldapsRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.ldapsRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={'status-item status-clickable' + (toggling === 'http' ? ' status-toggling' : '')}
|
||||
onClick={() => handleToggleServer('http')}
|
||||
title="Click to toggle HTTP server">
|
||||
<span className="status-label">HTTP ({status.httpPort})</span>
|
||||
<span className={'status-value ' + (status.httpRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.httpRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={'status-item status-clickable' + (toggling === 'rmi' ? ' status-toggling' : '')}
|
||||
onClick={() => handleToggleServer('rmi')}
|
||||
title="Click to toggle RMI server">
|
||||
<span className="status-label">RMI ({status.rmiPort})</span>
|
||||
<span className={'status-value ' + (status.rmiRunning ? 'status-online' : 'status-offline')}>
|
||||
{status.rmiRunning ? 'ONLINE' : 'OFFLINE'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">IP Address</span>
|
||||
<span className="status-value" style={{ color: 'var(--accent)' }}>{status.ip || '0.0.0.0'}</span>
|
||||
</div>
|
||||
<div className="status-item">
|
||||
<span className="status-label">Version</span>
|
||||
<span className="status-value" style={{ color: 'var(--accent)' }}>1.3.8</span>
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-primary" style={{ marginTop: 16 }} onClick={handleStartServers} disabled={loading}>
|
||||
{loading ? 'Starting...' : 'Start Selected Servers'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'config' && (
|
||||
<div>
|
||||
<div className="glass-card section-enter">
|
||||
<h2>Config</h2>
|
||||
<div className="config-form">
|
||||
<div className="form-group">
|
||||
<label>IP Address</label>
|
||||
<input type="text" value={configForm.ip}
|
||||
onChange={e => setConfigForm({ ...configForm, ip: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>LDAP Port</label>
|
||||
<input type="number" value={configForm.ldapPort}
|
||||
onChange={e => setConfigForm({ ...configForm, ldapPort: parseInt(e.target.value) || 1389 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>LDAPS Port</label>
|
||||
<input type="number" value={configForm.ldapsPort}
|
||||
onChange={e => setConfigForm({ ...configForm, ldapsPort: parseInt(e.target.value) || 1669 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>HTTP Port</label>
|
||||
<input type="number" value={configForm.httpPort}
|
||||
onChange={e => setConfigForm({ ...configForm, httpPort: parseInt(e.target.value) || 3456 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>RMI Port</label>
|
||||
<input type="number" value={configForm.rmiPort}
|
||||
onChange={e => setConfigForm({ ...configForm, rmiPort: parseInt(e.target.value) || 1099 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>CodeBase URL</label>
|
||||
<input type="text" value={configForm.codeBase} placeholder="http://x.x.x.x:port/"
|
||||
onChange={e => setConfigForm({ ...configForm, codeBase: e.target.value })} />
|
||||
</div>
|
||||
<button className="btn btn-primary" onClick={handleSaveConfig} disabled={loading}>
|
||||
{loading ? 'Saving...' : 'Save Configuration'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>IP Address</label>
|
||||
<input type="text" value={configForm.ip}
|
||||
onChange={e => setConfigForm({ ...configForm, ip: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>LDAP Port</label>
|
||||
<input type="number" value={configForm.ldapPort}
|
||||
onChange={e => setConfigForm({ ...configForm, ldapPort: parseInt(e.target.value) || 1389 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>LDAPS Port</label>
|
||||
<input type="number" value={configForm.ldapsPort}
|
||||
onChange={e => setConfigForm({ ...configForm, ldapsPort: parseInt(e.target.value) || 1669 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>HTTP Port</label>
|
||||
<input type="number" value={configForm.httpPort}
|
||||
onChange={e => setConfigForm({ ...configForm, httpPort: parseInt(e.target.value) || 3456 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>RMI Port</label>
|
||||
<input type="number" value={configForm.rmiPort}
|
||||
onChange={e => setConfigForm({ ...configForm, rmiPort: parseInt(e.target.value) || 1099 })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>AES Key</label>
|
||||
<input type="text" value={configForm.AESkey}
|
||||
onChange={e => setConfigForm({ ...configForm, AESkey: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>LDAP User</label>
|
||||
<input type="text" value={configForm.user} placeholder="ldap bind account"
|
||||
onChange={e => setConfigForm({ ...configForm, user: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>LDAP Password</label>
|
||||
<input type="password" value={configForm.PASSWD} placeholder="ldap bind password"
|
||||
onChange={e => setConfigForm({ ...configForm, PASSWD: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>JKS Key Password</label>
|
||||
<input type="password" value={configForm.keyPass} placeholder="JKS key password"
|
||||
onChange={e => setConfigForm({ ...configForm, keyPass: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>JKS Cert File</label>
|
||||
<input type="text" value={configForm.certFile} placeholder="/path/to/cert.jks"
|
||||
onChange={e => setConfigForm({ ...configForm, certFile: e.target.value })} />
|
||||
</div>
|
||||
<div className="form-group" style={{ gridColumn: 'span 2' }}>
|
||||
<label className="form-group" style={{ marginBottom: 0 }}>
|
||||
<input type="checkbox" checked={configForm.TLSProxy}
|
||||
onChange={e => setConfigForm({ ...configForm, TLSProxy: e.target.checked })} /> TLS Proxy (LDAPS Port Forwarding)
|
||||
</label>
|
||||
</div>
|
||||
<button className="btn btn-primary" onClick={handleSaveConfig} disabled={loading}>
|
||||
{loading ? 'Saving...' : 'Save Configuration'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'payload' && (
|
||||
{mode === 'gadget' && (
|
||||
<div className="glass-card section-enter">
|
||||
<h2>Payload Generator</h2>
|
||||
<div className="grid-2">
|
||||
<div>
|
||||
<h3 style={{ fontSize: 13, marginBottom: 10, color: 'var(--text-secondary)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
@@ -320,9 +321,9 @@ export default function Dashboard() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</LiquidGlass>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import LiquidGlass from 'liquid-glass-react'
|
||||
import { login, setAuthToken } from '../api'
|
||||
import { useTheme } from '../context/ThemeContext'
|
||||
|
||||
export default function Login() {
|
||||
const navigate = useNavigate()
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
const containerRef = useRef(null)
|
||||
const [username, setUsername] = useState('qi')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
@@ -44,7 +42,7 @@ export default function Login() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-wrapper" ref={containerRef}>
|
||||
<div className="login-wrapper">
|
||||
<div style={{ position: 'fixed', top: 22, right: 22, zIndex: 200 }}>
|
||||
<button
|
||||
className="theme-toggle"
|
||||
@@ -53,17 +51,7 @@ export default function Login() {
|
||||
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
|
||||
/>
|
||||
</div>
|
||||
<LiquidGlass
|
||||
mouseContainer={containerRef}
|
||||
elasticity={0.10}
|
||||
blurAmount={0.045}
|
||||
saturation={128}
|
||||
displacementScale={44}
|
||||
cornerRadius={28}
|
||||
aberrationIntensity={1.8}
|
||||
overLight={theme === 'light'}
|
||||
style={{ width: 380 }}
|
||||
>
|
||||
<div className="glass-card" style={{ width: 380 }}>
|
||||
<div className="login-inner">
|
||||
<h1>
|
||||
<span className="logo-dot" style={{ width: 12, height: 12, borderRadius: 6 }} />
|
||||
@@ -96,7 +84,7 @@ export default function Login() {
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</LiquidGlass>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,10 +30,11 @@ public class HTTPServer {
|
||||
private static final Logger log = LogManager.getLogger(HTTPServer.class);
|
||||
public static boolean isRunning = false;
|
||||
public static String cwd = System.getProperty("user.dir");
|
||||
private static HttpServer httpServer;
|
||||
|
||||
public static void start() throws IOException {
|
||||
|
||||
HttpServer httpServer = HttpServer.create(new InetSocketAddress(Config.httpPort), 0);
|
||||
httpServer = HttpServer.create(new InetSocketAddress(Config.httpPort), 0);
|
||||
httpServer.createContext("/", httpExchange -> {
|
||||
try {
|
||||
System.out.println(ansi().render("@|green [+]|@ New HTTP Request From >>" + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI()));
|
||||
@@ -82,6 +83,14 @@ public class HTTPServer {
|
||||
System.out.println(ansi().render("@|green [+]|@ HTTP Server Start Listening on >> " + Config.httpPort + "..."));
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (httpServer != null) {
|
||||
httpServer.stop(0);
|
||||
isRunning = false;
|
||||
System.out.println(ansi().render("@|yellow [!]|@ HTTP Server stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleFileRequest(HttpExchange exchange) throws Exception {
|
||||
System.out.println("[-] 请求的后缀不对");
|
||||
String path = exchange.getRequestURI().getPath();
|
||||
|
||||
@@ -31,6 +31,7 @@ public class LdapServer extends InMemoryOperationInterceptor {
|
||||
private static final Logger log = LogManager.getLogger(LdapServer.class);
|
||||
public static TreeMap<String, LdapController> routes = new TreeMap<>();
|
||||
public static boolean isRunning = false;
|
||||
private static InMemoryDirectoryServer ds;
|
||||
|
||||
public LdapServer() throws Exception {
|
||||
|
||||
@@ -71,7 +72,7 @@ public class LdapServer extends InMemoryOperationInterceptor {
|
||||
//添加操作拦截器
|
||||
//将提供的操作拦截器添加到操作拦截器列表中,该列表可用于在请求被内存目录服务器处理之前转换请求,和/或在响应返回给客户端之前转换响应。
|
||||
serverConfig.addInMemoryOperationInterceptor(new LdapServer());
|
||||
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(serverConfig);
|
||||
ds = new InMemoryDirectoryServer(serverConfig);
|
||||
ds.startListening();
|
||||
isRunning = true;
|
||||
System.out.println(ansi().render("@|green [+]|@ LDAP Server Start Listening on >> " + Config.ldapPort + "..."));
|
||||
@@ -80,6 +81,14 @@ public class LdapServer extends InMemoryOperationInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (ds != null) {
|
||||
ds.shutDown(true);
|
||||
isRunning = false;
|
||||
System.out.println(ansi().render("@|yellow [!]|@ LDAP Server stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processSearchResult(InMemoryInterceptedSearchResult result) {
|
||||
String base;
|
||||
|
||||
@@ -15,6 +15,7 @@ import static org.fusesource.jansi.Ansi.ansi;
|
||||
public class LdapsServer {
|
||||
private static final Logger log = LogManager.getLogger(LdapsServer.class);
|
||||
public static boolean isRunning = false;
|
||||
private static InMemoryDirectoryServer ds;
|
||||
private final String certFile;
|
||||
private final String keyPass;
|
||||
|
||||
@@ -50,7 +51,7 @@ public class LdapsServer {
|
||||
));
|
||||
config.addInMemoryOperationInterceptor(new LdapServer());
|
||||
|
||||
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
|
||||
ds = new InMemoryDirectoryServer(config);
|
||||
ds.startListening();
|
||||
isRunning = true;
|
||||
System.out.println(ansi().render("@|green [+]|@ LDAPS Server Start Listening on >> " + Config.ldapsPort + "..."));
|
||||
@@ -58,4 +59,12 @@ public class LdapsServer {
|
||||
log.error("e: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (ds != null) {
|
||||
ds.shutDown(true);
|
||||
isRunning = false;
|
||||
System.out.println(ansi().render("@|yellow [!]|@ LDAPS Server stopped"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import static org.fusesource.jansi.Ansi.ansi;
|
||||
public class RMIServer implements Runnable {
|
||||
|
||||
public static boolean isRunning = false;
|
||||
private static RMIServer instance;
|
||||
|
||||
private final ServerSocket ss;
|
||||
private final Object waitLock = new Object();
|
||||
@@ -67,15 +68,23 @@ public class RMIServer implements Runnable {
|
||||
|
||||
try {
|
||||
System.out.println(ansi().render("@|green [+]|@ RMI Server Start Listening on >> " + rmiPort + "..."));
|
||||
RMIServer c = new RMIServer(rmiPort, new URL(url));
|
||||
instance = new RMIServer(rmiPort, new URL(url));
|
||||
isRunning = true;
|
||||
c.run();
|
||||
instance.run();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Listener error");
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (instance != null) {
|
||||
instance.close();
|
||||
isRunning = false;
|
||||
System.out.println(ansi().render("@|yellow [!]|@ RMI Server stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleDGC(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
ois.readInt(); // method
|
||||
ois.readLong(); // hash
|
||||
|
||||
@@ -29,8 +29,6 @@ public class Config {
|
||||
|
||||
@Parameter(names = {"-hP", "--httpPort"}, description = "Http bind port", order = 3)
|
||||
public static int httpPort = 3456;
|
||||
@Parameter(names = {"-c", " --command"}, help = true, description = "RMI this command")
|
||||
public static String command = "whoami";
|
||||
@Parameter(names = {"-v", " --version"}, help = true, description = "Show version", order = 5)
|
||||
public static boolean showVersion;
|
||||
@Parameter(names = {"-ga", " --gadgets"}, help = true, description = "Show gadgets", order = 5)
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@@ -52,6 +54,8 @@ public class JYsoWebApplication {
|
||||
System.out.println(ansi().render("@|yellow [+]|@ Password: " + webPassword));
|
||||
System.out.println(ansi().render("@|yellow [+]|@ ============================================"));
|
||||
|
||||
copyToClipboard(webPassword);
|
||||
|
||||
startJndiServers(args);
|
||||
|
||||
Properties props = new Properties();
|
||||
@@ -77,6 +81,19 @@ public class JYsoWebApplication {
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyToClipboard(String text) {
|
||||
try {
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
|
||||
new StringSelection(text), null
|
||||
);
|
||||
System.out.println(ansi().render("@|cyan [+]|@ Password copied to clipboard"));
|
||||
} catch (HeadlessException e) {
|
||||
// headless environment, clipboard unavailable
|
||||
} catch (Exception e) {
|
||||
System.out.println(ansi().render("@|yellow [!]|@ Could not copy to clipboard: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] filterWebArgs(String[] args) {
|
||||
java.util.List<String> filtered = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
|
||||
@@ -29,7 +29,13 @@ public class ApiController {
|
||||
status.put("httpPort", Config.httpPort);
|
||||
status.put("rmiPort", Config.rmiPort);
|
||||
status.put("codeBase", Config.codeBase);
|
||||
status.put("version", "1.3.7");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -81,6 +87,83 @@ public class ApiController {
|
||||
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<>();
|
||||
@@ -134,8 +217,13 @@ public class ApiController {
|
||||
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("command")) Config.command = (String) body.get("command");
|
||||
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());
|
||||
|
||||
Reference in New Issue
Block a user