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