1 package com.jsql.model.suspendable;
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 com.jsql.model.exception.StoppedByUserSlidingException;
9 import com.jsql.model.injection.strategy.blind.InjectionCharInsertion;
10 import com.jsql.model.injection.vendor.MediatorVendor;
11 import com.jsql.model.injection.vendor.model.Vendor;
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.Collectors;
27 import java.util.stream.Stream;
28
29
30
31
32
33
34
35
36
37 public class SuspendableGetCharInsertion extends AbstractSuspendable {
38
39 private static final Logger LOGGER = LogManager.getRootLogger();
40
41 private static final String LABEL_PREFIX = "prefix";
42
43 public SuspendableGetCharInsertion(InjectionModel injectionModel) {
44 super(injectionModel);
45 }
46
47 @Override
48 public String run(Object... args) throws JSqlException {
49 String characterInsertionByUser = (String) args[0];
50
51 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetInsertionCharacter");
52 CompletionService<CallablePageSource> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
53
54 var charFromBooleanMatch = new String[1];
55 List<String> charactersInsertion = this.initCallables(taskCompletionService, charFromBooleanMatch);
56
57 var mediatorVendor = this.injectionModel.getMediatorVendor();
58 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Fingerprinting database and character insertion with Order by match...");
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<Vendor> vendorsOrderByMatch = this.getVendorsOrderByMatch(mediatorVendor, pageSource);
73 if (!vendorsOrderByMatch.isEmpty()) {
74 this.setVendor(mediatorVendor, vendorsOrderByMatch);
75
76 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Using [{}]", mediatorVendor.getVendor());
77 var requestSetVendor = new Request();
78 requestSetVendor.setMessage(Interaction.SET_VENDOR);
79 Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
80 msgHeader.put(Header.URL, this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlByUser());
81 msgHeader.put(Header.VENDOR, mediatorVendor.getVendor());
82 requestSetVendor.setParameters(msgHeader);
83 this.injectionModel.sendToViews(requestSetVendor);
84
85 charFromOrderBy = currentCallable.getCharacterInsertion();
86 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Character insertion [{}] matching with Order by and compatible with Error strategy", charFromOrderBy);
87 break;
88 }
89 } catch (InterruptedException e) {
90 LOGGER.log(LogLevelUtil.IGNORE, e, e);
91 Thread.currentThread().interrupt();
92 } catch (ExecutionException e) {
93 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
94 }
95 }
96 this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
97 if (charFromOrderBy == null && charFromBooleanMatch[0] != null) {
98 charFromOrderBy = charFromBooleanMatch[0];
99 }
100 return this.getCharacterInsertion(characterInsertionByUser, charFromOrderBy);
101 }
102
103 private void setVendor(MediatorVendor mediatorVendor, List<Vendor> vendorsOrderByMatch) {
104 if (
105 vendorsOrderByMatch.size() == 1
106 && vendorsOrderByMatch.get(0) != mediatorVendor.getVendor()
107 ) {
108 mediatorVendor.setVendor(vendorsOrderByMatch.get(0));
109 } else if (vendorsOrderByMatch.size() > 1) {
110 if (vendorsOrderByMatch.contains(mediatorVendor.getPostgres())) {
111 mediatorVendor.setVendor(mediatorVendor.getPostgres());
112 } else if (vendorsOrderByMatch.contains(mediatorVendor.getMysql())) {
113 mediatorVendor.setVendor(mediatorVendor.getMysql());
114 } else {
115 mediatorVendor.setVendor(vendorsOrderByMatch.get(0));
116 }
117 }
118 }
119
120 private List<Vendor> getVendorsOrderByMatch(MediatorVendor mediatorVendor, String pageSource) {
121 return mediatorVendor.getVendorsForFingerprint()
122 .stream()
123 .filter(vendor -> vendor != mediatorVendor.getAuto())
124 .filter(vendor -> StringUtils.isNotEmpty(
125 vendor.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
126 ))
127 .filter(vendor -> {
128 Optional<String> optionalOrderByErrorMatch = Stream.of(
129 vendor.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
130 .split("[\\r\\n]+")
131 )
132 .filter(errorMessage ->
133 Pattern
134 .compile(".*" + errorMessage + ".*", Pattern.DOTALL)
135 .matcher(pageSource)
136 .matches()
137 )
138 .findAny();
139 if (optionalOrderByErrorMatch.isPresent()) {
140 LOGGER.log(
141 LogLevelUtil.CONSOLE_SUCCESS,
142 String.format("Order by fingerprint matching vendor [%s]", vendor)
143 );
144 }
145 return optionalOrderByErrorMatch.isPresent();
146 })
147 .collect(Collectors.toList());
148 }
149
150 private List<String> initCallables(CompletionService<CallablePageSource> taskCompletionService, String[] charFromBooleanMatch) throws JSqlException {
151 List<String> prefixValues = Arrays.asList(
152 RandomStringUtils.secure().next(10, "012"),
153 "1"
154 );
155 List<String> prefixQuotes = Arrays.asList(
156 SuspendableGetCharInsertion.LABEL_PREFIX +"'",
157 SuspendableGetCharInsertion.LABEL_PREFIX,
158 SuspendableGetCharInsertion.LABEL_PREFIX +"`",
159 SuspendableGetCharInsertion.LABEL_PREFIX +"\"",
160 SuspendableGetCharInsertion.LABEL_PREFIX +"%bf'"
161 );
162 List<String> prefixParentheses = Arrays.asList(StringUtils.EMPTY, ")", "))");
163 List<String> charactersInsertion = new ArrayList<>();
164 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Fingerprinting character insertion with Boolean match...");
165 for (String prefixValue: prefixValues) {
166 for (String prefixQuote: prefixQuotes) {
167 for (String prefixParenthesis: prefixParentheses) {
168 this.checkInsertionChar(charFromBooleanMatch, charactersInsertion, prefixValue, prefixQuote, prefixParenthesis);
169 }
170 }
171 }
172 for (String characterInsertion: charactersInsertion) {
173 taskCompletionService.submit(
174 new CallablePageSource(
175 characterInsertion
176 + StringUtils.SPACE
177 + this.injectionModel.getMediatorVendor().getVendor().instance().sqlOrderBy(),
178 characterInsertion,
179 this.injectionModel,
180 "prefix#orderby"
181 )
182 );
183 }
184 return charactersInsertion;
185 }
186
187 private void checkInsertionChar(
188 String[] charFromBooleanMatch,
189 List<String> charactersInsertion,
190 String prefixValue,
191 String prefixQuote,
192 String prefixParenthesis
193 ) throws StoppedByUserSlidingException {
194 String characterInsertion = prefixQuote.replace(SuspendableGetCharInsertion.LABEL_PREFIX, prefixValue) + prefixParenthesis;
195 charactersInsertion.add(characterInsertion);
196
197 if (charFromBooleanMatch[0] == null) {
198 var injectionCharInsertion = new InjectionCharInsertion(
199 this.injectionModel,
200 characterInsertion,
201 prefixQuote + prefixParenthesis
202 );
203 if (injectionCharInsertion.isInjectable()) {
204 if (this.isSuspended()) {
205 throw new StoppedByUserSlidingException();
206 }
207 charFromBooleanMatch[0] = characterInsertion;
208 LOGGER.log(
209 LogLevelUtil.CONSOLE_SUCCESS,
210 "Found character insertion [{}] using Boolean match",
211 () -> charFromBooleanMatch[0]
212 );
213 }
214 }
215 }
216
217 private String getCharacterInsertion(String characterInsertionByUser, String characterInsertionDetected) {
218 String characterInsertionDetectedFixed = characterInsertionDetected;
219 if (characterInsertionDetectedFixed == null) {
220 characterInsertionDetectedFixed = characterInsertionByUser;
221 String logCharacterInsertion = characterInsertionDetectedFixed;
222 LOGGER.log(
223 LogLevelUtil.CONSOLE_ERROR,
224 "No character insertion found, forcing to [{}]",
225 () -> logCharacterInsertion.replace(InjectionModel.STAR, StringUtils.EMPTY)
226 );
227 } else if (!characterInsertionByUser.replace(InjectionModel.STAR, StringUtils.EMPTY).equals(characterInsertionDetectedFixed)) {
228 String characterInsertionByUserFormat = characterInsertionByUser.replace(InjectionModel.STAR, StringUtils.EMPTY);
229 LOGGER.log(
230 LogLevelUtil.CONSOLE_INFORM,
231 "Using [{}] and matching [{}]",
232 () -> this.injectionModel.getMediatorVendor().getVendor(),
233 () -> characterInsertionDetected
234 );
235 LOGGER.log(
236 LogLevelUtil.CONSOLE_DEFAULT,
237 "Disable search for char insertion in Preferences to force the value [{}]",
238 () -> characterInsertionByUserFormat
239 );
240 } else {
241 LOGGER.log(
242 LogLevelUtil.CONSOLE_INFORM,
243 "{} [{}]",
244 () -> I18nUtil.valueByKey("LOG_USING_INSERTION_CHARACTER"),
245 () -> characterInsertionDetected.replace(InjectionModel.STAR, StringUtils.EMPTY)
246 );
247 }
248 return characterInsertionDetectedFixed;
249 }
250 }