1 | package com.jsql.model.injection.strategy.blind; | |
2 | ||
3 | import com.jsql.model.InjectionModel; | |
4 | import com.jsql.model.accessible.DataAccess; | |
5 | import com.jsql.model.bean.util.Interaction; | |
6 | import com.jsql.model.bean.util.Request; | |
7 | import com.jsql.model.exception.InjectionFailureException; | |
8 | import com.jsql.model.exception.StoppedByUserSlidingException; | |
9 | import com.jsql.model.suspendable.AbstractSuspendable; | |
10 | import com.jsql.util.LogLevelUtil; | |
11 | import org.apache.logging.log4j.LogManager; | |
12 | import org.apache.logging.log4j.Logger; | |
13 | ||
14 | import java.util.ArrayList; | |
15 | import java.util.List; | |
16 | import java.util.concurrent.CompletionService; | |
17 | import java.util.concurrent.ExecutionException; | |
18 | import java.util.concurrent.ExecutorCompletionService; | |
19 | import java.util.concurrent.ExecutorService; | |
20 | import java.util.concurrent.atomic.AtomicInteger; | |
21 | ||
22 | public abstract class AbstractInjectionBoolean<T extends AbstractCallableBoolean<T>> { | |
23 | | |
24 | /** | |
25 | * Log4j logger sent to view. | |
26 | */ | |
27 | private static final Logger LOGGER = LogManager.getRootLogger(); | |
28 | | |
29 | // Every FALSE SQL statements will be checked, | |
30 | // more statements means a more robust application | |
31 | protected final List<String> falsy; | |
32 | | |
33 | // Every TRUE SQL statements will be checked, | |
34 | // more statements means a more robust application | |
35 | protected final List<String> truthy; | |
36 | | |
37 | public enum BooleanMode { | |
38 | AND, OR, STACKED, NO_MODE | |
39 | } | |
40 | | |
41 | protected final InjectionModel injectionModel; | |
42 | | |
43 | protected final BooleanMode booleanMode; | |
44 | | |
45 | protected AbstractInjectionBoolean(InjectionModel injectionModel, BooleanMode booleanMode) { | |
46 | | |
47 | this.injectionModel = injectionModel; | |
48 | this.booleanMode = booleanMode; | |
49 | this.falsy = this.injectionModel.getMediatorVendor().getVendor().instance().getFalsy(); | |
50 | this.truthy = this.injectionModel.getMediatorVendor().getVendor().instance().getTruthy(); | |
51 | } | |
52 | ||
53 | /** | |
54 | * Start one test to verify if boolean works. | |
55 | * @return true if boolean method is confirmed | |
56 | */ | |
57 | public abstract boolean isInjectable() throws StoppedByUserSlidingException; | |
58 | ||
59 | public abstract void initializeNextCharacters( | |
60 | String sqlQuery, | |
61 | List<char[]> bytes, | |
62 | AtomicInteger indexCharacter, | |
63 | CompletionService<T> taskCompletionService, | |
64 | AtomicInteger countTasksSubmitted | |
65 | ); | |
66 | ||
67 | public abstract char[] initializeBinaryMask(List<char[]> bytes, T currentCallable); | |
68 | ||
69 | /** | |
70 | * Display a message to explain how is blind/time working. | |
71 | * @return | |
72 | */ | |
73 | public abstract String getInfoMessage(); | |
74 | ||
75 | /** | |
76 | * Process the whole boolean injection, character by character, bit by bit. | |
77 | * @param sqlQuery SQL query | |
78 | * @param suspendable Action a user can stop | |
79 | * @return Final string: SQLiABCDEF... | |
80 | */ | |
81 | public String inject(String sqlQuery, AbstractSuspendable suspendable) throws StoppedByUserSlidingException { | |
82 | ||
83 | // List of the characters, each one represented by an array of 8 bits | |
84 | // e.g. SQLi: bytes[0] => 01010011:S, bytes[1] => 01010001:Q ... | |
85 | List<char[]> bytes = new ArrayList<>(); | |
86 | | |
87 | // Cursor for current character position | |
88 | var indexCharacter = new AtomicInteger(0); | |
89 | ||
90 | // Concurrent URL requests | |
91 | ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableAbstractBoolean"); | |
92 | | |
93 | CompletionService<T> taskCompletionService = new ExecutorCompletionService<>(taskExecutor); | |
94 | ||
95 | var countTasksSubmitted = new AtomicInteger(0); | |
96 | var countBadAsciiCode = new AtomicInteger(0); | |
97 | ||
98 |
1
1. inject : removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::initializeNextCharacters → NO_COVERAGE |
this.initializeNextCharacters(sqlQuery, bytes, indexCharacter, taskCompletionService, countTasksSubmitted); |
99 | ||
100 | // Process the job until there is no more active task, | |
101 | // in other word until all HTTP requests are done | |
102 |
2
1. inject : changed conditional boundary → NO_COVERAGE 2. inject : negated conditional → NO_COVERAGE |
while (countTasksSubmitted.get() > 0) { |
103 | | |
104 |
1
1. inject : negated conditional → NO_COVERAGE |
if (suspendable.isSuspended()) { |
105 | | |
106 | String result = this.stop(bytes, taskExecutor); | |
107 | throw new StoppedByUserSlidingException(result); | |
108 | } | |
109 | | |
110 | try { | |
111 | // The URL call is done, bring back the finished task | |
112 | var currentCallable = taskCompletionService.take().get(); | |
113 | | |
114 | // One task has just ended, decrease active tasks by 1 | |
115 | countTasksSubmitted.decrementAndGet(); | |
116 | | |
117 | // If SQL result is not empty, then add a new unknown character, | |
118 | // and define a new array of 8 undefined bit. | |
119 | // Then add 8 bits requests for that new character. | |
120 |
1
1. inject : removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::injectCharacter → NO_COVERAGE |
this.injectCharacter(bytes, countTasksSubmitted, countBadAsciiCode, currentCallable); |
121 |
1
1. inject : removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::initializeNextCharacters → NO_COVERAGE |
this.initializeNextCharacters(sqlQuery, bytes, indexCharacter, taskCompletionService, countTasksSubmitted); |
122 | ||
123 | String result = convert(bytes); | |
124 |
1
1. inject : negated conditional → NO_COVERAGE |
if (result.matches("(?s).*"+ DataAccess.TRAIL_RGX +".*")) { |
125 |
1
1. inject : removed call to java/util/concurrent/atomic/AtomicInteger::set → NO_COVERAGE |
countTasksSubmitted.set(0); |
126 | break; | |
127 | } | |
128 | } catch (InterruptedException e) { | |
129 | | |
130 | LOGGER.log(LogLevelUtil.IGNORE, e, e); | |
131 |
1
1. inject : removed call to java/lang/Thread::interrupt → NO_COVERAGE |
Thread.currentThread().interrupt(); |
132 | | |
133 | } catch (ExecutionException e) { | |
134 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e); | |
135 | } catch (InjectionFailureException e) { | |
136 | | |
137 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, e.getMessage()); | |
138 | break; | |
139 | } | |
140 | } | |
141 | ||
142 |
1
1. inject : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::inject → NO_COVERAGE |
return this.stop(bytes, taskExecutor); |
143 | } | |
144 | ||
145 | private static String convert(List<char[]> bytes) { | |
146 | ||
147 | var result = new StringBuilder(); | |
148 | ||
149 | for (char[] c: bytes) { | |
150 | try { | |
151 | var charCode = Integer.parseInt(new String(c), 2); | |
152 | var str = Character.toString((char) charCode); | |
153 | result.append(str); | |
154 | ||
155 | } catch (NumberFormatException err) { | |
156 | // Byte string not fully constructed : 0x1x010x | |
157 | // Ignore | |
158 | } | |
159 | } | |
160 |
1
1. convert : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::convert → NO_COVERAGE |
return result.toString(); |
161 | } | |
162 | ||
163 | private void injectCharacter(List<char[]> bytes, AtomicInteger countTasksSubmitted, AtomicInteger countBadAsciiCode, T currentCallable) throws InjectionFailureException { | |
164 | | |
165 | // Process url that has just checked one bit, convert bits to chars, | |
166 | // and change current bit from undefined to 0 or 1 | |
167 | | |
168 | char[] asciiCodeMask = this.initializeBinaryMask(bytes, currentCallable); | |
169 | | |
170 | var asciiCodeBinary = new String(asciiCodeMask); | |
171 | | |
172 | // Inform the View if bits array is complete, else nothing #Need fix | |
173 |
1
1. injectCharacter : negated conditional → NO_COVERAGE |
if (asciiCodeBinary.matches("^[01]{8}$")) { |
174 | | |
175 | var asciiCode = Integer.parseInt(asciiCodeBinary, 2); | |
176 |
2
1. injectCharacter : negated conditional → NO_COVERAGE 2. injectCharacter : negated conditional → NO_COVERAGE |
if (asciiCode == 127 || asciiCode == 0) { // Stop if many 11111111, 01111111 or 00000000 |
177 | | |
178 |
3
1. injectCharacter : changed conditional boundary → NO_COVERAGE 2. injectCharacter : negated conditional → NO_COVERAGE 3. injectCharacter : negated conditional → NO_COVERAGE |
if (countTasksSubmitted.get() != 0 && countBadAsciiCode.get() > 15) { |
179 | throw new InjectionFailureException("Boolean false positive, stopping..."); | |
180 | } | |
181 | | |
182 | countBadAsciiCode.incrementAndGet(); | |
183 | } | |
184 | ||
185 | currentCallable.charText = Character.toString((char) asciiCode); | |
186 | | |
187 | var interaction = new Request(); | |
188 |
1
1. injectCharacter : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE |
interaction.setMessage(Interaction.MESSAGE_BINARY); |
189 |
1
1. injectCharacter : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE |
interaction.setParameters( |
190 | asciiCodeBinary | |
191 | + "=" | |
192 | + currentCallable.charText | |
193 | .replace("\\n", "\\\\\\n") | |
194 | .replace("\\r", "\\\\\\r") | |
195 | .replace("\\t", "\\\\\\t") | |
196 | ); | |
197 |
1
1. injectCharacter : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE |
this.injectionModel.sendToViews(interaction); |
198 | } | |
199 | } | |
200 | ||
201 | private String stop(List<char[]> bytes, ExecutorService taskExecutor) { | |
202 | | |
203 |
1
1. stop : removed call to com/jsql/util/ThreadUtil::shutdown → NO_COVERAGE |
this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor); |
204 | ||
205 | // Get current progress and display | |
206 | var result = new StringBuilder(); | |
207 | | |
208 | for (char[] c: bytes) { | |
209 | try { | |
210 | var charCode = Integer.parseInt(new String(c), 2); | |
211 | var str = Character.toString((char) charCode); | |
212 | result.append(str); | |
213 | | |
214 | } catch (NumberFormatException err) { | |
215 | // Byte string not fully constructed : 0x1x010x | |
216 | // Ignore | |
217 | } | |
218 | } | |
219 | | |
220 |
1
1. stop : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::stop → NO_COVERAGE |
return result.toString(); |
221 | } | |
222 | ||
223 | /** | |
224 | * Run a HTTP call via the model. | |
225 | * @param urlString URL to inject | |
226 | * @return Source code | |
227 | */ | |
228 | public String callUrl(String urlString, String metadataInjectionProcess) { | |
229 |
1
1. callUrl : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::callUrl → NO_COVERAGE |
return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess); |
230 | } | |
231 | ||
232 | public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBoolean<?> callableBoolean) { | |
233 |
1
1. callUrl : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::callUrl → NO_COVERAGE |
return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean); |
234 | } | |
235 | ||
236 | public BooleanMode getBooleanMode() { | |
237 |
1
1. getBooleanMode : replaced return value with null for com/jsql/model/injection/strategy/blind/AbstractInjectionBoolean::getBooleanMode → NO_COVERAGE |
return this.booleanMode; |
238 | } | |
239 | } | |
Mutations | ||
98 |
1.1 |
|
102 |
1.1 2.2 |
|
104 |
1.1 |
|
120 |
1.1 |
|
121 |
1.1 |
|
124 |
1.1 |
|
125 |
1.1 |
|
131 |
1.1 |
|
142 |
1.1 |
|
160 |
1.1 |
|
173 |
1.1 |
|
176 |
1.1 2.2 |
|
178 |
1.1 2.2 3.3 |
|
188 |
1.1 |
|
189 |
1.1 |
|
197 |
1.1 |
|
203 |
1.1 |
|
220 |
1.1 |
|
229 |
1.1 |
|
233 |
1.1 |
|
237 |
1.1 |