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      /**
56       * Log4j logger sent to view.
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 RCE_SHELL = "RCE shell";
62      public static final String SQL_SHELL = "sqlShell";
63      public static final String WEB_SHELL = "webShell";
64      public static final String REV_SHELL = "revShell";
65      public static final String REVERSE_SHELL = "Reverse shell";
66  
67      /**
68       * Create the panel containing injection results.
69       */
70      public TabResults() {
71          this.setName("tabResults");
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          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      public void addReportTab(String content) {
92          JEditorPane editorPane = new JEditorPane();
93          editorPane.setContentType("text/html");
94          editorPane.setText("<html><span style=\"white-space: nowrap; font-family:'"+ UiUtil.FONT_NAME_MONO_NON_ASIAN +"'\">" + content + "</span></html>");
95          editorPane.setFont(UIManager.getFont("TextArea.font"));  // required to increase text size
96          editorPane.setDragEnabled(true);
97          editorPane.setEditable(false);
98          editorPane.setCaretPosition(0);
99          editorPane.getCaret().setBlinkRate(0);
100         editorPane.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
101         editorPane.setComponentPopupMenu(new JPopupMenuText(editorPane));
102         editorPane.addHyperlinkListener(linkEvent -> {
103             if (HyperlinkEvent.EventType.ACTIVATED.equals(linkEvent.getEventType())) {
104                 try {
105                     Desktop.getDesktop().browse(linkEvent.getURL().toURI());
106                 } catch (IOException | URISyntaxException | UnsupportedOperationException e) {
107                     LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Failing to browse Url", e);
108                 }
109             }
110         });
111         editorPane.addFocusListener(new FocusAdapter() {
112             @Override
113             public void focusGained(FocusEvent focusEvent) {
114                 editorPane.getCaret().setVisible(true);
115                 editorPane.getCaret().setSelectionVisible(true);
116                 editorPane.getCaret().setBlinkRate(0);
117             }
118         });
119         UiUtil.init(editorPane);  // silent delete
120 
121         this.addTextTab("Vulnerability report", "Analysis report with all payloads detected", editorPane, UiUtil.APP_ICON.getIcon());
122     }
123 
124     public void addTextTab(String label, String toolTipText, JComponent componentText, FlatSVGIcon icon) {
125         var scroller = new JScrollPane(componentText);
126         this.addTab(label + StringUtils.SPACE, scroller);
127         this.setSelectedComponent(scroller);  // Focus on the new tab
128         this.setToolTipTextAt(this.indexOfComponent(scroller), toolTipText);
129         var header = new TabHeader(label, icon);
130         this.setTabComponentAt(this.indexOfComponent(scroller), header);
131 
132         this.updateUI();  // required: light, open/close prefs, dark => light artifacts
133     }
134 
135     public void addTabExploitWeb(String url) {
136         try {
137             var terminalID = UUID.randomUUID();
138             var terminal = new AbstractExploit(terminalID, url, "web") {
139                 @Override
140                 public void action(String command, UUID terminalID, String urlShell, String... arg) {
141                     MediatorHelper.model().getResourceAccess().runWebShell(command, terminalID, urlShell);
142                 }
143             };
144             terminal.setName(TabResults.WEB_SHELL);
145             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
146 
147             JPanel panelTerminalWithReverse = this.getTerminalWithMenu(terminal);
148             this.addTab("Web shell", panelTerminalWithReverse);
149             this.setSelectedComponent(panelTerminalWithReverse);  // Focus on the new tab
150 
151             var header = new TabHeader("Web shell", UiUtil.TERMINAL.getIcon());
152             this.setTabComponentAt(this.indexOfComponent(panelTerminalWithReverse), header);
153             terminal.requestFocusInWindow();
154 
155             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
156         } catch (MalformedURLException | URISyntaxException e) {
157             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
158         }
159     }
160 
161     public void addTabExploitReverseShell(String port) {
162         try {
163             var terminalID = UUID.randomUUID();
164             var terminal = new ExploitReverseShell(terminalID, port);
165             terminal.setName(TabResults.REV_SHELL);
166             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
167 
168             JScrollPane scroller = new JScrollPane(terminal);
169             this.addTab(TabResults.REVERSE_SHELL, scroller);
170             this.setSelectedComponent(scroller);  // Focus on the new tab
171 
172             var header = new TabHeader(TabResults.REVERSE_SHELL, UiUtil.TERMINAL.getIcon());
173             this.setTabComponentAt(this.indexOfComponent(scroller), header);
174             terminal.requestFocusInWindow();
175 
176             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
177         } catch (URISyntaxException | IOException e) {
178             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
179         }
180     }
181 
182     public void addTabExploitRce(BiConsumer<String, UUID> biConsumerRunCmd) {
183         try {
184             var terminalID = UUID.randomUUID();
185             var terminal = new AbstractExploit(terminalID, null, "rce") {
186                 @Override
187                 public void action(String command, UUID terminalID, String urlShell, String... arg) {
188                     biConsumerRunCmd.accept(command, terminalID);
189                 }
190             };
191             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
192 
193             JPanel panelTerminalWithReverse = this.getTerminalWithMenu(terminal);
194             this.addTab(TabResults.RCE_SHELL, panelTerminalWithReverse);
195             this.setSelectedComponent(panelTerminalWithReverse);  // Focus on the new tab
196 
197             var header = new TabHeader(TabResults.RCE_SHELL, UiUtil.TERMINAL.getIcon());
198             this.setTabComponentAt(this.indexOfComponent(panelTerminalWithReverse), header);
199             terminal.requestFocusInWindow();
200 
201             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
202         } catch (MalformedURLException | URISyntaxException e) {
203             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
204         }
205     }
206 
207     public void addTabExploitSql(String url, String user, String pass) {
208         try {
209             var terminalID = UUID.randomUUID();
210             var terminal = new AbstractExploit(terminalID, url, "sql") {
211                 @Override
212                 public void action(String cmd, UUID terminalID, String wbhPath, String... arg) {
213                     MediatorHelper.model().getResourceAccess().runSqlShell(cmd, terminalID, wbhPath, arg[0], arg[1]);
214                 }
215             };
216             terminal.setName(TabResults.SQL_SHELL);
217             terminal.setLoginPassword(new String[]{ user, pass });
218             MediatorHelper.frame().getMapUuidShell().put(terminalID, terminal);
219 
220             JScrollPane scroller = new JScrollPane(terminal);
221             this.addTab("SQL shell", scroller);
222             this.setSelectedComponent(scroller);  // Focus on the new tab
223 
224             var header = new TabHeader("SQL shell", UiUtil.TERMINAL.getIcon());
225             this.setTabComponentAt(this.indexOfComponent(scroller), header);
226             terminal.requestFocusInWindow();
227 
228             this.updateUI();  // required: light, open/close prefs, dark => light artifacts
229         } catch (MalformedURLException | URISyntaxException e) {
230             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, TabResults.TAB_EXPLOIT_FAILURE_INCORRECT_URL, e);
231         }
232     }
233     
234     public void addTabValues(String[][] data, String[] columnNames, AbstractElementDatabase table) {
235         var panelTable = new PanelTable(data, columnNames);
236         
237         this.addTab(StringUtil.detectUtf8(table.toString()), panelTable);
238         panelTable.setComponentOrientation(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()));
239         
240         this.setSelectedComponent(panelTable);  // Focus on the new tab
241 
242         var header = new TabHeader(UiStringUtil.detectUtf8Html(table.toString()), UiUtil.TABLE_BOLD.getIcon());
243         this.setTabComponentAt(this.indexOfComponent(panelTable), header);
244 
245         this.updateUI();  // required: light, open/close prefs, dark => light artifacts
246     }
247 
248     private JPanel getTerminalWithMenu(AbstractExploit terminal) {
249         JPanel panelTerminalWithReverse = new JPanel() {
250             @Override
251             public boolean isOptimizedDrawingEnabled() {
252                 return false;  // both components always visible
253             }
254         };
255         OverlayLayout overlay = new OverlayLayout(panelTerminalWithReverse);
256         panelTerminalWithReverse.setLayout(overlay);
257 
258         var panelReverseMargin = new JPanel();
259         panelReverseMargin.setLayout(new BoxLayout(panelReverseMargin, BoxLayout.LINE_AXIS));
260         panelReverseMargin.setOpaque(false);
261         panelReverseMargin.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 15));
262 
263         var menuReverse = new JLabel(TabResults.REVERSE_SHELL, UiUtil.ARROW_DOWN.getIcon(), SwingConstants.LEFT);
264         menuReverse.addMouseListener(new MouseAdapter() {
265             @Override
266             public void mousePressed(MouseEvent e) {
267                 var popupMenu = TabResults.this.showMenu(terminal);
268                 popupMenu.updateUI();  // required: incorrect when dark/light mode switch
269                 popupMenu.show(e.getComponent(), e.getComponent().getX(),5 + e.getComponent().getY() + e.getComponent().getHeight());
270                 popupMenu.setLocation(e.getComponent().getLocationOnScreen().x,5 + e.getComponent().getLocationOnScreen().y + e.getComponent().getHeight());
271             }
272         });
273         menuReverse.setMaximumSize(menuReverse.getPreferredSize());
274         JScrollPane scrollerTerminal = new JScrollPane(terminal);
275         scrollerTerminal.setAlignmentX(1f);
276         scrollerTerminal.setAlignmentY(0f);
277         panelReverseMargin.setAlignmentX(1f);
278         panelReverseMargin.setAlignmentY(0f);
279         panelReverseMargin.add(menuReverse);
280         panelTerminalWithReverse.add(panelReverseMargin);
281         panelTerminalWithReverse.add(scrollerTerminal);
282 
283         return panelTerminalWithReverse;
284     }
285 
286     private JPopupMenu showMenu(AbstractExploit terminal) {
287         JPopupMenu menuReverse = new JPopupMenu();
288 
289         var menuListen = new JMenu("Listen");
290         menuListen.setComponentOrientation(
291             ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
292             ? ComponentOrientation.LEFT_TO_RIGHT
293             : ComponentOrientation.RIGHT_TO_LEFT
294         );
295         var panelPublicAddress = new JPanel(new BorderLayout());
296         panelPublicAddress.add(new JLabel("<html><b>Your public address (listener) :</b></html>"));
297         menuListen.add(panelPublicAddress);
298         menuListen.add(new JSeparator());
299         var address = new JTextFieldPlaceholder("Local IP/domain", "10.0.2.2");
300         menuListen.add(address);
301         var port = new JTextFieldPlaceholder("Local port", "4444");
302         menuListen.add(port);
303 
304         var panelServerConnection = new JPanel(new BorderLayout());
305         panelServerConnection.add(new JLabel("<html><b>Server method (connector) :</b></html>"));
306         menuListen.add(panelServerConnection);
307         menuListen.add(new JSeparator());
308         var buttonGroup = new ButtonGroup();
309         List<ModelReverse> commandsReverse = MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getCommandsReverse();
310         commandsReverse.forEach(modelReverse -> {
311             var radio = new RadioItemPreventClose(modelReverse.getName());
312             radio.setActionCommand(modelReverse.getName());
313             radio.setSelected("bash".equals(modelReverse.getName()));
314             buttonGroup.add(radio);
315             menuListen.add(radio);
316         });
317 
318         Runnable runnableReverse = () -> {
319             try {
320                 Thread.sleep(2500);
321                 MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getCommandsReverse().stream()
322                 .filter(modelReverse -> modelReverse.getName().equals(buttonGroup.getSelection().getActionCommand()))
323                 .findFirst()
324                 .ifPresent(modelReverse -> MediatorHelper.model().getResourceAccess().runWebShell(
325                     // TODO mysql UDF, pg Program/Extension/Archive, sqlite
326                     String.format(modelReverse.getCommand(), address.getText(), port.getText()),
327                     null,  // ignore connection response
328                     terminal.getUrlShell(),
329                     true
330                 ));
331             } catch (InterruptedException e) {
332                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
333                 Thread.currentThread().interrupt();
334             }
335         };
336 
337         var panelOpenIn = new JPanel(new BorderLayout());
338         panelOpenIn.add(new JLabel("<html><b>Open In :</b></html>"));
339         menuListen.add(panelOpenIn);
340         menuListen.add(new JSeparator());
341 
342         var menuBuiltInShell = new RadioItemPreventClose("Built-in shell", true);
343         var menuExternalShell = new RadioItemPreventClose("External listening shell");
344         var buttonTypeShell = new ButtonGroup();
345         buttonTypeShell.add(menuBuiltInShell);
346         buttonTypeShell.add(menuExternalShell);
347         menuListen.add(menuBuiltInShell);
348         menuListen.add(menuExternalShell);
349         menuListen.add(new JSeparator());
350         var panelCreate = new JPanel(new BorderLayout());
351         panelCreate.add(new JButton(new AbstractAction("Create reverse shell") {
352             @Override
353             public void actionPerformed(ActionEvent e) {
354                 if (menuBuiltInShell.isSelected()) {
355                     SwingUtilities.invokeLater(() -> MediatorHelper.tabResults().addTabExploitReverseShell(port.getText()));
356                 }
357                 new Thread(runnableReverse).start();
358                 menuReverse.setVisible(false);
359             }
360         }));
361         menuListen.add(panelCreate);
362 
363         var menuConnect = new JMenu("Connect");
364         menuConnect.setComponentOrientation(
365             ComponentOrientation.RIGHT_TO_LEFT.equals(ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()))
366             ? ComponentOrientation.LEFT_TO_RIGHT
367             : ComponentOrientation.RIGHT_TO_LEFT
368         );
369         var panelServerPublicAddress = new JPanel(new BorderLayout());
370         panelServerPublicAddress.add(new JLabel("<html><b>Server public address (listener) :</b></html>"));
371         menuConnect.add(panelServerPublicAddress);
372         menuConnect.add(new JSeparator());
373         menuConnect.add(new JTextFieldPlaceholder("Target IP/domain"));
374         menuConnect.add(new JTextFieldPlaceholder("Target port"));
375         menuConnect.add(new JSeparator());
376 
377         var panelServerListeningConnection = new JPanel(new BorderLayout());
378         panelServerListeningConnection.add(new JLabel("<html><b>Server listening method :</b></html>"));
379         menuConnect.add(panelServerListeningConnection);
380         var buttonGroupListening = new ButtonGroup();
381         Arrays.asList("netcat").forEach(method -> {
382             var radio = new JRadioButtonMenuItem(method) {
383                 @Override
384                 protected void processMouseEvent(MouseEvent evt) {
385                     if (evt.getID() == MouseEvent.MOUSE_RELEASED && this.contains(evt.getPoint())) {
386                         this.doClick();
387                         this.setArmed(true);
388                     } else {
389                         super.processMouseEvent(evt);
390                     }
391                 }
392             };
393             radio.setSelected("netcat".equals(method));
394             buttonGroupListening.add(radio);
395             menuConnect.add(radio);
396         });
397         menuConnect.add(new JSeparator());
398         menuConnect.add(new JMenuItem("Create"));
399 
400         menuReverse.add(menuListen);
401         menuReverse.add(menuConnect);
402 
403         return menuReverse;
404     }
405 }