View Javadoc
1   package com.jsql.util;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.bean.util.Header;
5   import com.jsql.model.bean.util.Interaction;
6   import com.jsql.model.bean.util.Request;
7   import com.jsql.model.exception.InjectionFailureException;
8   import com.jsql.model.exception.JSqlException;
9   import com.jsql.model.injection.method.AbstractMethodInjection;
10  import org.apache.commons.lang3.StringUtils;
11  import org.apache.logging.log4j.LogManager;
12  import org.apache.logging.log4j.Logger;
13  
14  import java.io.IOException;
15  import java.net.Authenticator;
16  import java.net.CookieManager;
17  import java.net.PasswordAuthentication;
18  import java.net.URI;
19  import java.net.http.HttpClient;
20  import java.net.http.HttpClient.Version;
21  import java.net.http.HttpHeaders;
22  import java.net.http.HttpRequest;
23  import java.net.http.HttpRequest.BodyPublishers;
24  import java.net.http.HttpRequest.Builder;
25  import java.net.http.HttpResponse;
26  import java.net.http.HttpResponse.BodyHandlers;
27  import java.time.Duration;
28  import java.util.*;
29  import java.util.AbstractMap.SimpleEntry;
30  import java.util.Map.Entry;
31  import java.util.stream.Collectors;
32  import java.util.stream.Stream;
33  
34  /**
35   * Utility class in charge of connection to web resources and management
36   * of source page and request and response headers.
37   */
38  public class ConnectionUtil {
39      
40      /**
41       * Log4j logger sent to view.
42       */
43      private static final Logger LOGGER = LogManager.getRootLogger();
44      
45      /**
46       * URL entered by user
47       */
48      private String urlByUser;
49  
50      /**
51       * URL entered by user without the query string
52       */
53      private String urlBase;
54  
55      /**
56       * Method of injection: by query string, request or header.
57       */
58      private AbstractMethodInjection methodInjection;
59  
60      /**
61       * Default HTTP method. It can be changed to a custom method.
62       */
63      private String typeRequest = StringUtil.GET;
64  
65      private final Random randomForUserAgent = new Random();
66      
67      private final InjectionModel injectionModel;
68      
69      private final CookieManager cookieManager = new CookieManager();
70      
71      public ConnectionUtil(InjectionModel injectionModel) {
72          this.injectionModel = injectionModel;
73      }
74      
75      public HttpClient.Builder getHttpClient() {
76          var httpClientBuilder = HttpClient.newBuilder()
77              .connectTimeout(Duration.ofSeconds(this.getTimeout()))
78              .sslContext(this.injectionModel.getMediatorUtils().getCertificateUtil().getSslContext())
79              .followRedirects(
80                  this.injectionModel.getMediatorUtils().getPreferencesUtil().isFollowingRedirection()
81                  ? HttpClient.Redirect.ALWAYS
82                  : HttpClient.Redirect.NEVER
83              );
84          if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isHttp2Disabled()) {
85              httpClientBuilder.version(Version.HTTP_1_1);
86          }
87          if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotProcessingCookies()) {
88              httpClientBuilder.cookieHandler(this.cookieManager);
89          }
90          if (this.injectionModel.getMediatorUtils().getAuthenticationUtil().isAuthentEnabled()) {
91              httpClientBuilder.authenticator(new Authenticator() {
92                  @Override
93                  protected PasswordAuthentication getPasswordAuthentication() {
94                      return new PasswordAuthentication(
95                          ConnectionUtil.this.injectionModel.getMediatorUtils().getAuthenticationUtil().getUsernameAuthentication(),
96                          ConnectionUtil.this.injectionModel.getMediatorUtils().getAuthenticationUtil().getPasswordAuthentication().toCharArray()
97                      );
98                  }
99              });
100         }
101         return httpClientBuilder;
102     }
103 
104     public static <T> Map<String, String> getHeadersMap(HttpResponse<T> httpResponse) {
105         Map<String, String> sortedMap = ConnectionUtil.getHeadersMap(httpResponse.headers());
106         String responseCodeHttp = String.valueOf(httpResponse.statusCode());
107         sortedMap.put(":status", responseCodeHttp);
108         return sortedMap;
109     }
110     
111     public static Map<String, String> getHeadersMap(HttpHeaders httpHeaders) {
112         Map<String, String> unsortedMap = httpHeaders.map()
113             .entrySet()
114             .stream()
115             .sorted(Entry.comparingByKey())
116             .map(entrySet -> new AbstractMap.SimpleEntry<>(
117                 entrySet.getKey(),
118                 String.join(", ", entrySet.getValue())
119             ))
120             .collect(Collectors.toMap(
121                 AbstractMap.SimpleEntry::getKey,
122                 AbstractMap.SimpleEntry::getValue
123             ));
124         return new TreeMap<>(unsortedMap);
125     }
126 
127     /**
128      * Check that the connection to the website is working correctly.
129      * It uses authentication defined by user, with fixed timeout, and warn
130      * user in case of authentication detected.
131      */
132     public HttpResponse<String> checkConnectionResponse() throws IOException, InterruptedException, JSqlException {
133         var queryString = this.injectionModel.getMediatorUtils().getParameterUtil().getQueryStringFromEntries();
134         var testUrl = this.getUrlBase().replaceAll("\\?$", StringUtils.EMPTY);
135 
136         if (StringUtils.isNotEmpty(queryString)) {
137             testUrl += "?"+ queryString;
138         }
139 
140         String contentTypeRequest = "text/plain";
141 
142         var body = this.injectionModel.getMediatorUtils().getParameterUtil().getRawRequest();
143 
144         if (this.injectionModel.getMediatorUtils().getParameterUtil().isMultipartRequest()) {
145             body = body.replaceAll("(?s)\\\\n", "\r\n");
146         } else if (this.injectionModel.getMediatorUtils().getParameterUtil().isRequestSoap()) {
147             contentTypeRequest = "text/xml";
148         } else if (!this.injectionModel.getMediatorUtils().getParameterUtil().getListRequest().isEmpty()) {
149             contentTypeRequest = "application/x-www-form-urlencoded";
150         }
151 
152         // Test the HTTP connection
153         Builder httpRequest = HttpRequest.newBuilder();
154         try {
155             httpRequest.uri(
156                 URI.create(
157                     testUrl  // Get encoded params without fragment
158                     .replace(InjectionModel.STAR, StringUtils.EMPTY)  // Ignore injection point during the test
159                 )
160             );
161         } catch (IllegalArgumentException e) {
162             throw new JSqlException(e);
163         }
164         httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, contentTypeRequest)
165             .timeout(Duration.ofSeconds(this.getTimeout()));
166         
167         this.injectionModel.getMediatorUtils().getCsrfUtil().addHeaderToken(httpRequest);
168         this.injectionModel.getMediatorUtils().getDigestUtil().addHeaderToken(httpRequest);
169 
170         httpRequest.method(this.typeRequest, BodyPublishers.ofString(body));
171 
172         // Add headers if exists (Authorization:Basic, etc.)
173         for (SimpleEntry<String, String> header: this.injectionModel.getMediatorUtils().getParameterUtil().getListHeader()) {
174             HeaderUtil.sanitizeHeaders(httpRequest, header);
175         }
176 
177         return this.injectionModel.getMediatorUtils().getHeaderUtil().checkResponseHeader(httpRequest, body);
178     }
179 
180     public void testConnection() throws IOException, InterruptedException, JSqlException {
181         // Check connection is working: define Cookie management, check HTTP status, parse <form> parameters, process CSRF
182         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, () -> I18nUtil.valueByKey("LOG_CONNECTION_TEST"));
183         this.getCookieManager().getCookieStore().removeAll();
184         HttpResponse<String> httpResponse = this.checkConnectionResponse();
185 
186         if (
187             (httpResponse.statusCode() == 401 || httpResponse.statusCode() == 403)
188             && !this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotProcessingCookies()
189             && (
190                 this.injectionModel.getMediatorUtils().getCsrfUtil().isCsrf()
191                 || this.injectionModel.getMediatorUtils().getDigestUtil().isDigest()
192             )
193         ) {
194             if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isProcessingCsrf()) {
195                 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, () -> "Testing CSRF handshake from previous connection...");
196             } else if (StringUtils.isNotEmpty(this.injectionModel.getMediatorUtils().getDigestUtil().getTokenDigest())) {
197                 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, () -> "Testing Digest handshake from previous connection...");
198             }
199             httpResponse = this.checkConnectionResponse();
200         }
201 
202         if (httpResponse.statusCode() >= 400 && !this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotTestingConnection()) {
203             throw new InjectionFailureException(String.format("Connection failed: problem when calling %s", httpResponse.uri().toURL()));
204         }
205     }
206 
207     public String getSource(String url, boolean lineFeed) {
208         Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
209         msgHeader.put(Header.URL, url);
210         
211         String pageSource = StringUtils.EMPTY;
212         
213         try {
214             var httpRequest = HttpRequest.newBuilder()
215                 .uri(URI.create(url))
216                 .timeout(Duration.ofSeconds(this.getTimeout()))
217                 .build();
218             
219             HttpHeaders httpHeaders;
220             if (lineFeed) {
221                 HttpResponse<Stream<String>> response = this.getHttpClient().build().send(httpRequest, BodyHandlers.ofLines());
222                 pageSource = response.body().collect(Collectors.joining("\n"));
223                 httpHeaders = response.headers();
224             } else {
225                 HttpResponse<String> response = this.getHttpClient().build().send(httpRequest, BodyHandlers.ofString());
226                 pageSource = response.body();
227                 httpHeaders = response.headers();
228             }
229             
230             msgHeader.put(Header.RESPONSE, ConnectionUtil.getHeadersMap(httpHeaders));
231             msgHeader.put(Header.HEADER, ConnectionUtil.getHeadersMap(httpRequest.headers()));
232             
233         } catch (IOException e) {
234             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
235         } catch (InterruptedException e) {
236             LOGGER.log(LogLevelUtil.IGNORE, e, e);
237             Thread.currentThread().interrupt();
238         } finally {
239             msgHeader.put(Header.SOURCE, pageSource);
240             // Inform the view about the log infos
241             var request = new Request();
242             request.setMessage(Interaction.MESSAGE_HEADER);
243             request.setParameters(msgHeader);
244             this.injectionModel.sendToViews(request);
245         }
246         
247         return pageSource.trim();
248     }
249     
250     /**
251      * Call a URL and return the source page.
252      * @param url to call
253      * @return the source page of the URL
254      */
255     public String getSourceLineFeed(String url) {
256         return this.getSource(url, true);
257     }
258     
259     public String getSource(String url) {
260         return this.getSource(url, false);
261     }
262 
263     public void setCustomUserAgent(Builder httpRequest) {
264         if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isUserAgentRandom()) {
265             String agents = this.injectionModel.getMediatorUtils().getUserAgentUtil().getCustomUserAgent();
266             List<String> listAgents = Stream.of(agents.split("[\\r\\n]+"))
267                 .filter(q -> !q.matches("^#.*"))
268                 .collect(Collectors.toList());
269             String randomElement = listAgents.get(this.randomForUserAgent.nextInt(listAgents.size()));
270             httpRequest.setHeader("User-Agent", randomElement);
271         }
272     }
273     
274     
275     // Builder
276 
277     public ConnectionUtil withMethodInjection(AbstractMethodInjection methodInjection) {
278         this.methodInjection = methodInjection;
279         return this;
280     }
281     
282     public ConnectionUtil withTypeRequest(String typeRequest) {
283         this.typeRequest = typeRequest;
284         return this;
285     }
286     
287     
288     // Getters and setters
289     
290     public String getUrlByUser() {
291         return this.urlByUser;
292     }
293 
294     public void setUrlByUser(String urlByUser) {
295         this.urlByUser = urlByUser;
296     }
297     
298     public String getUrlBase() {
299         return this.urlBase;
300     }
301 
302     public void setUrlBase(String urlBase) {
303         this.urlBase = urlBase;
304     }
305     
306     public AbstractMethodInjection getMethodInjection() {
307         return this.methodInjection;
308     }
309 
310     public void setMethodInjection(AbstractMethodInjection methodInjection) {
311         this.methodInjection = methodInjection;
312     }
313     
314     public String getTypeRequest() {
315         return this.typeRequest;
316     }
317 
318     public void setTypeRequest(String typeRequest) {
319         this.typeRequest = typeRequest;
320     }
321 
322     /**
323      * Default timeout used by the jcifs fix. It's the default value used usually by the JVM.
324      */
325     public Integer getTimeout() {
326         return this.injectionModel.getMediatorUtils().getPreferencesUtil().countConnectionTimeout();
327     }
328 
329     public CookieManager getCookieManager() {
330         return this.cookieManager;
331     }
332 }