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 = "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 getHttpClient() {
76          
77          var httpClientBuilder = HttpClient.newBuilder()
78              .connectTimeout(Duration.ofSeconds(this.getTimeout()))
79              .sslContext(this.injectionModel.getMediatorUtils().getCertificateUtil().getSslContext())
80              .followRedirects(
81                  this.injectionModel.getMediatorUtils().getPreferencesUtil().isFollowingRedirection()
82                  ? HttpClient.Redirect.ALWAYS
83                  : HttpClient.Redirect.NEVER
84              );
85          
86          if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isHttp2Disabled()) {
87              httpClientBuilder.version(Version.HTTP_1_1);
88          }
89          
90          if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotProcessingCookies()) {
91              httpClientBuilder.cookieHandler(this.cookieManager);
92          }
93          
94          if (this.injectionModel.getMediatorUtils().getAuthenticationUtil().isAuthentEnabled()) {
95              httpClientBuilder.authenticator(new Authenticator() {
96                @Override
97                protected PasswordAuthentication getPasswordAuthentication() {
98                    return new PasswordAuthentication(
99                        ConnectionUtil.this.injectionModel.getMediatorUtils().getAuthenticationUtil().getUsernameAuthentication(),
100                       ConnectionUtil.this.injectionModel.getMediatorUtils().getAuthenticationUtil().getPasswordAuthentication().toCharArray()
101                   );
102               }
103           });
104       }
105                 
106         return httpClientBuilder.build();
107     }
108 
109 
110     public static <T> Map<String, String> getHeadersMap(HttpResponse<T> httpResponse) {
111         
112         Map<String, String> sortedMap = getHeadersMap(httpResponse.headers());
113         
114         String responseCodeHttp = ""+ httpResponse.statusCode();
115         sortedMap.put(":status", responseCodeHttp);
116         
117         return sortedMap;
118     }
119     
120     public static Map<String, String> getHeadersMap(HttpHeaders httpHeaders) {
121         
122         Map<String, String> unsortedMap = httpHeaders.map()
123             .entrySet()
124             .stream()
125             .sorted(Entry.comparingByKey())
126             .map(entrySet -> new AbstractMap.SimpleEntry<>(
127                 entrySet.getKey(),
128                 String.join(", ", entrySet.getValue())
129             ))
130             .collect(Collectors.toMap(
131                 AbstractMap.SimpleEntry::getKey,
132                 AbstractMap.SimpleEntry::getValue
133             ));
134         
135         return new TreeMap<>(unsortedMap);
136     }
137 
138     /**
139      * Check that the connection to the website is working correctly.
140      * It uses authentication defined by user, with fixed timeout, and warn
141      * user in case of authentication detected.
142      * @return
143      * @throws IOException
144      * @throws InterruptedException
145      */
146     public HttpResponse<String> checkConnectionResponse() throws IOException, InterruptedException, JSqlException {
147 
148         var queryString = this.injectionModel.getMediatorUtils().getParameterUtil().getQueryStringFromEntries();
149         var testUrl = this.getUrlBase().replaceAll("\\?$", "");
150 
151         if (StringUtils.isNotEmpty(queryString)) {
152             testUrl += "?"+ queryString;
153         }
154 
155         String contentTypeRequest = "text/plain";
156 
157         var body = this.injectionModel.getMediatorUtils().getParameterUtil().getRawRequest();
158 
159         if (this.injectionModel.getMediatorUtils().getParameterUtil().isMultipartRequest()) {
160             body = body.replaceAll("(?s)\\\\n", "\r\n");
161         } else if (this.injectionModel.getMediatorUtils().getParameterUtil().isRequestSoap()) {
162             contentTypeRequest = "text/xml";
163         } else if (!this.injectionModel.getMediatorUtils().getParameterUtil().getListRequest().isEmpty()) {
164             contentTypeRequest = "application/x-www-form-urlencoded";
165         }
166 
167         // Test the HTTP connection
168         Builder httpRequest = HttpRequest.newBuilder();
169         try {
170             httpRequest.uri(
171                 URI.create(
172                     // Get encoded params without fragment
173                     testUrl
174                     // Ignore injection point during the test
175                     .replace(InjectionModel.STAR, StringUtils.EMPTY)
176                 )
177             );
178         } catch (IllegalArgumentException e) {
179             throw new JSqlException(e);
180         }
181         httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, contentTypeRequest)
182             .timeout(Duration.ofSeconds(this.getTimeout()));
183         
184         this.injectionModel.getMediatorUtils().getCsrfUtil().addHeaderToken(httpRequest);
185         this.injectionModel.getMediatorUtils().getDigestUtil().addHeaderToken(httpRequest);
186 
187         httpRequest.method(
188             this.typeRequest,
189             BodyPublishers.ofString(body)
190         );
191 
192         // Add headers if exists (Authorization:Basic, etc)
193         for (SimpleEntry<String, String> header: this.injectionModel.getMediatorUtils().getParameterUtil().getListHeader()) {
194             HeaderUtil.sanitizeHeaders(httpRequest, header);
195         }
196 
197         return this.injectionModel.getMediatorUtils().getHeaderUtil().checkResponseHeader(httpRequest, body);
198     }
199 
200     public void testConnection() throws IOException, InterruptedException, JSqlException {
201 
202         // Check connection is working: define Cookie management, check HTTP status, parse <form> parameters, process CSRF
203         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, () -> I18nUtil.valueByKey("LOG_CONNECTION_TEST"));
204         this.getCookieManager().getCookieStore().removeAll();
205         HttpResponse<String> httpResponse = this.checkConnectionResponse();
206 
207         if (
208             (httpResponse.statusCode() == 401 || httpResponse.statusCode() == 403)
209             && !this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotProcessingCookies()
210             && (
211                 this.injectionModel.getMediatorUtils().getCsrfUtil().isCsrf()
212                 || this.injectionModel.getMediatorUtils().getDigestUtil().isDigest()
213             )
214         ) {
215             if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isProcessingCsrf()) {
216                 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, () -> "Testing CSRF handshake from previous connection...");
217             } else if (StringUtils.isNotEmpty(this.injectionModel.getMediatorUtils().getDigestUtil().getTokenDigest())) {
218                 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, () -> "Testing Digest handshake from previous connection...");
219             }
220             httpResponse = this.checkConnectionResponse();
221         }
222 
223         if (httpResponse.statusCode() >= 400 && !this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotTestingConnection()) {
224             throw new InjectionFailureException(String.format("Connection failed: problem when calling %s", httpResponse.uri().toURL()));
225         }
226     }
227 
228     public String getSource(String url, boolean lineFeed) {
229         
230         Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
231         msgHeader.put(Header.URL, url);
232         
233         String pageSource = StringUtils.EMPTY;
234         
235         try {
236             var httpRequest = HttpRequest.newBuilder()
237                 .uri(URI.create(url))
238                 .timeout(Duration.ofSeconds(this.getTimeout()))
239                 .build();
240             
241             HttpHeaders httpHeaders;
242                 
243             if (lineFeed) {
244                 
245                 HttpResponse<Stream<String>> response = this.getHttpClient().send(httpRequest, BodyHandlers.ofLines());
246                 pageSource = response.body().collect(Collectors.joining("\n"));
247                 httpHeaders = response.headers();
248                 
249             } else {
250                 
251                 HttpResponse<String> response = this.getHttpClient().send(httpRequest, BodyHandlers.ofString());
252                 pageSource = response.body();
253                 httpHeaders = response.headers();
254             }
255             
256             msgHeader.put(Header.RESPONSE, ConnectionUtil.getHeadersMap(httpHeaders));
257             msgHeader.put(Header.HEADER, ConnectionUtil.getHeadersMap(httpRequest.headers()));
258             
259         } catch (IOException e) {
260             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
261         } catch (InterruptedException e) {
262             
263             LOGGER.log(LogLevelUtil.IGNORE, e, e);
264             Thread.currentThread().interrupt();
265             
266         } finally {
267             
268             msgHeader.put(Header.SOURCE, pageSource);
269             
270             // Inform the view about the log infos
271             var request = new Request();
272             request.setMessage(Interaction.MESSAGE_HEADER);
273             request.setParameters(msgHeader);
274             this.injectionModel.sendToViews(request);
275         }
276         
277         return pageSource.trim();
278     }
279     
280     /**
281      * Call an URL and return the source page.
282      * @param url to call
283      * @return the source page of the URL
284      */
285     public String getSourceLineFeed(String url) {
286         return this.getSource(url, true);
287     }
288     
289     public String getSource(String url) {
290         return this.getSource(url, false);
291     }
292 
293     public void setCustomUserAgent(Builder httpRequest) {
294         if (this.injectionModel.getMediatorUtils().getUserAgentUtil().isCustomUserAgent()) {
295             
296             String agents = this.injectionModel.getMediatorUtils().getUserAgentUtil().getCustomUserAgent();
297             List<String> listAgents = Stream.of(agents.split("[\\r\\n]+"))
298                 .filter(q -> !q.matches("^#.*"))
299                 .collect(Collectors.toList());
300             
301             String randomElement = listAgents.get(this.randomForUserAgent.nextInt(listAgents.size()));
302             
303             httpRequest.setHeader("User-Agent", randomElement);
304         }
305     }
306     
307     
308     // Builder
309 
310     public ConnectionUtil withMethodInjection(AbstractMethodInjection methodInjection) {
311         this.methodInjection = methodInjection;
312         return this;
313     }
314     
315     public ConnectionUtil withTypeRequest(String typeRequest) {
316         this.typeRequest = typeRequest;
317         return this;
318     }
319     
320     
321     // Getters and setters
322     
323     public String getUrlByUser() {
324         return this.urlByUser;
325     }
326 
327     public void setUrlByUser(String urlByUser) {
328         this.urlByUser = urlByUser;
329     }
330     
331     public String getUrlBase() {
332         return this.urlBase;
333     }
334 
335     public void setUrlBase(String urlBase) {
336         this.urlBase = urlBase;
337     }
338     
339     public AbstractMethodInjection getMethodInjection() {
340         return this.methodInjection;
341     }
342 
343     public void setMethodInjection(AbstractMethodInjection methodInjection) {
344         this.methodInjection = methodInjection;
345     }
346     
347     public String getTypeRequest() {
348         return this.typeRequest;
349     }
350 
351     public void setTypeRequest(String typeRequest) {
352         this.typeRequest = typeRequest;
353     }
354 
355     /**
356      * Default timeout used by the jcifs fix. It's the default value used usually by the JVM.
357      */
358     public Integer getTimeout() {
359         return this.injectionModel.getMediatorUtils().getPreferencesUtil().countConnectionTimeout();
360     }
361 
362     public CookieManager getCookieManager() {
363         return cookieManager;
364     }
365 }