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.*;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.logging.log4j.LogManager;
32  import org.apache.logging.log4j.Logger;
33  import org.jsoup.Jsoup;
34  import org.jsoup.safety.Safelist;
35  
36  import javax.swing.*;
37  import javax.swing.event.HyperlinkEvent;
38  import javax.swing.text.DefaultEditorKit;
39  import java.awt.*;
40  import java.awt.datatransfer.StringSelection;
41  import java.awt.event.*;
42  import java.io.IOException;
43  import java.net.MalformedURLException;
44  import java.net.URISyntaxException;
45  import java.util.Arrays;
46  import java.util.EmptyStackException;
47  import java.util.List;
48  import java.util.UUID;
49  import java.util.function.BiConsumer;
50  import java.util.function.IntConsumer;
51  
52  /**
53   * TabbedPane containing result injection panels.
54   */
55  public class TabResults extends DnDTabbedPane {
56  
57      private static final Logger LOGGER = LogManager.getRootLogger();
58  
59      public static final String TAB_EXPLOIT_FAILURE_INCORRECT_URL = "Tab exploit failure: incorrect URL";
60      public static final String UDF_SHELL = "UDF shell";
61      public static final String SQL_SHELL = "SQL shell";
62      public static final String REVERSE_SHELL = "Reverse shell";
63      public static final String WEB_SHELL = "webShell";
64      public static final String TAB_RESULTS = "tabResults";
65  
66      /**
67       * Create the panel containing injection results.
68       */
69      public TabResults() {
70          this.setName(TabResults.TAB_RESULTS);
71          this.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
72          this.setTransferHandler(new TabTransferHandler());
73          this.putClientProperty("JTabbedPane.tabClosable", true);
74          this.putClientProperty("JTabbedPane.tabCloseCallback", (IntConsumer) ActionCloseTabResult::perform);
75          UIManager.put("TabbedPane.closeHoverForeground", LogLevelUtil.COLOR_RED);
76          HotkeyUtil.addShortcut(this);  // Add hotkeys to root-pane ctrl-tab, ctrl-shift-tab, ctrl-w
77          this.addMouseWheelListener(new TabbedPaneMouseWheelListener());
78          MediatorHelper.register(this);
79      }
80  
81      public void addFileTab(String label, String content, String path) {
82          MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
83              JTextArea fileText = new JPopupTextArea().getProxy();
84              fileText.setText(content);
85              fileText.setFont(new Font(UiUtil.FONT_NAME_MONO_NON_ASIAN, Font.PLAIN, 14));
86              fileText.setCaretPosition(0);
87              this.addTextTab(label, path, fileText, UiUtil.DOWNLOAD.getIcon());
88              MediatorHelper.tabManagersCards().addToLists(path, label);
89          });
90      }
91  
92      public void addAdminTab(String urlSuccess) {
93          MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
94              String htmlSource = StringUtils.EMPTY;
95  
96              // Fix #4081: SocketTimeoutException on get()
97              // Fix #44642: NoClassDefFoundError on get()
98              // Fix #44641: ExceptionInInitializerError on get()
99              try {
100                 // Previous test for 2xx Success and 3xx Redirection was Header only,
101                 // now get the HTML content.
102                 // Proxy is used by jsoup
103                 htmlSource = Jsoup.clean(
104                     Jsoup.connect(urlSuccess)
105                         // Prevent exception on UnsupportedMimeTypeException: Unhandled content type. Must be text/*, application/xml, or application/*+xml
106                         .ignoreContentType(true)
107                         // Prevent exception on HTTP errors
108                         .ignoreHttpErrors(true)
109                         .get()
110                         .html()
111                         .replaceAll("<img[^>]*>", StringUtils.EMPTY)
112                         .replaceAll("<input[^>]*type=\"?hidden\"?[^>]*>", StringUtils.EMPTY)
113                         .replaceAll("<input[^>]*type=\"?(submit|button)\"?[^>]*>", "<div style=\"background-color:#eeeeee;text-align:center;border:1px solid black;width:100px;\">button</div>")
114                         .replaceAll("<input[^>]*>", "<div style=\"text-align:center;border:1px solid black;width:100px;\">input</div>"),
115                     Safelist.relaxed()
116                         .addTags("center", "div", "span")
117                         .addAttributes(":all", "style")
118                 );
119             } catch (IOException e) {
120                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Failure opening page: {}", e.getMessage());
121             } catch (ExceptionInInitializerError | NoClassDefFoundError e) {
122                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
123             }
124 
125             final var browser = new JTextPane();
126             browser.setContentType("text/html");
127             browser.setEditable(false);
128             browser.setCaretPosition(0);
129 
130             // Fix #43220: EmptyStackException on setText()
131             // Fix #94242: IndexOutOfBoundsException on setText()
132             try {
133                 browser.setText(htmlSource);
134             } catch (IndexOutOfBoundsException | EmptyStackException e) {
135                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
136             }
137 
138             JMenuItem itemCopyUrl = new JMenuItem(I18nUtil.valueByKey("CONTEXT_MENU_COPY_PAGE_URL"));
139             I18nViewUtil.addComponentForKey("CONTEXT_MENU_COPY_PAGE_URL", itemCopyUrl);
140 
141             JMenuItem itemCopy = new JMenuItem();
142             itemCopy.setAction(browser.getActionMap().get(DefaultEditorKit.copyAction));
143             itemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
144             itemCopy.setMnemonic('C');
145             itemCopy.setText(I18nUtil.valueByKey("CONTEXT_MENU_COPY"));
146             I18nViewUtil.addComponentForKey("CONTEXT_MENU_COPY", itemCopy);
147 
148             JMenuItem itemSelectAll = new JMenuItem();
149             itemSelectAll.setAction(browser.getActionMap().get(DefaultEditorKit.selectAllAction));
150             itemSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK));
151             itemSelectAll.setText(I18nUtil.valueByKey("CONTEXT_MENU_SELECT_ALL"));
152             I18nViewUtil.addComponentForKey("CONTEXT_MENU_SELECT_ALL", itemSelectAll);
153             itemSelectAll.setMnemonic('A');
154 
155             final var menu = new JPopupMenu();
156             menu.add(itemCopyUrl);
157             menu.add(new JSeparator());
158             menu.add(itemCopy);
159             menu.add(itemSelectAll);
160             menu.applyComponentOrientation(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()));
161 
162             itemCopyUrl.addActionListener(actionEvent -> {
163                 var stringSelection = new StringSelection(urlSuccess);
164                 var clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
165                 clipboard.setContents(stringSelection, null);
166             });
167 
168             itemSelectAll.addActionListener(actionEvent -> browser.selectAll());
169 
170             browser.addFocusListener(new FocusAdapter() {
171                 @Override
172                 public void focusGained(FocusEvent focusEvent) {
173                 browser.getCaret().setVisible(true);
174                 browser.getCaret().setSelectionVisible(true);
175                 }
176             });
177             browser.addMouseListener(new BrowserMouseAdapter(browser, menu));
178 
179             final var scroller = new JScrollPane(browser);
180             MediatorHelper.tabResults().addTab(urlSuccess.replaceAll(".*/", StringUtils.EMPTY) + StringUtils.SPACE, scroller);
181             try {  // Fix #96175: ArrayIndexOutOfBoundsException on setSelectedComponent()
182                 MediatorHelper.tabResults().setSelectedComponent(scroller);  // Focus on the new tab
183             } catch (ArrayIndexOutOfBoundsException e) {
184                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
185             }
186             MediatorHelper.tabResults().setToolTipTextAt(
187                 MediatorHelper.tabResults().indexOfComponent(scroller),
188                 String.format("<html>%s</html>", urlSuccess)
189             );
190 
191             // Create a custom tab header
192             var header = new TabHeader(
193                 urlSuccess.replaceAll(".*/", StringUtils.EMPTY),
194                 UiUtil.ADMIN.getIcon()
195             );
196             MediatorHelper.tabResults().setTabComponentAt(MediatorHelper.tabResults().indexOfComponent(scroller), header);  // Apply the custom header to the tab
197             browser.setCaretPosition(0);
198 
199             MediatorHelper.tabResults().updateUI();  // required: light, open/close prefs, dark => light artifacts
200         });
201     }
202 
203     public void addReportTab(String content) {
204         MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
205             JEditorPane editorPane = new JEditorPane();
206             editorPane.setContentType("text/html");
207             editorPane.setText("<html><span style=\"white-space: nowrap; font-family:'"+ UiUtil.FONT_NAME_MONO_NON_ASIAN +"'\">" + content + "</span></html>");
208             editorPane.setFont(UIManager.getFont("TextArea.font"));  // required to increase text size
209             editorPane.setDragEnabled(true);
210             editorPane.setEditable(false);
211             editorPane.setCaretPosition(0);
212             editorPane.getCaret().setBlinkRate(0);
213             editorPane.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
214             editorPane.setComponentPopupMenu(new JPopupMenuText(editorPane));
215             editorPane.addHyperlinkListener(linkEvent -> {
216                 if (HyperlinkEvent.EventType.ACTIVATED.equals(linkEvent.getEventType())) {
217                     try {
218                         Desktop.getDesktop().browse(linkEvent.getURL().toURI());
219                     } catch (IOException | URISyntaxException | UnsupportedOperationException e) {
220                         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Failing to browse Url", e);
221                     }
222                 }
223             });
224             editorPane.addFocusListener(new FocusAdapter() {
225                 @Override
226                 public void focusGained(FocusEvent focusEvent) {
227                 editorPane.getCaret().setVisible(true);
228                 editorPane.getCaret().setSelectionVisible(true);
229                 editorPane.getCaret().setBlinkRate(0);
230                 }
231             });
232             UiUtil.init(editorPane);  // silent delete
233 
234             this.addTextTab("Vulnerability report", "Analysis report with all payloads detected", editorPane, UiUtil.APP_ICON.getIcon());
235         });
236     }
237 
238     public void addTextTab(String label, String toolTipText, JComponent componentText, FlatSVGIcon icon) {
239         var scroller = new JScrollPane(componentText);
240         this.addTab(label + StringUtils.SPACE, scroller);
241         this.setSelectedComponent(scroller);  // Focus on the new tab
242         this.setToolTipTextAt(this.indexOfComponent(scroller), toolTipText);
243         var header = new TabHeader(label, icon);
244         this.setTabComponentAt(this.indexOfComponent(scroller), header);
245 
246         this.updateUI();  // required: light, open/close prefs, dark => light artifacts
247     }
248 
249     public void addTabExploitWeb(String url) {
250         MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
251             try {
252                 var terminalID = UUID.randomUUID();
253                 var terminal = new AbstractExploit(terminalID, url, "web") {
254                     @Override
255                     public void action(String command, UUID terminalID, String urlShell, String... arg) {
256                         MediatorHelper.model().getResourceAccess().runWebShell(command, terminalID, urlShell);
257                     }
258                 };
259                 terminal.setName(TabResults.WEB_SHELL);
260                 MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
261 
262                 JPanel panelTerminalWithReverse = this.getTerminalWithMenu(terminal);
263                 this.addTab("Web shell", panelTerminalWithReverse);
264                 this.setSelectedComponent(panelTerminalWithReverse);  // Focus on the new tab
265 
266                 var header = new TabHeader("Web shell", UiUtil.TERMINAL.getIcon());
267                 this.setTabComponentAt(this.indexOfComponent(panelTerminalWithReverse), header);
268                 terminal.requestFocusInWindow();
269 
270                 this.updateUI();  // required: light, open/close prefs, dark => light artifacts
271             } catch (MalformedURLException | URISyntaxException e) {
272                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
273             }
274         });
275     }
276 
277     public void addTabExploitReverseShell(String port) {
278         MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
279             try {
280                 var terminalID = UUID.randomUUID();
281                 var terminal = new ExploitReverseShell(terminalID, port);
282                 MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
283 
284                 JScrollPane scroller = new JScrollPane(terminal);
285                 this.addTab(TabResults.REVERSE_SHELL, scroller);
286                 this.setSelectedComponent(scroller);  // Focus on the new tab
287 
288                 var header = new TabHeader(TabResults.REVERSE_SHELL, UiUtil.TERMINAL.getIcon());
289                 this.setTabComponentAt(this.indexOfComponent(scroller), header);
290                 terminal.requestFocusInWindow();
291 
292                 this.updateUI();  // required: light, open/close prefs, dark => light artifacts
293             } catch (URISyntaxException | IOException e) {
294                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
295             }
296         });
297     }
298 
299     public void addTabExploitUdf(BiConsumer<String, UUID> biConsumerRunCmd) {
300         MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
301             try {
302                 var terminalID = UUID.randomUUID();
303                 var terminal = new AbstractExploit(terminalID, null, "udf") {
304                     @Override
305                     public void action(String command, UUID terminalID, String urlShell, String... arg) {
306                         biConsumerRunCmd.accept(command, terminalID);
307                     }
308                 };
309                 MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
310 
311                 JPanel panelTerminalWithReverse = this.getTerminalWithMenu(terminal);
312                 this.addTab(TabResults.UDF_SHELL, panelTerminalWithReverse);
313                 this.setSelectedComponent(panelTerminalWithReverse);  // Focus on the new tab
314 
315                 var header = new TabHeader(TabResults.UDF_SHELL, UiUtil.TERMINAL.getIcon());
316                 this.setTabComponentAt(this.indexOfComponent(panelTerminalWithReverse), header);
317                 terminal.requestFocusInWindow();
318 
319                 this.updateUI();  // required: light, open/close prefs, dark => light artifacts
320             } catch (MalformedURLException | URISyntaxException e) {
321                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
322             }
323         });
324     }
325 
326     public void addTabExploitSql(String url, String user, String pass) {
327         MediatorHelper.frame().getSplitNS().invokeLaterWithSplitOrientation(() -> {
328             try {
329                 var terminalID = UUID.randomUUID();
330                 var terminal = new AbstractExploit(terminalID, url, "sql") {
331                     @Override
332                     public void action(String cmd, UUID terminalID, String wbhPath, String... arg) {
333                         MediatorHelper.model().getResourceAccess().runSqlShell(cmd, terminalID, wbhPath, arg[0], arg[1]);
334                     }
335                 };
336                 terminal.setLoginPassword(new String[]{ user, pass });
337                 MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
338 
339                 JScrollPane scroller = new JScrollPane(terminal);
340                 this.addTab(TabResults.SQL_SHELL, scroller);
341                 this.setSelectedComponent(scroller);  // Focus on the new tab
342 
343                 var header = new TabHeader(TabResults.SQL_SHELL, UiUtil.TERMINAL.getIcon());
344                 this.setTabComponentAt(this.indexOfComponent(scroller), header);
345                 terminal.requestFocusInWindow();
346 
347                 this.updateUI();  // required: light, open/close prefs, dark => light artifacts
348             } catch (MalformedURLException | URISyntaxException e) {
349                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
350             }
351         });
352     }
353     
354     public void addTabValues(String[][] data, String[] columnNames, AbstractElementDatabase table) {
355         var panelTable = new PanelTable(data, columnNames);
356         
357         this.addTab(StringUtil.detectUtf8(table.toString()), panelTable);
358         panelTable.setComponentOrientation(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()));
359         
360         this.setSelectedComponent(panelTable);  // Focus on the new tab
361 
362         var header = new TabHeader(UiStringUtil.detectUtf8Html(table.toString()), UiUtil.TABLE_BOLD.getIcon());
363         this.setTabComponentAt(this.indexOfComponent(panelTable), header);
364 
365         this.updateUI();  // required: light, open/close prefs, dark => light artifacts
366     }
367 
368     private JPanel getTerminalWithMenu(AbstractExploit terminal) {
369         JPanel panelTerminalWithReverse = new JPanel() {
370             @Override
371             public boolean isOptimizedDrawingEnabled() {
372                 return false;  // both components always visible
373             }
374         };
375         OverlayLayout overlay = new OverlayLayout(panelTerminalWithReverse);
376         panelTerminalWithReverse.setLayout(overlay);
377 
378         var panelReverseMargin = new JPanel();
379         panelReverseMargin.setLayout(new BoxLayout(panelReverseMargin, BoxLayout.LINE_AXIS));
380         panelReverseMargin.setOpaque(false);
381         panelReverseMargin.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 15));
382 
383         var menuReverse = new JLabel(TabResults.REVERSE_SHELL, UiUtil.ARROW_DOWN.getIcon(), SwingConstants.LEFT);
384         menuReverse.setName(TabResults.REVERSE_SHELL);
385         menuReverse.addMouseListener(new MouseAdapter() {
386             @Override
387             public void mousePressed(MouseEvent e) {
388                 var popupMenu = TabResults.this.showMenu(terminal);
389                 popupMenu.updateUI();  // required: incorrect when dark/light mode switch
390                 popupMenu.show(e.getComponent(), e.getComponent().getX(),5 + e.getComponent().getY() + e.getComponent().getHeight());
391                 popupMenu.setLocation(e.getComponent().getLocationOnScreen().x,5 + e.getComponent().getLocationOnScreen().y + e.getComponent().getHeight());
392             }
393         });
394         menuReverse.setMaximumSize(menuReverse.getPreferredSize());
395         JScrollPane scrollerTerminal = new JScrollPane(terminal);
396         scrollerTerminal.setAlignmentX(1f);
397         scrollerTerminal.setAlignmentY(0f);
398         panelReverseMargin.setAlignmentX(1f);
399         panelReverseMargin.setAlignmentY(0f);
400         panelReverseMargin.add(menuReverse);
401         panelTerminalWithReverse.add(panelReverseMargin);
402         panelTerminalWithReverse.add(scrollerTerminal);
403 
404         return panelTerminalWithReverse;
405     }
406 
407     private JPopupMenu showMenu(AbstractExploit terminal) {
408         JPopupMenu menuReverse = new JPopupMenu();
409 
410         var menuListen = new JMenu("Listen");
411         menuListen.setComponentOrientation(
412             ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
413             ? ComponentOrientation.LEFT_TO_RIGHT
414             : ComponentOrientation.RIGHT_TO_LEFT
415         );
416         var panelPublicAddress = new JPanel(new BorderLayout());
417         panelPublicAddress.add(new JLabel("<html><b>Your public address (listener) :</b></html>"));
418         menuListen.add(panelPublicAddress);
419         menuListen.add(new JSeparator());
420         var address = new JTextFieldPlaceholder("Local IP/domain", "10.0.2.2");
421         menuListen.add(address);
422         var port = new JTextFieldPlaceholder("Local port", "4444");
423         menuListen.add(port);
424 
425         var panelServerConnection = new JPanel(new BorderLayout());
426         panelServerConnection.add(new JLabel("<html><b>Server method (connector) :</b></html>"));
427         menuListen.add(panelServerConnection);
428         menuListen.add(new JSeparator());
429         var buttonGroup = new ButtonGroup();
430         List<ModelReverse> commandsReverse = MediatorHelper.model().getMediatorUtils().preferencesUtil().getCommandsReverse();
431         commandsReverse.forEach(modelReverse -> {
432             var radio = new RadioItemNonClosing(modelReverse.getName());
433             radio.setActionCommand(modelReverse.getName());
434             radio.setSelected("bash".equals(modelReverse.getName()));
435             buttonGroup.add(radio);
436             menuListen.add(radio);
437         });
438 
439         Runnable runnableReverse = () -> {
440             try {
441                 Thread.sleep(2500);
442                 MediatorHelper.model().getMediatorUtils().preferencesUtil().getCommandsReverse().stream()
443                 .filter(modelReverse -> modelReverse.getName().equals(buttonGroup.getSelection().getActionCommand()))
444                 .findFirst()
445                 .ifPresent(modelReverse -> MediatorHelper.model().getResourceAccess().runWebShell(
446                     String.format(modelReverse.getCommand(), address.getText(), port.getText()),
447                     null,  // ignore connection response
448                     terminal.getUrlShell(),
449                     true
450                 ));
451             } catch (InterruptedException e) {
452                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
453                 Thread.currentThread().interrupt();
454             }
455         };
456 
457         var panelOpenIn = new JPanel(new BorderLayout());
458         panelOpenIn.add(new JLabel("<html><b>Open In :</b></html>"));
459         menuListen.add(panelOpenIn);
460         menuListen.add(new JSeparator());
461 
462         var menuBuiltInShell = new RadioItemNonClosing("Built-in shell", true);
463         var menuExternalShell = new RadioItemNonClosing("External listening shell");
464         var buttonTypeShell = new ButtonGroup();
465         buttonTypeShell.add(menuBuiltInShell);
466         buttonTypeShell.add(menuExternalShell);
467         menuListen.add(menuBuiltInShell);
468         menuListen.add(menuExternalShell);
469         menuListen.add(new JSeparator());
470         var panelCreate = new JPanel(new BorderLayout());
471         panelCreate.add(new JButton(new AbstractAction("Create reverse shell") {
472             @Override
473             public void actionPerformed(ActionEvent e) {
474                 if (menuBuiltInShell.isSelected()) {
475                     MediatorHelper.tabResults().addTabExploitReverseShell(port.getText());
476                 }
477                 new Thread(runnableReverse).start();
478                 menuReverse.setVisible(false);
479             }
480         }));
481         menuListen.add(panelCreate);
482 
483         var menuConnect = new JMenu("Connect");
484         menuConnect.setComponentOrientation(
485             ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
486             ? ComponentOrientation.LEFT_TO_RIGHT
487             : ComponentOrientation.RIGHT_TO_LEFT
488         );
489         var panelServerPublicAddress = new JPanel(new BorderLayout());
490         panelServerPublicAddress.add(new JLabel("<html><b>Server public address (listener) :</b></html>"));
491         menuConnect.add(panelServerPublicAddress);
492         menuConnect.add(new JSeparator());
493         menuConnect.add(new JTextFieldPlaceholder("Target IP/domain"));
494         menuConnect.add(new JTextFieldPlaceholder("Target port"));
495         menuConnect.add(new JSeparator());
496 
497         var panelServerListeningConnection = new JPanel(new BorderLayout());
498         panelServerListeningConnection.add(new JLabel("<html><b>Server listening method :</b></html>"));
499         menuConnect.add(panelServerListeningConnection);
500         var buttonGroupListening = new ButtonGroup();
501         List.of("netcat").forEach(method -> {
502             var radio = new JRadioButtonMenuItem(method) {
503                 @Override
504                 protected void processMouseEvent(MouseEvent evt) {
505                     if (evt.getID() == MouseEvent.MOUSE_RELEASED && this.contains(evt.getPoint())) {
506                         this.doClick();
507                         this.setArmed(true);
508                     } else {
509                         super.processMouseEvent(evt);
510                     }
511                 }
512             };
513             radio.setSelected("netcat".equals(method));
514             buttonGroupListening.add(radio);
515             menuConnect.add(radio);
516         });
517         menuConnect.add(new JSeparator());
518         menuConnect.add(new JMenuItem("Create"));
519 
520         menuReverse.add(menuListen);
521         menuReverse.add(menuConnect);
522         return menuReverse;
523     }
524 
525     private static class BrowserMouseAdapter extends MouseAdapter {
526         private final JTextPane browser;
527         private final JPopupMenu menu;
528 
529         public BrowserMouseAdapter(JTextPane browser, JPopupMenu menu) {
530             this.browser = browser;
531             this.menu = menu;
532         }
533 
534         @Override
535         public void mousePressed(MouseEvent evt) {
536             this.browser.requestFocusInWindow();
537             if (evt.isPopupTrigger()) {
538                 this.menu.show(evt.getComponent(), evt.getX(), evt.getY());
539             }
540         }
541 
542         @Override
543         public void mouseReleased(MouseEvent evt) {
544             if (evt.isPopupTrigger()) {
545                 Arrays.stream(this.menu.getComponents()).map(a -> (JComponent) a).forEach(JComponent::updateUI);  // required: incorrect when dark/light mode switch
546                 this.menu.updateUI();  // required: incorrect when dark/light mode switch
547                 // Fix #45348: IllegalComponentStateException on show()
548                 try {
549                     this.menu.show(evt.getComponent(), evt.getX(), evt.getY());
550                 } catch (IllegalComponentStateException e) {
551                     LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
552                 }
553                 this.menu.setLocation(
554                     ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
555                     ? evt.getXOnScreen() - this.menu.getWidth()
556                     : evt.getXOnScreen(),
557                     evt.getYOnScreen()
558                 );
559             }
560         }
561     }
562 }