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