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