From 02e817167edbc72e56d351036ed2cdead3626709 Mon Sep 17 00:00:00 2001 From: qi4l Date: Fri, 1 May 2026 00:23:07 +0800 Subject: [PATCH] =?UTF-8?q?perf=EF=BC=9A=E5=89=8D=E7=AB=AF=E5=A4=A7?= =?UTF-8?q?=E6=94=B93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/frontend/src/api/index.js | 4 + src/main/frontend/src/index.css | 150 +++++++ src/main/frontend/src/pages/Dashboard.jsx | 248 +++++++++++- .../java/com/qi4l/JYso/web/ApiAuthFilter.java | 49 +++ .../java/com/qi4l/JYso/web/AuthServlet.java | 57 +++ .../com/qi4l/JYso/web/JYsoWebApplication.java | 6 +- .../com/qi4l/JYso/web/JettyApiServlet.java | 368 ++++++++++++++++++ .../qi4l/JYso/web/RequestLogCollector.java | 122 ++++++ .../com/qi4l/JYso/web/SpaFallbackFilter.java | 66 ++++ .../qi4l/JYso/web/StaticResourceServlet.java | 71 ++++ 10 files changed, 1124 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/qi4l/JYso/web/ApiAuthFilter.java create mode 100644 src/main/java/com/qi4l/JYso/web/AuthServlet.java create mode 100644 src/main/java/com/qi4l/JYso/web/JettyApiServlet.java create mode 100644 src/main/java/com/qi4l/JYso/web/RequestLogCollector.java create mode 100644 src/main/java/com/qi4l/JYso/web/SpaFallbackFilter.java create mode 100644 src/main/java/com/qi4l/JYso/web/StaticResourceServlet.java diff --git a/src/main/frontend/src/api/index.js b/src/main/frontend/src/api/index.js index 681563e..00e2dda 100644 --- a/src/main/frontend/src/api/index.js +++ b/src/main/frontend/src/api/index.js @@ -68,4 +68,8 @@ export function getConfig() { return api.get('/config') } +export function getLogs(count) { + return api.get('/logs', { params: { count } }) +} + export default api diff --git a/src/main/frontend/src/index.css b/src/main/frontend/src/index.css index 2f5cc79..d66e5b6 100644 --- a/src/main/frontend/src/index.css +++ b/src/main/frontend/src/index.css @@ -533,6 +533,14 @@ code, pre, .mono { transform: translateX(calc(100% + 2px)); } +.tab-segment-control.tab-3::before { + transform: translateX(calc(200% + 4px)); +} + +.tab-segment-control.tabs-3::before { + width: calc(33.333% - 4px); +} + .tab-segment-btn { position: relative; z-index: 1; @@ -646,6 +654,8 @@ code, pre, .mono { color: var(--text-secondary); margin-top: 14px; white-space: pre-wrap; + word-break: break-all; + overflow-wrap: break-word; max-height: 160px; overflow-y: auto; } @@ -658,6 +668,40 @@ input[type="checkbox"] { cursor: pointer; } +.base64-toggle { + width: 44px; + height: 26px; + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 13px; + position: relative; + cursor: pointer; + transition: background var(--transition-fast), border-color var(--transition-fast); + outline: none; + flex-shrink: 0; +} +.base64-toggle:focus-visible { + box-shadow: 0 0 0 3px var(--accent-glow); +} +.base64-toggle.active { + background: var(--accent); + border-color: var(--accent); +} +.base64-toggle-thumb { + width: 20px; + height: 20px; + background: #FFFFFF; + border-radius: 50%; + position: absolute; + top: 2px; + left: 2px; + transition: transform var(--transition-fast); + box-shadow: 0 1px 3px rgba(0,0,0,0.2); +} +.base64-toggle.active .base64-toggle-thumb { + transform: translateX(18px); +} + label.form-group { display: flex; align-items: center; @@ -667,6 +711,112 @@ label.form-group { cursor: pointer; } +.copy-btn { + position: absolute; + top: 8px; + right: 8px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + color: var(--text-secondary); + transition: all var(--transition-fast); + padding: 0; +} +.copy-btn:hover { + background: var(--accent-glow); + border-color: var(--accent); + color: var(--accent); +} + +.advanced-section { + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 14px; + padding: 14px 16px; + margin-bottom: 4px; +} +.adv-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 16px; +} +.adv-check { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--text-secondary); + cursor: pointer; + padding: 4px 0; +} +.adv-check em { + font-style: normal; + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-muted); +} +.adv-check input[type="checkbox"] { + width: 14px; + height: 14px; + margin: 0; + flex-shrink: 0; +} + +select { + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23999999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 30px; +} + +.log-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.log-container { + background: #0d1117; + border: 1px solid #30363d; + border-radius: 12px; + padding: 12px; + max-height: 380px; + overflow-y: auto; + font-family: var(--font-mono); + font-size: 11px; + line-height: 1.65; + transition: background var(--transition-fluid), border-color var(--transition-fluid); +} + +.log-line { + color: #c9d1d9; + white-space: pre-wrap; + word-break: break-all; + padding: 1px 0; +} + +.log-empty { + color: #8b949e; + text-align: center; + padding: 32px 0; + font-family: 'Inter', sans-serif; + font-size: 13px; +} + @media (max-width: 768px) { .page-shell { padding: 48px 16px 32px; } .grid-2 { grid-template-columns: 1fr; } diff --git a/src/main/frontend/src/pages/Dashboard.jsx b/src/main/frontend/src/pages/Dashboard.jsx index a20839c..2b85852 100644 --- a/src/main/frontend/src/pages/Dashboard.jsx +++ b/src/main/frontend/src/pages/Dashboard.jsx @@ -1,9 +1,9 @@ -import { useState, useEffect, useMemo, useCallback } from 'react' +import { useState, useEffect, useMemo, useCallback, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { useTheme } from '../context/ThemeContext' import { getStatus, toggleServer, getGadgets, - generatePayload as apiGenerate, updateConfig, setAuthToken + generatePayload as apiGenerate, updateConfig, setAuthToken, getLogs } from '../api' export default function Dashboard() { @@ -21,6 +21,18 @@ export default function Dashboard() { const [selectedGadget, setSelectedGadget] = useState('') const [payloadCmd, setPayloadCmd] = useState('') const [payloadResult, setPayloadResult] = useState('') + const [encodeBase64, setEncodeBase64] = useState(true) + const [copied, setCopied] = useState(false) + const [showAdvanced, setShowAdvanced] = useState(false) + const [inherit, setInherit] = useState(false) + const [obscure, setObscure] = useState(false) + const [dcfp, setDcfp] = useState('') + const [dirtyType, setDirtyType] = useState('') + const [dirtyLength, setDirtyLength] = useState('') + const [noComSun, setNoComSun] = useState(false) + const [mozillaClassLoader, setMozillaClassLoader] = useState(false) + const [rhino, setRhino] = useState(false) + const [utf8Overlong, setUtf8Overlong] = useState(false) const [payloadSubTab, setPayloadSubTab] = useState('gadget') const [gadgetOpen, setGadgetOpen] = useState(false) const [jndiPayloadResult, setJndiPayloadResult] = useState('') @@ -35,6 +47,9 @@ export default function Dashboard() { const [ldapsClassLoaderResult, setLdapsClassLoaderResult] = useState('') const [routing, setRouting] = useState('ELProcessor') const [routingOpen, setRoutingOpen] = useState(false) + const [logLines, setLogLines] = useState([]) + const [logLoading, setLogLoading] = useState(false) + const logEndRef = useRef(null) const ROUTING_OPTIONS = ['Basic', 'ELProcessor', 'Groovy', 'jdbcBypass1', 'jdbcBypass2', 'ldap2rmi', 'SnakeYaml', 'XStream', 'MemoryXXE'] @@ -120,7 +135,8 @@ export default function Dashboard() { setLoading(true) setMsg({ success: '', error: '' }) try { - const res = await apiGenerate({ gadget: gadgetName, command: payloadCmd, filename: saveFilename }) + const res = await apiGenerate({ gadget: gadgetName, command: payloadCmd, filename: saveFilename, encodeBase64, + inherit, obscure, dcfp, dirtyType, dirtyLength, noComSun, mozillaClassLoader, rhino, utf8Overlong }) if (res.data.success) { setPayloadResult(res.data.message || 'Payload generated successfully') setMsg({ success: 'Payload generated', error: '' }) @@ -133,6 +149,24 @@ export default function Dashboard() { } finally { setLoading(false) } } + async function handleCopyPayload() { + if (!payloadResult) return + try { + await navigator.clipboard.writeText(payloadResult) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { /* ignore */ } + } + + async function copyText(text) { + if (!text) return + try { + await navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { /* ignore */ } + } + function handleGenerateJndiPayload() { const gadgetName = (jndiGadgetInput || selectedGadget || '').trim() if (!gadgetName || !payloadCmd) return @@ -200,8 +234,26 @@ export default function Dashboard() { navigate('/login', { replace: true }) } + async function fetchLogs() { + try { + const res = await getLogs(100) + setLogLines(res.data.logs || []) + } catch { /* ignore */ } + } + useEffect(() => { loadStatus(); loadGadgets() }, []) + useEffect(() => { + if (activeJndiTab !== 'logs') return + fetchLogs() + const id = setInterval(fetchLogs, 2000) + return () => clearInterval(id) + }, [activeJndiTab]) + + useEffect(() => { + if (logEndRef.current) logEndRef.current.scrollIntoView({ behavior: 'smooth' }) + }, [logLines]) + return (
@@ -293,7 +345,7 @@ export default function Dashboard() {
-
+
+
@@ -432,13 +490,40 @@ export default function Dashboard() { {loading ? 'Generating...' : 'Generate'} {jndiPayloadResult && ( -
{jndiPayloadResult}
+
+ {jndiPayloadResult} + +
)} {rmiPayloadResult && ( -
{rmiPayloadResult}
+
+ {rmiPayloadResult} + +
)} {ldapsPayloadResult && ( -
{ldapsPayloadResult}
+
+ {ldapsPayloadResult} + +
)}
)} @@ -482,26 +567,152 @@ export default function Dashboard() { {loading ? 'Generating...' : 'Generate'} {classLoaderResult && ( -
{classLoaderResult}
+
+ {classLoaderResult} + +
)} {rmiClassLoaderResult && ( -
{rmiClassLoaderResult}
+
+ {rmiClassLoaderResult} + +
)} {ldapsClassLoaderResult && ( -
{ldapsClassLoaderResult}
+
+ {ldapsClassLoaderResult} + +
)}
)}
)} + + {activeJndiTab === 'logs' && ( +
+
+ Server Events + +
+
+ {logLines.length === 0 ? ( +
No events yet. Start a server or wait for incoming requests.
+ ) : ( + logLines.map((line, i) => ( +
{line}
+ )) + )} +
+
+
+ )}
)} {mode === 'gadget' && (
-

Payload Generator

-
+
+

Payload Generator

+ +
+
+