View Javadoc
1   package com.jsql.util;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.bean.util.Interaction;
5   import com.jsql.model.bean.util.Request;
6   import com.jsql.model.exception.InjectionFailureException;
7   import com.jsql.model.injection.method.AbstractMethodInjection;
8   import org.apache.commons.lang3.StringUtils;
9   import org.apache.logging.log4j.LogManager;
10  import org.apache.logging.log4j.Logger;
11  
12  import java.net.IDN;
13  import java.net.MalformedURLException;
14  import java.net.URI;
15  import java.net.URISyntaxException;
16  import java.util.AbstractMap.SimpleEntry;
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.List;
20  import java.util.Objects;
21  import java.util.concurrent.CopyOnWriteArrayList;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  import java.util.stream.Collectors;
25  
26  public class ParameterUtil {
27  
28      /**
29       * Log4j logger sent to view.
30       */
31      private static final Logger LOGGER = LogManager.getRootLogger();
32  
33      /**
34       * Query string built from the URL submitted by user.
35       */
36      private List<SimpleEntry<String, String>> listQueryString = new CopyOnWriteArrayList<>();
37  
38      /**
39       * Request submitted by user.
40       */
41      private List<SimpleEntry<String, String>> listRequest = new CopyOnWriteArrayList<>();
42  
43      /**
44       * Header submitted by user.
45       */
46      private List<SimpleEntry<String, String>> listHeader = new CopyOnWriteArrayList<>();
47  
48      private String rawRequest = StringUtils.EMPTY;
49      private String rawHeader = StringUtils.EMPTY;
50      private boolean isMultipartRequest = false;
51      private static final String FORMAT_KEY_VALUE = "%s=%s";
52  
53      private final InjectionModel injectionModel;
54      
55      public ParameterUtil(InjectionModel injectionModel) {
56          this.injectionModel = injectionModel;
57      }
58      
59      /**
60       * Send each parameters from the GUI to the model in order to
61       * start the preparation of injection, the injection process is
62       * started in a new thread via model function inputValidation().
63       */
64      public void controlInput(
65          String urlQuery,
66          String rawRequest,
67          String rawHeader,
68          AbstractMethodInjection methodInjection,
69          String typeRequest,
70          boolean isScanning
71      ) {
72          try {
73              String urlQueryFixed = urlQuery;
74                 
75              // Keep single check
76              if (urlQueryFixed.isEmpty()) {
77                  throw new MalformedURLException("empty URL");
78              } else if (!urlQueryFixed.matches("(?i)^https?://.*")) {
79                  if (!urlQueryFixed.matches("(?i)^\\w+://.*")) {
80                      
81                      LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Undefined URL protocol, forcing to [http://]");
82                      urlQueryFixed = "http://"+ urlQueryFixed;
83                      
84                  } else {
85                      throw new MalformedURLException("unknown URL protocol");
86                  }
87              }
88  
89              String authority = URI.create(urlQueryFixed).getAuthority();
90              if (authority == null) {
91                  throw new MalformedURLException("incorrect domain authority");
92              }
93              String authorityPunycode = IDN.toASCII(authority);
94              if (!authority.equals(authorityPunycode)) {
95                  LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Punycode domain detected, using [{}] instead of [{}]", authorityPunycode, authority);
96                  urlQueryFixed = urlQueryFixed.replace(authority, authorityPunycode);
97              }
98  
99              this.initializeQueryString(urlQueryFixed);
100             this.initializeHeader(rawHeader);
101             this.initializeRequest(rawRequest);
102 
103             this.injectionModel.getMediatorUtils().getConnectionUtil().setMethodInjection(methodInjection);
104             this.injectionModel.getMediatorUtils().getConnectionUtil().setTypeRequest(typeRequest);
105             
106             if (isScanning) {
107                 this.injectionModel.beginInjection();
108             } else {
109                 // Start the model injection process in a thread
110                 new Thread(
111                     this.injectionModel::beginInjection,
112                     "ThreadBeginInjection"
113                 )
114                 .start();
115             }
116         } catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
117             
118             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Url: {}", e.getMessage());
119             
120             // Incorrect URL, reset the start button
121             var request = new Request();
122             request.setMessage(Interaction.END_PREPARATION);
123             this.injectionModel.sendToViews(request);
124         }
125     }
126 
127     /**
128      * Check integrity of parameters defined by user.
129      * @throws InjectionFailureException when params integrity is failure
130      */
131     public void checkParametersFormat() throws InjectionFailureException {
132         
133         this.checkOneOrLessStar();
134         this.checkStarMatchMethod();
135         this.checkMethodNotEmpty();
136         this.checkMultipart();
137     }
138 
139     private void checkMultipart() throws InjectionFailureException {
140 
141         isMultipartRequest = false;
142 
143         if (
144             this.getListHeader()
145             .stream()
146             .filter(entry -> "Content-Type".equals(entry.getKey()))
147             .anyMatch(entry ->
148                 entry.getValue() != null
149                 && entry.getValue().contains("multipart/form-data")
150                 && entry.getValue().contains("boundary=")
151             )
152         ) {
153             LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Multipart boundary found in header");
154             Matcher matcherBoundary = Pattern.compile("boundary=([^;]*)").matcher(this.getHeaderFromEntries());
155             if (matcherBoundary.find()) {
156                 String boundary = matcherBoundary.group(1);
157                 if (!this.rawRequest.contains(boundary)) {
158                     throw new InjectionFailureException(
159                         String.format("Incorrect multipart data, boundary not found in body: %s", boundary)
160                     );
161                 } else {
162                     isMultipartRequest = true;
163                 }
164             }
165         }
166     }
167 
168     private void checkOneOrLessStar() throws InjectionFailureException {
169         
170         var nbStarInParameter = 0;
171         
172         if (this.getQueryStringFromEntries().contains(InjectionModel.STAR)) {
173             nbStarInParameter++;
174         }
175         if (this.getRequestFromEntries().contains(InjectionModel.STAR)) {
176             nbStarInParameter++;
177         }
178         if (this.getHeaderFromEntries().contains(InjectionModel.STAR)) {
179             nbStarInParameter++;
180         }
181         
182         // Injection Point
183         if (
184             nbStarInParameter > 1
185             || StringUtils.countMatches(this.getQueryStringFromEntries(), "*") > 1
186             || StringUtils.countMatches(this.getRequestFromEntries(), "*") > 1
187             || StringUtils.countMatches(this.getHeaderFromEntries(), "*") > 1
188         ) {
189             throw new InjectionFailureException("Character insertion [*] must be used once in Query String, Request or Header parameters");
190         }
191     }
192     
193     public void checkStarMatchMethod() throws InjectionFailureException {
194         
195         AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection();
196         boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam();
197 
198         if (
199             this.getQueryStringFromEntries().contains(InjectionModel.STAR)
200             && methodInjection != this.injectionModel.getMediatorMethod().getQuery()
201             && !isCheckingAllParam
202         ) {
203             throw new InjectionFailureException("Select method GET to use character [*] or remove [*] from GET parameters");
204         } else if (
205             this.getRequestFromEntries().contains(InjectionModel.STAR)
206             && methodInjection != this.injectionModel.getMediatorMethod().getRequest()
207             && !isCheckingAllParam
208         ) {
209             throw new InjectionFailureException("Select a Request method (like POST) to use [*], or remove [*] from Request parameters");
210         } else if (
211             this.getHeaderFromEntries().contains(InjectionModel.STAR)
212             && methodInjection != this.injectionModel.getMediatorMethod().getHeader()
213             && !isCheckingAllParam
214         ) {
215             throw new InjectionFailureException("Select method Header to use character [*] or remove [*] from Header parameters");
216         }
217     }
218     
219     public void checkMethodNotEmpty() throws InjectionFailureException {
220         
221         AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection();
222         boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam();
223         
224         if (
225             methodInjection == this.injectionModel.getMediatorMethod().getQuery()
226             && !isCheckingAllParam
227             && this.getListQueryString().isEmpty()
228             && !this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)
229         ) {
230             throw new InjectionFailureException("No query string");
231         } else if (
232             methodInjection == this.injectionModel.getMediatorMethod().getRequest()
233             && this.getListRequest().isEmpty()
234         ) {
235             throw new InjectionFailureException("Incorrect Request format");
236         } else if (
237             methodInjection == this.injectionModel.getMediatorMethod().getHeader()
238             && this.getListHeader().isEmpty()
239         ) {
240             throw new InjectionFailureException("Incorrect Header format");
241         }
242     }
243     
244     public String initializeStar(SimpleEntry<String, String> parameterToInject) {
245         
246         String characterInsertionByUser;
247 
248         if (parameterToInject == null) {
249             characterInsertionByUser = InjectionModel.STAR;
250         } else {
251             
252             characterInsertionByUser = parameterToInject.getValue();
253             parameterToInject.setValue(InjectionModel.STAR);
254         }
255         
256         return characterInsertionByUser;
257     }
258 
259     public void initializeQueryString(String urlQuery) throws MalformedURLException, URISyntaxException {
260 
261         // Format and get rid of anchor fragment using native URL
262         var url = new URI(urlQuery).toURL();
263         
264         if (
265             StringUtils.isEmpty(urlQuery)
266             || StringUtils.isEmpty(url.getHost())
267         ) {
268             throw new MalformedURLException("empty URL");
269         }
270         
271         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlByUser(urlQuery);
272         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(urlQuery);
273         
274         this.listQueryString.clear();
275         
276         // Parse url and GET query string
277         var regexQueryString = Pattern.compile("(.*\\?)(.*)").matcher(urlQuery);
278         
279         if (!regexQueryString.find()) {
280             return;
281         }
282         
283         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(regexQueryString.group(1));
284         
285         if (StringUtils.isNotEmpty(url.getQuery())) {
286             this.listQueryString = Pattern.compile("&")
287                 .splitAsStream(url.getQuery())
288                 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
289                 .map(keyValue -> new SimpleEntry<>(
290                     keyValue[0],
291                     keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
292                 ))
293                 .collect(Collectors.toList());
294         }
295     }
296 
297     public void initializeRequest(String rawRequest) {
298 
299         this.rawRequest = rawRequest;
300         this.listRequest.clear();
301 
302         if (StringUtils.isNotEmpty(rawRequest)) {
303             if (isMultipartRequest()) {
304                 // Pass request containing star * param without any parsing
305                 this.listRequest = new ArrayList<>(List.of(new SimpleEntry<>(
306                     rawRequest,
307                     ""
308                 )));
309             } else {
310                 this.listRequest = Pattern.compile("&")
311                     .splitAsStream(rawRequest)
312                     .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
313                     .map(keyValue -> new SimpleEntry<>(
314                         keyValue[0],
315                         keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
316                     ))
317                     .collect(Collectors.toList());
318             }
319         }
320     }
321 
322     public void initializeHeader(String rawHeader) {
323         
324         this.rawHeader = rawHeader;
325         this.listHeader.clear();
326 
327         if (StringUtils.isNotEmpty(rawHeader)) {
328             this.listHeader = Pattern.compile("\\\\r\\\\n")
329                 .splitAsStream(rawHeader)
330                 .map(keyValue -> Arrays.copyOf(keyValue.split(":"), 2))
331                 .map(keyValue -> new SimpleEntry<>(
332                     keyValue[0],
333                     keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
334                 ))
335                 .collect(Collectors.toList());
336         }
337     }
338     
339     public String getQueryStringFromEntries() {
340         return this.listQueryString.stream()
341             .filter(Objects::nonNull)
342             .map(entry -> {
343                 if (
344                     this.injectionModel.getMediatorStrategy().getStrategy() == this.injectionModel.getMediatorStrategy().getMultibit()
345                     && entry.getValue() != null
346                     && entry.getValue().contains(InjectionModel.STAR)
347                 ) {
348                     return String.format(FORMAT_KEY_VALUE, entry.getKey(), InjectionModel.STAR);
349                 } else {
350                     return String.format(FORMAT_KEY_VALUE, entry.getKey(), entry.getValue());
351                 }
352             })
353             .collect(Collectors.joining("&"));
354     }
355 
356     public String getRequestFromEntries() {
357         return this.listRequest.stream()
358             .filter(Objects::nonNull)
359             .map(entry -> String.format(
360                 FORMAT_KEY_VALUE,
361                 entry.getKey(),
362                 StringUtils.isEmpty(entry.getValue()) ? "" : entry.getValue()
363             ))
364             .collect(Collectors.joining("&"));
365     }
366     
367     public String getHeaderFromEntries() {
368         return this.listHeader.stream()
369             .filter(Objects::nonNull)
370             .map(entry -> String.format(
371                 "%s:%s",
372                 entry.getKey(),
373                 entry.getValue()
374             ))
375             .collect(Collectors.joining("\\r\\n"));
376     }
377 
378     public boolean isRequestSoap() {
379         return this.rawRequest
380             .trim()
381             .matches("^(<soapenv:|<\\?xml).*");
382     }
383 
384     
385     // Getters / setters
386     
387     public String getRawRequest() {
388         return this.rawRequest;
389     }
390 
391     public String getRawHeader() {
392         return this.rawHeader;
393     }
394 
395     public List<SimpleEntry<String, String>> getListRequest() {
396         return this.listRequest;
397     }
398 
399     public void setListRequest(List<SimpleEntry<String, String>> listRequest) {
400         this.listRequest = listRequest;
401     }
402 
403     public List<SimpleEntry<String, String>> getListHeader() {
404         return this.listHeader;
405     }
406 
407     public void setListHeader(List<SimpleEntry<String, String>> listHeader) {
408         this.listHeader = listHeader;
409     }
410     
411     public List<SimpleEntry<String, String>> getListQueryString() {
412         return this.listQueryString;
413     }
414     
415     public void setListQueryString(List<SimpleEntry<String, String>> listQueryString) {
416         this.listQueryString = listQueryString;
417     }
418 
419     public boolean isMultipartRequest() {
420         return isMultipartRequest;
421     }
422 }