mirror of
https://github.com/qi4L/JYso.git
synced 2026-09-21 22:40:43 +08:00
perf:前端大改3
This commit is contained in:
@@ -68,4 +68,8 @@ export function getConfig() {
|
||||
return api.get('/config')
|
||||
}
|
||||
|
||||
export function getLogs(count) {
|
||||
return api.get('/logs', { params: { count } })
|
||||
}
|
||||
|
||||
export default api
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<div className="dashboard-bg" />
|
||||
@@ -293,7 +345,7 @@ export default function Dashboard() {
|
||||
|
||||
<div className="glass-card section-enter">
|
||||
<div className="control-bar">
|
||||
<div className={'tab-segment-control' + (activeJndiTab === 'payload' ? ' config-tab' : '')}>
|
||||
<div className={'tab-segment-control tabs-3' + (activeJndiTab === 'payload' ? ' config-tab' : '') + (activeJndiTab === 'logs' ? ' tab-3' : '')}>
|
||||
<button
|
||||
className={'tab-segment-btn' + (activeJndiTab === 'config' ? ' active' : '')}
|
||||
onClick={() => setActiveJndiTab('config')}
|
||||
@@ -304,7 +356,13 @@ export default function Dashboard() {
|
||||
className={'tab-segment-btn' + (activeJndiTab === 'payload' ? ' active' : '')}
|
||||
onClick={() => setActiveJndiTab('payload')}
|
||||
>
|
||||
Payload Generator
|
||||
Payload
|
||||
</button>
|
||||
<button
|
||||
className={'tab-segment-btn' + (activeJndiTab === 'logs' ? ' active' : '')}
|
||||
onClick={() => setActiveJndiTab('logs')}
|
||||
>
|
||||
Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -432,13 +490,40 @@ export default function Dashboard() {
|
||||
{loading ? 'Generating...' : 'Generate'}
|
||||
</button>
|
||||
{jndiPayloadResult && (
|
||||
<div className="payload-output" style={{ marginTop: 14 }}>{jndiPayloadResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 14, position: 'relative', paddingRight: 42 }}>
|
||||
{jndiPayloadResult}
|
||||
<button className="copy-btn" onClick={() => copyText(jndiPayloadResult)} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{rmiPayloadResult && (
|
||||
<div className="payload-output" style={{ marginTop: 8 }}>{rmiPayloadResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 8, position: 'relative', paddingRight: 42 }}>
|
||||
{rmiPayloadResult}
|
||||
<button className="copy-btn" onClick={() => copyText(rmiPayloadResult)} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{ldapsPayloadResult && (
|
||||
<div className="payload-output" style={{ marginTop: 8 }}>{ldapsPayloadResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 8, position: 'relative', paddingRight: 42 }}>
|
||||
{ldapsPayloadResult}
|
||||
<button className="copy-btn" onClick={() => copyText(ldapsPayloadResult)} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -482,26 +567,152 @@ export default function Dashboard() {
|
||||
{loading ? 'Generating...' : 'Generate'}
|
||||
</button>
|
||||
{classLoaderResult && (
|
||||
<div className="payload-output" style={{ marginTop: 14 }}>{classLoaderResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 14, position: 'relative', paddingRight: 42 }}>
|
||||
{classLoaderResult}
|
||||
<button className="copy-btn" onClick={() => copyText(classLoaderResult)} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{rmiClassLoaderResult && (
|
||||
<div className="payload-output" style={{ marginTop: 8 }}>{rmiClassLoaderResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 8, position: 'relative', paddingRight: 42 }}>
|
||||
{rmiClassLoaderResult}
|
||||
<button className="copy-btn" onClick={() => copyText(rmiClassLoaderResult)} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{ldapsClassLoaderResult && (
|
||||
<div className="payload-output" style={{ marginTop: 8 }}>{ldapsClassLoaderResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 8, position: 'relative', paddingRight: 42 }}>
|
||||
{ldapsClassLoaderResult}
|
||||
<button className="copy-btn" onClick={() => copyText(ldapsClassLoaderResult)} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeJndiTab === 'logs' && (
|
||||
<div key="jndi-logs" className="tab-content-enter">
|
||||
<div className="log-header">
|
||||
<span>Server Events</span>
|
||||
<button className="btn btn-secondary" style={{ padding: '4px 12px', fontSize: 11, width: 'auto' }}
|
||||
onClick={fetchLogs} disabled={logLoading}>
|
||||
{logLoading ? 'Loading...' : 'Refresh'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="log-container">
|
||||
{logLines.length === 0 ? (
|
||||
<div className="log-empty">No events yet. Start a server or wait for incoming requests.</div>
|
||||
) : (
|
||||
logLines.map((line, i) => (
|
||||
<div key={i} className="log-line">{line}</div>
|
||||
))
|
||||
)}
|
||||
<div ref={logEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{mode === 'gadget' && (
|
||||
<div className="glass-card section-enter">
|
||||
<div className="header" style={{ padding: 0, marginBottom: 16 }}>
|
||||
<h2>Payload Generator</h2>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<button
|
||||
className={`btn btn-secondary${showAdvanced ? ' active-tab' : ''}`}
|
||||
style={{ padding: '6px 14px', fontSize: 12, width: 'auto' }}
|
||||
onClick={() => setShowAdvanced(v => !v)}
|
||||
>
|
||||
{showAdvanced ? 'Hide Options' : 'Advanced Options'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="control-bar" style={{ marginBottom: 12 }}>
|
||||
<label className="form-group" style={{ marginBottom: 0, display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}>
|
||||
<div className={`base64-toggle${encodeBase64 ? ' active' : ''}`}
|
||||
onClick={() => setEncodeBase64(v => !v)}
|
||||
role="switch"
|
||||
aria-checked={encodeBase64}
|
||||
tabIndex={0}
|
||||
onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setEncodeBase64(v => !v); } }}>
|
||||
<div className="base64-toggle-thumb" />
|
||||
</div>
|
||||
<span style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-secondary)' }}>Base64 Encode Output</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{showAdvanced && (
|
||||
<div className="advanced-section section-enter">
|
||||
<div className="adv-grid">
|
||||
<label className="adv-check">
|
||||
<input type="checkbox" checked={inherit} onChange={e => setInherit(e.target.checked)} />
|
||||
<span>Inherit AbstractTranslet <em>(-i)</em></span>
|
||||
</label>
|
||||
<label className="adv-check">
|
||||
<input type="checkbox" checked={obscure} onChange={e => setObscure(e.target.checked)} />
|
||||
<span>Obscure (reflection bypass RASP) <em>(-o)</em></span>
|
||||
</label>
|
||||
<label className="adv-check">
|
||||
<input type="checkbox" checked={noComSun} onChange={e => setNoComSun(e.target.checked)} />
|
||||
<span>Force org.apache.XXX.TemplatesImpl <em>(-ncs)</em></span>
|
||||
</label>
|
||||
<label className="adv-check">
|
||||
<input type="checkbox" checked={mozillaClassLoader} onChange={e => setMozillaClassLoader(e.target.checked)} />
|
||||
<span>Mozilla DefiningClassLoader <em>(-mcl)</em></span>
|
||||
</label>
|
||||
<label className="adv-check">
|
||||
<input type="checkbox" checked={rhino} onChange={e => setRhino(e.target.checked)} />
|
||||
<span>Rhino Engine <em>(-rh)</em></span>
|
||||
</label>
|
||||
<label className="adv-check">
|
||||
<input type="checkbox" checked={utf8Overlong} onChange={e => setUtf8Overlong(e.target.checked)} />
|
||||
<span>UTF-8 Overlong Encoding <em>(-utf)</em></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="adv-grid" style={{ gridTemplateColumns: '1fr 1fr', marginTop: 10 }}>
|
||||
<div className="form-group" style={{ marginBottom: 0 }}>
|
||||
<label style={{ fontSize: 12 }}>DefineClassFromParameter <em>(-dcfp)</em></label>
|
||||
<input type="text" value={dcfp} placeholder="parameter name"
|
||||
onChange={e => setDcfp(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: 0 }}>
|
||||
<label style={{ fontSize: 12 }}>Dirty Type <em>(-dt)</em></label>
|
||||
<select value={dirtyType} onChange={e => setDirtyType(e.target.value)}
|
||||
style={{ width: '100%', padding: '9px 12px', borderRadius: 'var(--input-radius)', border: '1px solid var(--border-color)', background: 'var(--bg-input)', color: 'var(--text-primary)', fontSize: 13, fontFamily: 'inherit', outline: 'none' }}>
|
||||
<option value="">None</option>
|
||||
<option value="1">1: Random Hashable Collections</option>
|
||||
<option value="2">2: LinkedList Nesting</option>
|
||||
<option value="3">3: TC_RESET in Serialized Data</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: 0 }}>
|
||||
<label style={{ fontSize: 12 }}>Dirty Length <em>(-dl)</em></label>
|
||||
<input type="number" value={dirtyLength} placeholder="length/counts"
|
||||
onChange={e => setDirtyLength(e.target.value)} />
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ position: 'relative', marginTop: showAdvanced ? 14 : 0 }}>
|
||||
<div className="form-group">
|
||||
<label>Gadget</label>
|
||||
<input type="text"
|
||||
@@ -543,7 +754,16 @@ export default function Dashboard() {
|
||||
{loading ? 'Generating...' : 'Generate'}
|
||||
</button>
|
||||
{payloadResult && (
|
||||
<div className="payload-output" style={{ marginTop: 14 }}>{payloadResult}</div>
|
||||
<div className="payload-output" style={{ marginTop: 14, position: 'relative', paddingRight: 42 }}>
|
||||
{payloadResult}
|
||||
<button className="copy-btn" onClick={handleCopyPayload} title="Copy to clipboard">
|
||||
{copied ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M3 11V3h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.qi4l.JYso.web;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ApiAuthFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
HttpServletResponse resp = (HttpServletResponse) response;
|
||||
|
||||
String path = req.getRequestURI();
|
||||
|
||||
if (!path.startsWith("/api/") || path.equals("/api/auth/login")) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String authHeader = req.getHeader("Authorization");
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
resp.setStatus(401);
|
||||
resp.setContentType("application/json");
|
||||
resp.getWriter().write("{\"error\":\"Unauthorized\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
String token = authHeader.substring(7);
|
||||
if (!AuthServlet.validateToken(token)) {
|
||||
resp.setStatus(401);
|
||||
resp.setContentType("application/json");
|
||||
resp.getWriter().write("{\"error\":\"Invalid token\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.qi4l.JYso.web;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.qi4l.JYso.web.config.JYsoWebPasswordProvider;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AuthServlet extends HttpServlet {
|
||||
|
||||
static final ConcurrentHashMap<String, String> tokens = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setContentType("application/json");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = req.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
}
|
||||
JSONObject body = JSON.parseObject(sb.toString());
|
||||
|
||||
String username = body.getString("username");
|
||||
String password = body.getString("password");
|
||||
|
||||
if (!"qi".equals(username) || !JYsoWebPasswordProvider.getPassword().equals(password)) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", "Invalid credentials");
|
||||
resp.setStatus(401);
|
||||
resp.getWriter().write(JSON.toJSONString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
String token = UUID.randomUUID().toString();
|
||||
tokens.put(token, username);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
result.put("username", username);
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
static boolean validateToken(String token) {
|
||||
return token != null && tokens.containsKey(token);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ public class JYsoWebApplication {
|
||||
public static void start(String[] args) {
|
||||
logo();
|
||||
|
||||
RequestLogCollector.install();
|
||||
|
||||
webPassword = WebPasswordGenerator.generatePassword();
|
||||
JYsoWebPasswordProvider.setPassword(webPassword);
|
||||
|
||||
@@ -47,7 +49,7 @@ public class JYsoWebApplication {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(ansi().render("@|green [+]|@ Web GUI starting >> http://127.0.0.1:" + webPort));
|
||||
System.out.println(ansi().render("@|green [+]|@ Web GUI started on http://127.0.0.1:" + webPort));
|
||||
System.out.println(ansi().render("@|yellow [+]|@ ============================================"));
|
||||
System.out.println(ansi().render("@|yellow [+]|@ Username: qi"));
|
||||
System.out.println(ansi().render("@|yellow [+]|@ Password: " + webPassword));
|
||||
@@ -74,8 +76,6 @@ public class JYsoWebApplication {
|
||||
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...");
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.qi4l.JYso.web;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
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 javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
public class JettyApiServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentType("application/json");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
String path = req.getRequestURI();
|
||||
if (path.startsWith("/api/")) {
|
||||
path = path.substring(4);
|
||||
}
|
||||
|
||||
try {
|
||||
if ("/status".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleStatus(resp);
|
||||
} else if ("/servers/start".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
|
||||
handleServersStart(req, resp);
|
||||
} else if ("/servers/stop".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
|
||||
handleServersStop(req, resp);
|
||||
} else if ("/servers/toggle".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
|
||||
handleServersToggle(req, resp);
|
||||
} else if ("/gadgets".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleGadgets(resp);
|
||||
} else if ("/payload/generate".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
|
||||
handlePayloadGenerate(req, resp);
|
||||
} else if ("/config/update".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
|
||||
handleConfigUpdate(req, resp);
|
||||
} else if ("/config".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleStatus(resp);
|
||||
} else if ("/logs".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleLogs(req, resp);
|
||||
} else {
|
||||
resp.setStatus(404);
|
||||
resp.getWriter().write("{\"error\":\"Not found\"}");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(500);
|
||||
resp.getWriter().write("{\"success\":false,\"error\":\"" + escapeJson(e.getMessage()) + "\"}");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStatus(HttpServletResponse resp) throws IOException {
|
||||
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");
|
||||
resp.getWriter().write(JSON.toJSONString(status));
|
||||
}
|
||||
|
||||
private void handleLogs(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
int count = 100;
|
||||
String countParam = req.getParameter("count");
|
||||
if (countParam != null) {
|
||||
try { count = Integer.parseInt(countParam); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
List<String> logLines = RequestLogCollector.getLines(count);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("logs", logLines);
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleServersStart(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
JSONObject json = readJson(req);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
List<String> started = new ArrayList<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
boolean startLdap = json.getBooleanValue("ldap");
|
||||
boolean startLdaps = json.getBooleanValue("ldaps");
|
||||
boolean startHttp = json.getBooleanValue("http");
|
||||
boolean startRmi = json.getBooleanValue("rmi");
|
||||
|
||||
if (json.containsKey("ip")) Config.ip = json.getString("ip");
|
||||
if (json.containsKey("ldapPort")) Config.ldapPort = json.getIntValue("ldapPort");
|
||||
if (json.containsKey("ldapsPort")) Config.ldapsPort = json.getIntValue("ldapsPort");
|
||||
if (json.containsKey("httpPort")) Config.httpPort = json.getIntValue("httpPort");
|
||||
if (json.containsKey("rmiPort")) Config.rmiPort = json.getIntValue("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);
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleServersStop(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
JSONObject json = readJson(req);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
String server = json.getString("server");
|
||||
if (server == null) {
|
||||
result.put("success", false);
|
||||
result.put("error", "server name required");
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
return;
|
||||
}
|
||||
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);
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
return;
|
||||
}
|
||||
result.put("success", true);
|
||||
result.put("server", server);
|
||||
result.put("status", buildStatusMap());
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleServersToggle(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
JSONObject json = readJson(req);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
String server = json.getString("server");
|
||||
if (server == null) {
|
||||
result.put("success", false);
|
||||
result.put("error", "server name required");
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
return;
|
||||
}
|
||||
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);
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
return;
|
||||
}
|
||||
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", buildStatusMap());
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleGadgets(HttpServletResponse resp) throws IOException {
|
||||
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);
|
||||
}
|
||||
resp.getWriter().write(JSON.toJSONString(list));
|
||||
}
|
||||
|
||||
private void handlePayloadGenerate(HttpServletRequest req, HttpServletResponse resp) throws Exception {
|
||||
JSONObject json = readJson(req);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
String gadget = json.getString("gadget");
|
||||
String command = json.getString("command");
|
||||
String saveFilename = json.getString("filename");
|
||||
boolean encodeBase64 = json.getBooleanValue("encodeBase64");
|
||||
|
||||
if (gadget == null || command == null) {
|
||||
result.put("success", false);
|
||||
result.put("error", "gadget and command are required");
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean keepFile = (saveFilename != null && !saveFilename.trim().isEmpty());
|
||||
String filename = keepFile ? saveFilename.trim() : "1.ser";
|
||||
|
||||
java.util.List<String> argList = new java.util.ArrayList<>();
|
||||
argList.add("-y");
|
||||
argList.add("-g");
|
||||
argList.add(gadget);
|
||||
argList.add("-p");
|
||||
argList.add(command);
|
||||
argList.add("-f");
|
||||
argList.add(filename);
|
||||
|
||||
if (json.getBooleanValue("inherit")) argList.add("-i");
|
||||
if (json.getBooleanValue("obscure")) argList.add("-o");
|
||||
if (json.getBooleanValue("noComSun")) argList.add("-ncs");
|
||||
if (json.getBooleanValue("mozillaClassLoader")) argList.add("-mcl");
|
||||
if (json.getBooleanValue("rhino")) argList.add("-rh");
|
||||
if (json.getBooleanValue("utf8Overlong")) argList.add("-utf");
|
||||
|
||||
String dcfpVal = json.getString("dcfp");
|
||||
if (dcfpVal != null && !dcfpVal.trim().isEmpty()) {
|
||||
argList.add("-dcfp");
|
||||
argList.add(dcfpVal.trim());
|
||||
}
|
||||
|
||||
String dirtyTypeVal = json.getString("dirtyType");
|
||||
String dirtyLengthVal = json.getString("dirtyLength");
|
||||
if (dirtyTypeVal != null && !dirtyTypeVal.trim().isEmpty()
|
||||
&& dirtyLengthVal != null && !dirtyLengthVal.trim().isEmpty()) {
|
||||
argList.add("-dt");
|
||||
argList.add(dirtyTypeVal.trim());
|
||||
argList.add("-dl");
|
||||
argList.add(dirtyLengthVal.trim());
|
||||
}
|
||||
|
||||
ysoserial.run(argList.toArray(new String[0]));
|
||||
|
||||
Path filePath = Paths.get(filename);
|
||||
byte[] data = Files.readAllBytes(filePath);
|
||||
|
||||
result.put("success", true);
|
||||
if (encodeBase64) {
|
||||
String b64 = Base64.getEncoder().encodeToString(data);
|
||||
result.put("message", b64);
|
||||
if (keepFile) {
|
||||
Files.write(filePath, b64.getBytes());
|
||||
}
|
||||
} else {
|
||||
StringBuilder hex = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
hex.append(String.format("%02x", b));
|
||||
}
|
||||
result.put("message", hex.toString());
|
||||
}
|
||||
if (!keepFile) {
|
||||
Files.deleteIfExists(filePath);
|
||||
}
|
||||
if (keepFile) {
|
||||
result.put("saved", filename);
|
||||
}
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleConfigUpdate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
JSONObject json = readJson(req);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
if (json.containsKey("ip")) Config.ip = json.getString("ip");
|
||||
if (json.containsKey("ldapPort")) Config.ldapPort = json.getIntValue("ldapPort");
|
||||
if (json.containsKey("ldapsPort")) Config.ldapsPort = json.getIntValue("ldapsPort");
|
||||
if (json.containsKey("httpPort")) Config.httpPort = json.getIntValue("httpPort");
|
||||
if (json.containsKey("rmiPort")) Config.rmiPort = json.getIntValue("rmiPort");
|
||||
if (json.containsKey("codeBase")) Config.codeBase = json.getString("codeBase");
|
||||
if (json.containsKey("AESkey")) Config.AESkey = json.getString("AESkey");
|
||||
if (json.containsKey("user")) Config.USER = json.getString("user");
|
||||
if (json.containsKey("PASSWD")) Config.PASSWD = json.getString("PASSWD");
|
||||
if (json.containsKey("TLSProxy")) Config.TLSProxy = json.getBooleanValue("TLSProxy");
|
||||
if (json.containsKey("keyPass")) Config.keyPass = json.getString("keyPass");
|
||||
if (json.containsKey("certFile")) Config.certFile = json.getString("certFile");
|
||||
|
||||
result.put("success", true);
|
||||
result.put("config", buildStatusMap());
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private Map<String, Object> buildStatusMap() {
|
||||
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;
|
||||
}
|
||||
|
||||
private JSONObject readJson(HttpServletRequest req) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = req.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
}
|
||||
String body = sb.toString();
|
||||
if (body.isEmpty()) return new JSONObject();
|
||||
return JSON.parseObject(body);
|
||||
}
|
||||
|
||||
private String escapeJson(String s) {
|
||||
if (s == null) return "null";
|
||||
return s.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r")
|
||||
.replace("\t", "\\t");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.qi4l.JYso.web;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class RequestLogCollector {
|
||||
|
||||
private static final int MAX_LINES = 200;
|
||||
private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
private static final List<String> lines = new ArrayList<>();
|
||||
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private static PrintStream originalOut;
|
||||
private static boolean installed = false;
|
||||
|
||||
public static synchronized void install() {
|
||||
if (installed) return;
|
||||
installed = true;
|
||||
originalOut = System.out;
|
||||
System.setOut(new PrintStream(new TeeOutputStream(originalOut), true));
|
||||
}
|
||||
|
||||
private static void addLine(String rawLine) {
|
||||
String clean = rawLine
|
||||
.replaceAll("\u001B\\[[;\\d]*m", "")
|
||||
.replaceAll("\u001B\\[[;\\d]*[@-~]", "")
|
||||
.trim();
|
||||
if (clean.isEmpty()) return;
|
||||
|
||||
String timestamp = LocalTime.now().format(TIME_FMT);
|
||||
String entry = "[" + timestamp + "] " + clean;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
lines.add(entry);
|
||||
while (lines.size() > MAX_LINES) {
|
||||
lines.remove(0);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getLines(int count) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
int size = lines.size();
|
||||
if (size <= count || count <= 0) {
|
||||
return new ArrayList<>(lines);
|
||||
}
|
||||
return new ArrayList<>(lines.subList(size - count, size));
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getLines() {
|
||||
return getLines(0);
|
||||
}
|
||||
|
||||
private static class TeeOutputStream extends OutputStream {
|
||||
private final OutputStream target;
|
||||
|
||||
TeeOutputStream(OutputStream target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
private final StringBuilder lineBuffer = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void write(int b) {
|
||||
try {
|
||||
target.write(b);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (b == '\n' || b == '\r') {
|
||||
if (lineBuffer.length() > 0) {
|
||||
String line = lineBuffer.toString();
|
||||
lineBuffer.setLength(0);
|
||||
if (!line.isEmpty()) {
|
||||
addLine(line);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lineBuffer.append((char) b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buf, int off, int len) {
|
||||
try {
|
||||
target.write(buf, off, len);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
for (int i = off; i < off + len; i++) {
|
||||
char c = (char) buf[i];
|
||||
if (c == '\n' || c == '\r') {
|
||||
if (lineBuffer.length() > 0) {
|
||||
addLine(lineBuffer.toString());
|
||||
lineBuffer.setLength(0);
|
||||
}
|
||||
} else {
|
||||
lineBuffer.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
target.flush();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.qi4l.JYso.web;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
public class SpaFallbackFilter implements Filter {
|
||||
|
||||
private byte[] indexContent;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
try {
|
||||
URL indexUrl = getClass().getClassLoader().getResource("static/index.html");
|
||||
if (indexUrl != null) {
|
||||
try (InputStream is = indexUrl.openStream()) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[4096];
|
||||
int n;
|
||||
while ((n = is.read(buf)) != -1) bos.write(buf, 0, n);
|
||||
indexContent = bos.toByteArray();
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
HttpServletResponse resp = (HttpServletResponse) response;
|
||||
|
||||
String path = req.getRequestURI();
|
||||
if (path.startsWith("/api/")) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String lower = path.toLowerCase();
|
||||
boolean hasFileExt = lower.contains(".") && !lower.endsWith(".html");
|
||||
|
||||
if (!hasFileExt && indexContent != null) {
|
||||
resp.setContentType("text/html");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setStatus(200);
|
||||
resp.setContentLength(indexContent.length);
|
||||
OutputStream os = resp.getOutputStream();
|
||||
os.write(indexContent);
|
||||
os.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.qi4l.JYso.web;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class StaticResourceServlet extends HttpServlet {
|
||||
|
||||
private static final Map<String, String> MIME_TYPES = new HashMap<>();
|
||||
|
||||
static {
|
||||
MIME_TYPES.put("html", "text/html");
|
||||
MIME_TYPES.put("htm", "text/html");
|
||||
MIME_TYPES.put("css", "text/css");
|
||||
MIME_TYPES.put("js", "application/javascript");
|
||||
MIME_TYPES.put("mjs", "application/javascript");
|
||||
MIME_TYPES.put("json", "application/json");
|
||||
MIME_TYPES.put("png", "image/png");
|
||||
MIME_TYPES.put("jpg", "image/jpeg");
|
||||
MIME_TYPES.put("jpeg", "image/jpeg");
|
||||
MIME_TYPES.put("gif", "image/gif");
|
||||
MIME_TYPES.put("svg", "image/svg+xml");
|
||||
MIME_TYPES.put("ico", "image/x-icon");
|
||||
MIME_TYPES.put("woff", "font/woff");
|
||||
MIME_TYPES.put("woff2", "font/woff2");
|
||||
MIME_TYPES.put("ttf", "font/ttf");
|
||||
MIME_TYPES.put("eot", "application/vnd.ms-fontobject");
|
||||
MIME_TYPES.put("txt", "text/plain");
|
||||
MIME_TYPES.put("xml", "application/xml");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
String path = req.getRequestURI();
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
URL resource = getClass().getClassLoader().getResource(path);
|
||||
if (resource == null) {
|
||||
resource = getClass().getClassLoader().getResource("static/" + path);
|
||||
}
|
||||
if (resource == null) {
|
||||
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
String ext = "";
|
||||
int dot = path.lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
ext = path.substring(dot + 1).toLowerCase();
|
||||
}
|
||||
String mime = MIME_TYPES.getOrDefault(ext, "application/octet-stream");
|
||||
resp.setContentType(mime);
|
||||
|
||||
try (InputStream is = resource.openStream();
|
||||
OutputStream os = resp.getOutputStream()) {
|
||||
byte[] buf = new byte[8192];
|
||||
int n;
|
||||
while ((n = is.read(buf)) != -1) {
|
||||
os.write(buf, 0, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user