1 package com.jsql.util;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.view.subscriber.Seal;
5 import com.jsql.model.exception.JSqlException;
6 import org.apache.commons.lang3.StringUtils;
7 import org.apache.logging.log4j.LogManager;
8 import org.apache.logging.log4j.Logger;
9
10 import java.io.IOException;
11 import java.net.HttpCookie;
12 import java.net.URLEncoder;
13 import java.net.http.HttpRequest.Builder;
14 import java.net.http.HttpResponse;
15 import java.net.http.HttpResponse.BodyHandlers;
16 import java.nio.charset.StandardCharsets;
17 import java.text.DecimalFormat;
18 import java.util.AbstractMap.SimpleEntry;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.regex.Pattern;
22 import java.util.stream.Stream;
23
24 public class HeaderUtil {
25
26 private static final Logger LOGGER = LogManager.getRootLogger();
27
28 public static final String CONTENT_TYPE_REQUEST = "Content-Type";
29 public static final String WWW_AUTHENTICATE_RESPONSE = "www-authenticate";
30 private static final String REGEX_HTTP_STATUS = "4\\d\\d";
31 private static final String FOUND_STATUS_HTTP = "Found status HTTP";
32
33 private final InjectionModel injectionModel;
34
35 public HeaderUtil(InjectionModel injectionModel) {
36 this.injectionModel = injectionModel;
37 }
38
39
40
41
42
43
44
45 public static void sanitizeHeaders(Builder httpRequest, SimpleEntry<String, String> header) throws JSqlException {
46 String keyHeader = header.getKey().trim();
47 String valueHeader = header.getValue().trim();
48
49 if ("cookie".equalsIgnoreCase(keyHeader) && Pattern.compile(".+=.*").matcher(valueHeader).find()) {
50
51 List<String> cookies = Stream.of(valueHeader.split(";"))
52 .filter(value -> value.contains("="))
53 .map(cookie -> cookie.split("=", 2))
54 .map(arrayEntry -> arrayEntry[0].trim() + "=" + (
55 arrayEntry[1] == null
56 ? StringUtils.EMPTY
57
58 : URLEncoder.encode(arrayEntry[1].trim().replace("+", "%2B"), StandardCharsets.UTF_8)
59 ))
60 .toList();
61 valueHeader = String.join("; ", cookies);
62 }
63
64 try {
65 httpRequest.setHeader(
66 keyHeader,
67 valueHeader.replaceAll("[^\\p{ASCII}]", StringUtils.EMPTY)
68 );
69 } catch (IllegalArgumentException e) {
70 throw new JSqlException(e);
71 }
72 }
73
74
75
76
77
78
79
80
81 public HttpResponse<String> checkResponseHeader(Builder httpRequestBuilder, String body) throws IOException, InterruptedException {
82 var httpRequest = httpRequestBuilder.build();
83 HttpResponse<String> httpResponse = this.injectionModel.getMediatorUtils().connectionUtil().getHttpClient().build().send(
84 httpRequest,
85 BodyHandlers.ofString()
86 );
87 String pageSource = httpResponse.body();
88
89 List<HttpCookie> cookies = this.injectionModel.getMediatorUtils().connectionUtil().getCookieManager().getCookieStore().getCookies();
90 if (!cookies.isEmpty()) {
91 LOGGER.info("Cookies set by host: {}", cookies);
92 }
93
94 var responseCode = Integer.toString(httpResponse.statusCode());
95 Map<String, String> mapResponseHeaders = ConnectionUtil.getHeadersMap(httpResponse);
96 this.checkResponse(responseCode, mapResponseHeaders);
97 this.checkStatus(httpResponse);
98
99 this.injectionModel.getMediatorUtils().formUtil().parseForms(httpResponse.statusCode(), pageSource);
100 this.injectionModel.getMediatorUtils().csrfUtil().parseForCsrfToken(pageSource, mapResponseHeaders);
101 this.injectionModel.getMediatorUtils().digestUtil().parseWwwAuthenticate(mapResponseHeaders);
102
103 int sizeHeaders = mapResponseHeaders.keySet()
104 .stream()
105 .map(key -> mapResponseHeaders.get(key).length() + key.length())
106 .mapToInt(Integer::intValue)
107 .sum();
108 float size = (float) (pageSource.length() + sizeHeaders) / 1024;
109 var decimalFormat = new DecimalFormat("0.000");
110
111
112 this.injectionModel.sendToViews(new Seal.MessageHeader(
113 httpRequest.uri().toURL().toString(),
114 body,
115 ConnectionUtil.getHeadersMap(httpRequest.headers()),
116 mapResponseHeaders,
117 pageSource,
118 decimalFormat.format(size),
119 "#none",
120 "test#conn",
121 null
122 ));
123
124 return httpResponse;
125 }
126
127 private void checkStatus(HttpResponse<String> response) {
128 if (response.statusCode() >= 400) {
129 if (this.injectionModel.getMediatorUtils().preferencesUtil().isNotTestingConnection()) {
130 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Connection test disabled, skipping error {}...", response.statusCode());
131 } else {
132 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Try with option 'Disable connection test' to skip HTTP error {}", response.statusCode());
133 }
134 }
135 }
136
137 private void checkResponse(String responseCode, Map<String, String> mapResponse) {
138 if (this.isBasicAuth(responseCode, mapResponse)) {
139 LOGGER.log(
140 LogLevelUtil.CONSOLE_ERROR,
141 "Basic Authentication detected: "
142 + "set authentication in preferences, "
143 + "or add header 'Authorization: Basic b3N..3Jk', with b3N..3Jk as "
144 + "'osUserName:osPassword' encoded in Base64 (use the Coder in jSQL to encode the string)."
145 );
146 } else if (this.isNtlm(responseCode, mapResponse)) {
147 LOGGER.log(
148 LogLevelUtil.CONSOLE_ERROR,
149 "NTLM Authentication detected: "
150 + "set authentication in preferences, "
151 + "or add username, password and domain information to the URL, e.g. http://domain\\user:password@127.0.0.1/[..]"
152 );
153 } else if (this.isDigest(responseCode, mapResponse)) {
154 LOGGER.log(
155 LogLevelUtil.CONSOLE_ERROR,
156 "Digest Authentication detected: set authentication in preferences."
157 );
158 } else if (this.isNegotiate(responseCode, mapResponse)) {
159 LOGGER.log(
160 LogLevelUtil.CONSOLE_ERROR,
161 "Negotiate Authentication detected: "
162 + "add username, password and domain information to the URL, e.g. http://domain\\user:password@127.0.0.1/[..]"
163 );
164 } else if (Pattern.matches("1\\d\\d", responseCode)) {
165 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} {} Informational", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
166 } else if (Pattern.matches("2\\d\\d", responseCode)) {
167 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "{} {} Success", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
168 } else if (Pattern.matches("3\\d\\d", responseCode)) {
169
170 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Redirection", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
171
172 if (!this.injectionModel.getMediatorUtils().preferencesUtil().isFollowingRedirection()) {
173 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "If injection fails retry with option 'Follow HTTP redirection' activated");
174 } else {
175 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Redirecting to the next page...");
176 }
177 } else if (Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)) {
178 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Client Error", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
179 } else if (Pattern.matches("5\\d\\d", responseCode)) {
180 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Server Error", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
181 } else {
182 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} {} Unknown", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
183 }
184 }
185
186 private boolean isNegotiate(String responseCode, Map<String, String> mapResponse) {
187 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
188 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
189 && "Negotiate".equals(mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE));
190 }
191
192 private boolean isDigest(String responseCode, Map<String, String> mapResponse) {
193 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
194 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
195 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE) != null
196 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE).startsWith("Digest ");
197 }
198
199 private boolean isNtlm(String responseCode, Map<String, String> mapResponse) {
200 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
201 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
202 && "NTLM".equals(mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE));
203 }
204
205 private boolean isBasicAuth(String responseCode, Map<String, String> mapResponse) {
206 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
207 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
208 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE) != null
209 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE).startsWith("Basic ");
210 }
211 }