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