View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    *
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   *******************************************************************************/
11  package com.jsql.view.swing.tab;
12  
13  import com.formdev.flatlaf.extras.FlatSVGIcon;
14  import com.jsql.model.bean.database.AbstractElementDatabase;
15  import com.jsql.util.I18nUtil;
16  import com.jsql.util.LogLevelUtil;
17  import com.jsql.util.StringUtil;
18  import com.jsql.util.reverse.ModelReverse;
19  import com.jsql.view.swing.action.ActionCloseTabResult;
20  import com.jsql.view.swing.action.HotkeyUtil;
21  import com.jsql.view.swing.popupmenu.JPopupMenuText;
22  import com.jsql.view.swing.tab.dnd.DnDTabbedPane;
23  import com.jsql.view.swing.tab.dnd.TabTransferHandler;
24  import com.jsql.view.swing.table.PanelTable;
25  import com.jsql.view.swing.terminal.AbstractExploit;
26  import com.jsql.view.swing.terminal.ExploitReverseShell;
27  import com.jsql.view.swing.text.JPopupTextArea;
28  import com.jsql.view.swing.text.JTextFieldPlaceholder;
29  import com.jsql.view.swing.util.MediatorHelper;
30  import com.jsql.view.swing.util.RadioItemPreventClose;
31  import com.jsql.view.swing.util.UiStringUtil;
32  import com.jsql.view.swing.util.UiUtil;
33  import org.apache.commons.lang3.StringUtils;
34  import org.apache.logging.log4j.LogManager;
35  import org.apache.logging.log4j.Logger;
36  
37  import javax.swing.*;
38  import javax.swing.event.HyperlinkEvent;
39  import java.awt.*;
40  import java.awt.event.*;
41  import java.io.IOException;
42  import java.net.MalformedURLException;
43  import java.net.URISyntaxException;
44  import java.util.Arrays;
45  import java.util.List;
46  import java.util.UUID;
47  import java.util.function.BiConsumer;
48  import java.util.function.IntConsumer;
49  
50  /**
51   * TabbedPane containing result injection panels.
52   */
53  public class TabResults extends DnDTabbedPane {
54  
55      private static final Logger LOGGER = LogManager.getRootLogger();
56  
57      public static final String TAB_EXPLOIT_FAILURE_INCORRECT_URL = "Tab exploit failure: incorrect URL";
58      public static final String UDF_SHELL = "UDF shell";
59      public static final String SQL_SHELL = "sqlShell";
60      public static final String WEB_SHELL = "webShell";
61      public static final String REV_SHELL = "revShell";
62      public static final String REVERSE_SHELL = "Reverse shell";
63  
64      /**
65       * Create the panel containing injection results.
66       */
67      public TabResults() {
68          this.setName("tabResults");
69          this.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
70          this.setTransferHandler(new TabTransferHandler());
71          this.putClientProperty("JTabbedPane.tabClosable", true);
72          this.putClientProperty("JTabbedPane.tabCloseCallback", (IntConsumer) ActionCloseTabResult::perform);
73          UIManager.put("TabbedPane.closeHoverForeground", LogLevelUtil.COLOR_RED);
74          HotkeyUtil.addShortcut(this);  // Add hotkeys to root-pane ctrl-tab, ctrl-shift-tab, ctrl-w
75          this.addMouseWheelListener(new TabbedPaneMouseWheelListener());
76          MediatorHelper.register(this);
77      }
78  
79      public void addFileTab(String label, String content, String path) {
80          JTextArea fileText = new JPopupTextArea().getProxy();
81          fileText.setText(content);
82          fileText.setFont(new Font(UiUtil.FONT_NAME_MONO_NON_ASIAN, Font.PLAIN, 14));
83          fileText.setCaretPosition(0);
84          this.addTextTab(label, path, fileText, UiUtil.DOWNLOAD.getIcon());
85          MediatorHelper.tabManagersCards().addToLists(path, label);
86      }
87  
88      public void addReportTab(String content) {
89          JEditorPane editorPane = new JEditorPane();
90          editorPane.setContentType("text/html");
91          editorPane.setText("<html><span style=\"white-space: nowrap; font-family:'"+ UiUtil.FONT_NAME_MONO_NON_ASIAN +"'\">" + content + "</span></html>");
92          editorPane.setFont(UIManager.getFont("TextArea.font"));  // required to increase text size
93          editorPane.setDragEnabled(true);
94          editorPane.setEditable(false);
95          editorPane.setCaretPosition(0);
96          editorPane.getCaret().setBlinkRate(0);
97          editorPane.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
98          editorPane.setComponentPopupMenu(new JPopupMenuText(editorPane));
99          editorPane.addHyperlinkListener(linkEvent -> {
100             if (HyperlinkEvent.EventType.ACTIVATED.equals(linkEvent.getEventType())) {
101                 try {
102                     Desktop.getDesktop().browse(linkEvent.getURL().toURI());
103                 } catch (IOException | URISyntaxException | UnsupportedOperationException e) {
104                     LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Failing to browse Url", e);
105                 }
106             }
107         });
108         editorPane.addFocusListener(new FocusAdapter() {
109             @Override
110             public void focusGained(FocusEvent focusEvent) {
111                 editorPane.getCaret().setVisible(true);
112                 editorPane.getCaret().setSelectionVisible(true);
113                 editorPane.getCaret().setBlinkRate(0);
114             }
115         });
116         UiUtil.init(editorPane);  // silent delete
117 
118         this.addTextTab("Vulnerability report", "Analysis report with all payloads detected", editorPane, UiUtil.APP_ICON.getIcon());
119     }
120 
121     public void addTextTab(String label, String toolTipText, JComponent componentText, FlatSVGIcon icon) {
122         var scroller = new JScrollPane(componentText);
123         this.addTab(label + StringUtils.SPACE, scroller);
124         this.setSelectedComponent(scroller);  // Focus on the new tab
125         this.setToolTipTextAt(this.indexOfComponent(scroller), toolTipText);
126         var header = new TabHeader(label, icon);
127         this.setTabComponentAt(this.indexOfComponent(scroller), header);
128 
129         this.updateUI();  // required: light, open/close prefs, dark => light artifacts
130     }
131 
132     public void addTabExploitWeb(String url) {
133         try {
134             var terminalID = UUID.randomUUID();
135             var terminal = new AbstractExploit(terminalID, url, "web") {
136                 @Override
137                 public void action(String command, UUID terminalID, String urlShell, String... arg) {
138                     MediatorHelper.model().getResourceAccess().runWebShell(command, terminalID, urlShell);
139                 }
140             };
141             terminal.setName(TabResults.WEB_SHELL);
142             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
143 
144             JPanel panelTerminalWithReverse = this.getTerminalWithMenu(terminal);
145             this.addTab("Web shell", panelTerminalWithReverse);
146             this.setSelectedComponent(panelTerminalWithReverse);  // Focus on the new tab
147 
148             var header = new TabHeader("Web shell", UiUtil.TERMINAL.getIcon());
149             this.setTabComponentAt(this.indexOfComponent(panelTerminalWithReverse), header);
150             terminal.requestFocusInWindow();
151 
152             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
153         } catch (MalformedURLException | URISyntaxException e) {
154             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
155         }
156     }
157 
158     public void addTabExploitReverseShell(String port) {
159         try {
160             var terminalID = UUID.randomUUID();
161             var terminal = new ExploitReverseShell(terminalID, port);
162             terminal.setName(TabResults.REV_SHELL);
163             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
164 
165             JScrollPane scroller = new JScrollPane(terminal);
166             this.addTab(TabResults.REVERSE_SHELL, scroller);
167             this.setSelectedComponent(scroller);  // Focus on the new tab
168 
169             var header = new TabHeader(TabResults.REVERSE_SHELL, UiUtil.TERMINAL.getIcon());
170             this.setTabComponentAt(this.indexOfComponent(scroller), header);
171             terminal.requestFocusInWindow();
172 
173             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
174         } catch (URISyntaxException | IOException e) {
175             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
176         }
177     }
178 
179     public void addTabExploitUdf(BiConsumer<String, UUID> biConsumerRunCmd) {
180         try {
181             var terminalID = UUID.randomUUID();
182             var terminal = new AbstractExploit(terminalID, null, "udf") {
183                 @Override
184                 public void action(String command, UUID terminalID, String urlShell, String... arg) {
185                     biConsumerRunCmd.accept(command, terminalID);
186                 }
187             };
188             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
189 
190             JPanel panelTerminalWithReverse = this.getTerminalWithMenu(terminal);
191             this.addTab(TabResults.UDF_SHELL, panelTerminalWithReverse);
192             this.setSelectedComponent(panelTerminalWithReverse);  // Focus on the new tab
193 
194             var header = new TabHeader(TabResults.UDF_SHELL, UiUtil.TERMINAL.getIcon());
195             this.setTabComponentAt(this.indexOfComponent(panelTerminalWithReverse), header);
196             terminal.requestFocusInWindow();
197 
198             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
199         } catch (MalformedURLException | URISyntaxException e) {
200             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
201         }
202     }
203 
204     public void addTabExploitSql(String url, String user, String pass) {
205         try {
206             var terminalID = UUID.randomUUID();
207             var terminal = new AbstractExploit(terminalID, url, "sql") {
208                 @Override
209                 public void action(String cmd, UUID terminalID, String wbhPath, String... arg) {
210                     MediatorHelper.model().getResourceAccess().runSqlShell(cmd, terminalID, wbhPath, arg[0], arg[1]);
211                 }
212             };
213             terminal.setName(TabResults.SQL_SHELL);
214             terminal.setLoginPassword(new String[]{ user, pass });
215             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
216 
217             JScrollPane scroller = new JScrollPane(terminal);
218             this.addTab("SQL shell", scroller);
219             this.setSelectedComponent(scroller);  // Focus on the new tab
220 
221             var header = new TabHeader("SQL shell", UiUtil.TERMINAL.getIcon());
222             this.setTabComponentAt(this.indexOfComponent(scroller), header);
223             terminal.requestFocusInWindow();
224 
225             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
226         } catch (MalformedURLException | URISyntaxException e) {
227             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
228         }
229     }
230     
231     public void addTabValues(String[][] data, String[] columnNames, AbstractElementDatabase table) {
232         var panelTable = new PanelTable(data, columnNames);
233         
234         this.addTab(StringUtil.detectUtf8(table.toString()), panelTable);
235         panelTable.setComponentOrientation(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()));
236         
237         this.setSelectedComponent(panelTable);  // Focus on the new tab
238 
239         var header = new TabHeader(UiStringUtil.detectUtf8Html(table.toString()), UiUtil.TABLE_BOLD.getIcon());
240         this.setTabComponentAt(this.indexOfComponent(panelTable), header);
241 
242         this.updateUI();  // required: light, open/close prefs, dark => light artifacts
243     }
244 
245     private JPanel getTerminalWithMenu(AbstractExploit terminal) {
246         JPanel panelTerminalWithReverse = new JPanel() {
247             @Override
248             public boolean isOptimizedDrawingEnabled() {
249                 return false;  // both components always visible
250             }
251         };
252         OverlayLayout overlay = new OverlayLayout(panelTerminalWithReverse);
253         panelTerminalWithReverse.setLayout(overlay);
254 
255         var panelReverseMargin = new JPanel();
256         panelReverseMargin.setLayout(new BoxLayout(panelReverseMargin, BoxLayout.LINE_AXIS));
257         panelReverseMargin.setOpaque(false);
258         panelReverseMargin.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 15));
259 
260         var menuReverse = new JLabel(TabResults.REVERSE_SHELL, UiUtil.ARROW_DOWN.getIcon(), SwingConstants.LEFT);
261         menuReverse.addMouseListener(new MouseAdapter() {
262             @Override
263             public void mousePressed(MouseEvent e) {
264                 var popupMenu = TabResults.this.showMenu(terminal);
265                 popupMenu.updateUI();  // required: incorrect when dark/light mode switch
266                 popupMenu.show(e.getComponent(), e.getComponent().getX(),5 + e.getComponent().getY() + e.getComponent().getHeight());
267                 popupMenu.setLocation(e.getComponent().getLocationOnScreen().x,5 + e.getComponent().getLocationOnScreen().y + e.getComponent().getHeight());
268             }
269         });
270         menuReverse.setMaximumSize(menuReverse.getPreferredSize());
271         JScrollPane scrollerTerminal = new JScrollPane(terminal);
272         scrollerTerminal.setAlignmentX(1f);
273         scrollerTerminal.setAlignmentY(0f);
274         panelReverseMargin.setAlignmentX(1f);
275         panelReverseMargin.setAlignmentY(0f);
276         panelReverseMargin.add(menuReverse);
277         panelTerminalWithReverse.add(panelReverseMargin);
278         panelTerminalWithReverse.add(scrollerTerminal);
279 
280         return panelTerminalWithReverse;
281     }
282 
283     private JPopupMenu showMenu(AbstractExploit terminal) {
284         JPopupMenu menuReverse = new JPopupMenu();
285 
286         var menuListen = new JMenu("Listen");
287         menuListen.setComponentOrientation(
288             ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
289             ? ComponentOrientation.LEFT_TO_RIGHT
290             : ComponentOrientation.RIGHT_TO_LEFT
291         );
292         var panelPublicAddress = new JPanel(new BorderLayout());
293         panelPublicAddress.add(new JLabel("<html><b>Your public address (listener) :</b></html>"));
294         menuListen.add(panelPublicAddress);
295         menuListen.add(new JSeparator());
296         var address = new JTextFieldPlaceholder("Local IP/domain", "10.0.2.2");
297         menuListen.add(address);
298         var port = new JTextFieldPlaceholder("Local port", "4444");
299         menuListen.add(port);
300 
301         var panelServerConnection = new JPanel(new BorderLayout());
302         panelServerConnection.add(new JLabel("<html><b>Server method (connector) :</b></html>"));
303         menuListen.add(panelServerConnection);
304         menuListen.add(new JSeparator());
305         var buttonGroup = new ButtonGroup();
306         List<ModelReverse> commandsReverse = MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getCommandsReverse();
307         commandsReverse.forEach(modelReverse -> {
308             var radio = new RadioItemPreventClose(modelReverse.getName());
309             radio.setActionCommand(modelReverse.getName());
310             radio.setSelected("bash".equals(modelReverse.getName()));
311             buttonGroup.add(radio);
312             menuListen.add(radio);
313         });
314 
315         Runnable runnableReverse = () -> {
316             try {
317                 Thread.sleep(2500);
318                 MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getCommandsReverse().stream()
319                 .filter(modelReverse -> modelReverse.getName().equals(buttonGroup.getSelection().getActionCommand()))
320                 .findFirst()
321                 .ifPresent(modelReverse -> MediatorHelper.model().getResourceAccess().runWebShell(
322                     String.format(modelReverse.getCommand(), address.getText(), port.getText()),
323                     null,  // ignore connection response
324                     terminal.getUrlShell(),
325                     true
326                 ));
327             } catch (InterruptedException e) {
328                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
329                 Thread.currentThread().interrupt();
330             }
331         };
332 
333         var panelOpenIn = new JPanel(new BorderLayout());
334         panelOpenIn.add(new JLabel("<html><b>Open In :</b></html>"));
335         menuListen.add(panelOpenIn);
336         menuListen.add(new JSeparator());
337 
338         var menuBuiltInShell = new RadioItemPreventClose("Built-in shell", true);
339         var menuExternalShell = new RadioItemPreventClose("External listening shell");
340         var buttonTypeShell = new ButtonGroup();
341         buttonTypeShell.add(menuBuiltInShell);
342         buttonTypeShell.add(menuExternalShell);
343         menuListen.add(menuBuiltInShell);
344         menuListen.add(menuExternalShell);
345         menuListen.add(new JSeparator());
346         var panelCreate = new JPanel(new BorderLayout());
347         panelCreate.add(new JButton(new AbstractAction("Create reverse shell") {
348             @Override
349             public void actionPerformed(ActionEvent e) {
350                 if (menuBuiltInShell.isSelected()) {
351                     SwingUtilities.invokeLater(() -> MediatorHelper.tabResults().addTabExploitReverseShell(port.getText()));
352                 }
353                 new Thread(runnableReverse).start();
354                 menuReverse.setVisible(false);
355             }
356         }));
357         menuListen.add(panelCreate);
358 
359         var menuConnect = new JMenu("Connect");
360         menuConnect.setComponentOrientation(
361             ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
362             ? ComponentOrientation.LEFT_TO_RIGHT
363             : ComponentOrientation.RIGHT_TO_LEFT
364         );
365         var panelServerPublicAddress = new JPanel(new BorderLayout());
366         panelServerPublicAddress.add(new JLabel("<html><b>Server public address (listener) :</b></html>"));
367         menuConnect.add(panelServerPublicAddress);
368         menuConnect.add(new JSeparator());
369         menuConnect.add(new JTextFieldPlaceholder("Target IP/domain"));
370         menuConnect.add(new JTextFieldPlaceholder("Target port"));
371         menuConnect.add(new JSeparator());
372 
373         var panelServerListeningConnection = new JPanel(new BorderLayout());
374         panelServerListeningConnection.add(new JLabel("<html><b>Server listening method :</b></html>"));
375         menuConnect.add(panelServerListeningConnection);
376         var buttonGroupListening = new ButtonGroup();
377         Arrays.asList("netcat").forEach(method -> {
378             var radio = new JRadioButtonMenuItem(method) {
379                 @Override
380                 protected void processMouseEvent(MouseEvent evt) {
381                     if (evt.getID() == MouseEvent.MOUSE_RELEASED && this.contains(evt.getPoint())) {
382                         this.doClick();
383                         this.setArmed(true);
384                     } else {
385                         super.processMouseEvent(evt);
386                     }
387                 }
388             };
389             radio.setSelected("netcat".equals(method));
390             buttonGroupListening.add(radio);
391             menuConnect.add(radio);
392         });
393         menuConnect.add(new JSeparator());
394         menuConnect.add(new JMenuItem("Create"));
395 
396         menuReverse.add(menuListen);
397         menuReverse.add(menuConnect);
398 
399         return menuReverse;
400     }
401 }