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 parameter 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              // Keep single check
75              if (urlQueryFixed.isEmpty()) {
76                  throw new MalformedURLException("empty URL");
77              } else if (!urlQueryFixed.matches("(?i)^https?://.*")) {
78                  if (!urlQueryFixed.matches("(?i)^\\w+://.*")) {
79                      LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Undefined URL protocol, forcing to [http://]");
80                      urlQueryFixed = "http://"+ urlQueryFixed;
81                  } else {
82                      throw new MalformedURLException("unknown URL protocol");
83                  }
84              }
85  
86              String authority = URI.create(urlQueryFixed).getAuthority();
87              if (authority == null) {
88                  throw new MalformedURLException("incorrect domain authority");
89              }
90              String authorityPunycode = IDN.toASCII(authority);
91              if (!authority.equals(authorityPunycode)) {
92                  LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Punycode domain detected, using [{}] instead of [{}]", authorityPunycode, authority);
93                  urlQueryFixed = urlQueryFixed.replace(authority, authorityPunycode);
94              }
95  
96              this.initQueryString(urlQueryFixed);
97              this.initHeader(rawHeader);
98              this.initRequest(rawRequest);
99  
100             this.injectionModel.getMediatorUtils().getConnectionUtil().setMethodInjection(methodInjection);
101             this.injectionModel.getMediatorUtils().getConnectionUtil().setTypeRequest(typeRequest);
102             
103             if (isScanning) {
104                 this.injectionModel.beginInjection();
105             } else {
106                 new Thread(this.injectionModel::beginInjection, "ThreadBeginInjection").start();  // in thread
107             }
108         } catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
109             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Url: {}", e.getMessage());
110             
111             // Incorrect URL, reset the start button
112             var request = new Request();
113             request.setMessage(Interaction.END_PREPARATION);
114             this.injectionModel.sendToViews(request);
115         }
116     }
117 
118     /**
119      * Check integrity of parameters defined by user.
120      * @throws InjectionFailureException when params integrity is failure
121      */
122     public void checkParametersFormat() throws InjectionFailureException {
123         this.checkOneOrLessStar();
124         this.checkStarMatchMethod();
125         this.checkMethodNotEmpty();
126         this.checkMultipart();
127         if (ParameterUtil.isInvalidName(this.injectionModel.getMediatorUtils().getConnectionUtil().getTypeRequest())) {
128             throw new InjectionFailureException(String.format(
129                 "Illegal method: \"%s\"",
130                 this.injectionModel.getMediatorUtils().getConnectionUtil().getTypeRequest()
131             ));
132         }
133     }
134 
135     // ABNF primitives defined in RFC 7230
136     private static final boolean[] tchar = new boolean[256];
137 
138     static {
139         char[] allowedTokenChars = (
140             "!#$%&'*+-.^_`|~0123456789" +
141             "abcdefghijklmnopqrstuvwxyz" +
142             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
143         ).toCharArray();
144         for (char c : allowedTokenChars) {
145             ParameterUtil.tchar[c] = true;
146         }
147     }
148 
149     /*
150      * Validates a RFC 7230 field-name.
151      */
152     public static boolean isInvalidName(String token) {
153         for (int i = 0; i < token.length(); i++) {
154             char c = token.charAt(i);
155             if (c > 255 || !ParameterUtil.tchar[c]) {
156                 return true;
157             }
158         }
159         return token.isEmpty();
160     }
161 
162     private void checkMultipart() throws InjectionFailureException {
163         this.isMultipartRequest = false;
164 
165         if (
166             this.getListHeader()
167             .stream()
168             .filter(entry -> "Content-Type".equals(entry.getKey()))
169             .anyMatch(entry ->
170                 entry.getValue() != null
171                 && entry.getValue().contains("multipart/form-data")
172                 && entry.getValue().contains("boundary=")
173             )
174         ) {
175             LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Multipart boundary found in header");
176             Matcher matcherBoundary = Pattern.compile("boundary=([^;]*)").matcher(this.getHeaderFromEntries());
177             if (matcherBoundary.find()) {
178                 String boundary = matcherBoundary.group(1);
179                 if (!this.rawRequest.contains(boundary)) {
180                     throw new InjectionFailureException(
181                         String.format("Incorrect multipart data, boundary not found in body: %s", boundary)
182                     );
183                 } else {
184                     this.isMultipartRequest = true;
185                 }
186             }
187         }
188     }
189 
190     private void checkOneOrLessStar() throws InjectionFailureException {
191         var nbStarInParameter = 0;
192         
193         if (this.getQueryStringFromEntries().contains(InjectionModel.STAR)) {
194             nbStarInParameter++;
195         }
196         if (this.getRequestFromEntries().contains(InjectionModel.STAR)) {
197             nbStarInParameter++;
198         }
199         if (this.getHeaderFromEntries().contains(InjectionModel.STAR)) {
200             nbStarInParameter++;
201         }
202         
203         // Injection Point
204         if (
205             nbStarInParameter > 1
206             || StringUtils.countMatches(this.getQueryStringFromEntries(), "*") > 1
207             || StringUtils.countMatches(this.getRequestFromEntries(), "*") > 1
208             || StringUtils.countMatches(this.getHeaderFromEntries(), "*") > 1
209         ) {
210             throw new InjectionFailureException("Character insertion [*] must be used once in Query String, Request or Header parameters");
211         }
212     }
213     
214     public void checkStarMatchMethod() throws InjectionFailureException {
215         AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection();
216         boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam();
217 
218         if (
219             this.getQueryStringFromEntries().contains(InjectionModel.STAR)
220             && methodInjection != this.injectionModel.getMediatorMethod().getQuery()
221             && !isCheckingAllParam
222         ) {
223             throw new InjectionFailureException("Select method GET to use character [*] or remove [*] from GET parameters");
224         } else if (
225             this.getRequestFromEntries().contains(InjectionModel.STAR)
226             && methodInjection != this.injectionModel.getMediatorMethod().getRequest()
227             && !isCheckingAllParam
228         ) {
229             throw new InjectionFailureException("Select a Request method (like POST) to use [*], or remove [*] from Request parameters");
230         } else if (
231             this.getHeaderFromEntries().contains(InjectionModel.STAR)
232             && methodInjection != this.injectionModel.getMediatorMethod().getHeader()
233             && !isCheckingAllParam
234         ) {
235             throw new InjectionFailureException("Select method Header to use character [*] or remove [*] from Header parameters");
236         }
237     }
238     
239     public void checkMethodNotEmpty() throws InjectionFailureException {
240         AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection();
241         boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam();
242         
243         if (
244             methodInjection == this.injectionModel.getMediatorMethod().getQuery()
245             && !isCheckingAllParam
246             && this.getListQueryString().isEmpty()
247             && !this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)
248         ) {
249             throw new InjectionFailureException("No query string");
250         } else if (
251             methodInjection == this.injectionModel.getMediatorMethod().getRequest()
252             && this.getListRequest().isEmpty()
253         ) {
254             throw new InjectionFailureException("Incorrect Request format");
255         } else if (
256             methodInjection == this.injectionModel.getMediatorMethod().getHeader()
257             && this.getListHeader().isEmpty()
258         ) {
259             throw new InjectionFailureException("Incorrect Header format");
260         }
261     }
262     
263     public String initStar(SimpleEntry<String, String> parameterToInject) {
264         String characterInsertionByUser;
265         if (parameterToInject == null) {
266             characterInsertionByUser = InjectionModel.STAR;
267         } else {
268             characterInsertionByUser = parameterToInject.getValue();
269             parameterToInject.setValue(InjectionModel.STAR);
270         }
271         return characterInsertionByUser;
272     }
273 
274     public void initQueryString(String urlQuery) throws MalformedURLException, URISyntaxException {
275         // Format and get rid of anchor fragment using native URL
276         var url = new URI(urlQuery).toURL();
277         
278         if (
279             StringUtils.isEmpty(urlQuery)
280             || StringUtils.isEmpty(url.getHost())
281         ) {
282             throw new MalformedURLException("empty URL");
283         }
284         
285         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlByUser(urlQuery);
286         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(urlQuery);
287         this.listQueryString.clear();
288         
289         // Parse url and GET query string
290         var regexQueryString = Pattern.compile("(.*\\?)(.*)").matcher(urlQuery);
291         if (!regexQueryString.find()) {
292             return;
293         }
294         
295         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(regexQueryString.group(1));
296         
297         if (StringUtils.isNotEmpty(url.getQuery())) {
298             this.listQueryString = Pattern.compile("&")
299                 .splitAsStream(url.getQuery())
300                 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
301                 .map(keyValue -> new SimpleEntry<>(
302                     keyValue[0],
303                     keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
304                 ))
305                 .collect(Collectors.toList());
306         }
307     }
308 
309     public void initRequest(String rawRequest) {
310         this.rawRequest = rawRequest;
311         this.listRequest.clear();
312         if (StringUtils.isNotEmpty(rawRequest)) {
313             if (this.isMultipartRequest) {
314                 // Pass request containing star * param without any parsing
315                 this.listRequest = new ArrayList<>(List.of(new SimpleEntry<>(
316                     rawRequest,
317                     StringUtils.EMPTY
318                 )));
319             } else {
320                 this.listRequest = Pattern.compile("&")
321                     .splitAsStream(rawRequest)
322                     .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
323                     .map(keyValue -> new SimpleEntry<>(
324                         keyValue[0],
325                         keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
326                     ))
327                     .collect(Collectors.toList());
328             }
329         }
330     }
331 
332     public void initHeader(String rawHeader) {
333         this.rawHeader = rawHeader;
334         this.listHeader.clear();
335         if (StringUtils.isNotEmpty(rawHeader)) {
336             this.listHeader = Pattern.compile("\\\\r\\\\n")
337                 .splitAsStream(rawHeader)
338                 .map(keyValue -> Arrays.copyOf(keyValue.split(":"), 2))
339                 .map(keyValue -> new SimpleEntry<>(
340                     keyValue[0],
341                     keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
342                 ))
343                 .collect(Collectors.toList());
344         }
345     }
346     
347     public String getQueryStringFromEntries() {
348         return this.listQueryString.stream()
349             .filter(Objects::nonNull)
350             .map(entry -> {
351                 if (
352                     this.injectionModel.getMediatorStrategy().getStrategy() == this.injectionModel.getMediatorStrategy().getMultibit()
353                     && entry.getValue() != null
354                     && entry.getValue().contains(InjectionModel.STAR)
355                 ) {
356                     return String.format(ParameterUtil.FORMAT_KEY_VALUE, entry.getKey(), InjectionModel.STAR);
357                 } else {
358                     return String.format(ParameterUtil.FORMAT_KEY_VALUE, entry.getKey(), entry.getValue());
359                 }
360             })
361             .collect(Collectors.joining("&"));
362     }
363 
364     public String getRequestFromEntries() {
365         return this.listRequest.stream()
366             .filter(Objects::nonNull)
367             .map(entry -> String.format(
368                 ParameterUtil.FORMAT_KEY_VALUE,
369                 entry.getKey(),
370                 StringUtils.isEmpty(entry.getValue()) ? StringUtils.EMPTY : entry.getValue()
371             ))
372             .collect(Collectors.joining("&"));
373     }
374     
375     public String getHeaderFromEntries() {
376         return this.listHeader.stream()
377             .filter(Objects::nonNull)
378             .map(entry -> String.format("%s:%s", entry.getKey(), entry.getValue()))
379             .collect(Collectors.joining("\\r\\n"));
380     }
381 
382     public boolean isRequestSoap() {
383         return this.rawRequest
384             .trim()
385             .matches("^(<soapenv:|<\\?xml).*");
386     }
387 
388     
389     // Getters / setters
390     
391     public String getRawRequest() {
392         return this.rawRequest;
393     }
394 
395     public String getRawHeader() {
396         return this.rawHeader;
397     }
398 
399     public List<SimpleEntry<String, String>> getListRequest() {
400         return this.listRequest;
401     }
402 
403     public List<SimpleEntry<String, String>> getListHeader() {
404         return this.listHeader;
405     }
406 
407     public List<SimpleEntry<String, String>> getListQueryString() {
408         return this.listQueryString;
409     }
410     
411     public boolean isMultipartRequest() {
412         return this.isMultipartRequest;
413     }
414 }