diff --git a/src/main/frontend/src/api/index.js b/src/main/frontend/src/api/index.js
index 66db25a..7cb4199 100644
--- a/src/main/frontend/src/api/index.js
+++ b/src/main/frontend/src/api/index.js
@@ -90,4 +90,8 @@ export function deleteFile(name) {
return api.post('/files/delete', { name })
}
+export function uploadFile(formData) {
+ return api.post('/files/upload', formData)
+}
+
export default api
diff --git a/src/main/frontend/src/index.css b/src/main/frontend/src/index.css
index b55bf85..4192acd 100644
--- a/src/main/frontend/src/index.css
+++ b/src/main/frontend/src/index.css
@@ -906,6 +906,32 @@ select {
font-family: 'Inter', sans-serif;
}
+.file-upload-zone {
+ border: 2px dashed var(--border);
+ border-radius: 8px;
+ padding: 18px;
+ text-align: center;
+ cursor: pointer;
+ transition: all .2s;
+ margin-bottom: 10px;
+ color: var(--text-muted);
+ font-size: 13px;
+ font-family: 'Inter', sans-serif;
+}
+.file-upload-zone:hover {
+ border-color: var(--primary);
+ color: var(--primary);
+}
+.file-upload-zone.drag-over {
+ border-color: var(--primary);
+ background: rgba(99, 102, 241, 0.08);
+ color: var(--primary);
+}
+.file-upload-zone.uploading {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition-duration: 0ms !important;
diff --git a/src/main/frontend/src/pages/Dashboard.jsx b/src/main/frontend/src/pages/Dashboard.jsx
index 1158c7d..451c0cc 100644
--- a/src/main/frontend/src/pages/Dashboard.jsx
+++ b/src/main/frontend/src/pages/Dashboard.jsx
@@ -4,7 +4,8 @@ import { useTheme } from '../context/ThemeContext'
import {
getStatus, toggleServer, getGadgets,
generatePayload as apiGenerate, updateConfig, setAuthToken, getLogs,
- getFiles, downloadFile, deleteFile as apiDeleteFile
+ getFiles, downloadFile, deleteFile as apiDeleteFile,
+ uploadFile
} from '../api'
export default function Dashboard() {
@@ -53,6 +54,9 @@ export default function Dashboard() {
const logEndRef = useRef(null)
const [files, setFiles] = useState([])
const [filesLoading, setFilesLoading] = useState(false)
+ const [dragOver, setDragOver] = useState(false)
+ const [uploading, setUploading] = useState(false)
+ const fileInputRef = useRef(null)
const ROUTING_OPTIONS = ['Basic', 'ELProcessor', 'Groovy', 'jdbcBypass1', 'jdbcBypass2', 'ldap2rmi', 'SnakeYaml', 'XStream', 'MemoryXXE']
@@ -149,7 +153,7 @@ export default function Dashboard() {
setMsg({ success: '', error: res.data.error || 'Generation failed' })
}
} catch (e) {
- setMsg({ success: '', error: 'Failed to generate payload' })
+ setMsg({ success: '', error: e.response?.data?.error || 'Failed to generate payload' })
} finally { setLoading(false) }
}
@@ -279,6 +283,56 @@ export default function Dashboard() {
} catch { /* ignore */ }
}
+ function handleDragOver(e) {
+ e.preventDefault()
+ e.stopPropagation()
+ setDragOver(true)
+ }
+
+ function handleDragLeave(e) {
+ e.preventDefault()
+ e.stopPropagation()
+ setDragOver(false)
+ }
+
+ function handleDrop(e) {
+ e.preventDefault()
+ e.stopPropagation()
+ setDragOver(false)
+ const droppedFiles = e.dataTransfer.files
+ if (droppedFiles.length > 0) {
+ doUpload(droppedFiles[0])
+ }
+ }
+
+ function handleFileSelect(e) {
+ const selectedFiles = e.target.files
+ if (selectedFiles.length > 0) {
+ doUpload(selectedFiles[0])
+ }
+ e.target.value = ''
+ }
+
+ async function doUpload(file) {
+ setUploading(true)
+ setMsg({ success: '', error: '' })
+ try {
+ const formData = new FormData()
+ formData.append('file', file)
+ const res = await uploadFile(formData)
+ if (res.data.success) {
+ setMsg({ success: 'Uploaded: ' + res.data.name, error: '' })
+ fetchFiles()
+ } else {
+ setMsg({ success: '', error: res.data.error || 'Upload failed' })
+ }
+ } catch (e) {
+ setMsg({ success: '', error: 'Upload failed' })
+ } finally {
+ setUploading(false)
+ }
+ }
+
useEffect(() => {
if (activeJndiTab !== 'logs') return
fetchLogs()
@@ -814,6 +868,25 @@ export default function Dashboard() {
{filesLoading ? 'Loading...' : 'Refresh'}
+
fileInputRef.current?.click()}
+ >
+
+ {uploading ? (
+ Uploading...
+ ) : (
+ Drop file here or click to upload
+ )}
+
{files.length === 0 ? (
No saved files
diff --git a/src/main/java/com/qi4l/JYso/Starter.java b/src/main/java/com/qi4l/JYso/Starter.java
index a3f8a76..d9ed254 100644
--- a/src/main/java/com/qi4l/JYso/Starter.java
+++ b/src/main/java/com/qi4l/JYso/Starter.java
@@ -38,7 +38,12 @@ public class Starter {
if (args[0].equals("-y")) {
JYsoMode = true;
- ysoserial.run(args);
+ try {
+ ysoserial.run(args);
+ } catch (Exception e) {
+ System.err.println("[!] " + e.getMessage());
+ System.exit(1);
+ }
}
}
}
diff --git a/src/main/java/com/qi4l/JYso/gadgets/Config/ysoserial.java b/src/main/java/com/qi4l/JYso/gadgets/Config/ysoserial.java
index e203029..91c3815 100644
--- a/src/main/java/com/qi4l/JYso/gadgets/Config/ysoserial.java
+++ b/src/main/java/com/qi4l/JYso/gadgets/Config/ysoserial.java
@@ -84,9 +84,7 @@ public class ysoserial {
//载入gadget
final Class extends ObjectPayload>> payloadClass = ObjectPayload.Utils.getPayloadClass(payloadType);
if (payloadClass == null) {
- System.err.println("Invalid payload type '" + payloadType + "'");
- printUsage(options);
- System.exit(1);
+ throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'");
}
diff --git a/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java b/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java
index 0b156c5..f1efd9b 100644
--- a/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java
+++ b/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java
@@ -15,6 +15,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.util.EnumSet;
@@ -70,7 +71,9 @@ public class JYsoWebApplication {
context.addServlet(new ServletHolder(new AuthServlet()), "/api/auth/login");
- context.addServlet(new ServletHolder(new JettyApiServlet()), "/api/*");
+ ServletHolder apiHolder = new ServletHolder(new JettyApiServlet());
+ apiHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(""));
+ context.addServlet(apiHolder, "/api/*");
context.addFilter(new FilterHolder(new SpaFallbackFilter()), "/*", EnumSet.of(DispatcherType.REQUEST));
diff --git a/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java b/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java
index b8ab31e..9afc5b9 100644
--- a/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java
+++ b/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java
@@ -11,16 +11,21 @@ import com.qi4l.JYso.gadgets.Config.Config;
import com.qi4l.JYso.gadgets.Config.ysoserial;
import javax.servlet.ServletException;
+import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.util.*;
+@MultipartConfig
public class JettyApiServlet extends HttpServlet {
@Override
@@ -58,6 +63,8 @@ public class JettyApiServlet extends HttpServlet {
handleFileDownload(req, resp);
} else if ("/files/delete".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
handleFileDelete(req, resp);
+ } else if ("/files/upload".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
+ handleFileUpload(req, resp);
} else {
resp.setStatus(404);
resp.getWriter().write("{\"error\":\"Not found\"}");
@@ -159,6 +166,34 @@ public class JettyApiServlet extends HttpServlet {
resp.getWriter().write(JSON.toJSONString(result));
}
+ private void handleFileUpload(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ Map
result = new LinkedHashMap<>();
+ try {
+ Part filePart = req.getPart("file");
+ if (filePart == null || filePart.getSize() == 0) {
+ result.put("success", false);
+ result.put("error", "no file uploaded");
+ resp.getWriter().write(JSON.toJSONString(result));
+ return;
+ }
+ String fileName = filePart.getSubmittedFileName();
+ if (fileName == null || fileName.isEmpty()) {
+ fileName = "uploaded_file";
+ }
+ Path targetPath = Paths.get(fileName);
+ try (InputStream input = filePart.getInputStream()) {
+ Files.copy(input, targetPath, StandardCopyOption.REPLACE_EXISTING);
+ }
+ result.put("success", true);
+ result.put("name", fileName);
+ result.put("size", Files.size(targetPath));
+ } catch (ServletException e) {
+ result.put("success", false);
+ result.put("error", "upload failed: " + e.getMessage());
+ }
+ resp.getWriter().write(JSON.toJSONString(result));
+ }
+
private void handleServersStart(HttpServletRequest req, HttpServletResponse resp) throws IOException {
JSONObject json = readJson(req);
Map result = new LinkedHashMap<>();
@@ -236,27 +271,39 @@ public class JettyApiServlet extends HttpServlet {
resp.getWriter().write(JSON.toJSONString(result));
return;
}
- boolean nowRunning;
+ boolean nowRunning = false;
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;
+ if (LdapServer.isRunning) {
+ LdapServer.stop();
+ } else {
+ new Thread(() -> { try { LdapServer.start(); } catch (Exception ignored) {} }, "ldap-toggler").start();
+ nowRunning = true;
+ }
break;
case "ldaps":
- if (LdapsServer.isRunning) LdapsServer.stop();
- else new Thread(() -> { try { LdapsServer.start(); } catch (Exception ignored) {} }, "ldaps-toggler").start();
- nowRunning = !LdapsServer.isRunning;
+ if (LdapsServer.isRunning) {
+ LdapsServer.stop();
+ } else {
+ new Thread(() -> { try { LdapsServer.start(); } catch (Exception ignored) {} }, "ldaps-toggler").start();
+ nowRunning = true;
+ }
break;
case "http":
- if (HTTPServer.isRunning) HTTPServer.stop();
- else new Thread(() -> { try { HTTPServer.start(); } catch (Exception ignored) {} }, "http-toggler").start();
- nowRunning = !HTTPServer.isRunning;
+ if (HTTPServer.isRunning) {
+ HTTPServer.stop();
+ } else {
+ new Thread(() -> { try { HTTPServer.start(); } catch (Exception ignored) {} }, "http-toggler").start();
+ nowRunning = true;
+ }
break;
case "rmi":
- if (RMIServer.isRunning) RMIServer.stop();
- else new Thread(() -> { try { RMIServer.start(); } catch (Exception ignored) {} }, "rmi-toggler").start();
- nowRunning = !RMIServer.isRunning;
+ if (RMIServer.isRunning) {
+ RMIServer.stop();
+ } else {
+ new Thread(() -> { try { RMIServer.start(); } catch (Exception ignored) {} }, "rmi-toggler").start();
+ nowRunning = true;
+ }
break;
default:
result.put("success", false);