1 | package com.jsql.model.injection.strategy.blind; | |
2 | ||
3 | import com.jsql.model.InjectionModel; | |
4 | import com.jsql.model.exception.InjectionFailureException; | |
5 | import com.jsql.model.exception.StoppedByUserSlidingException; | |
6 | import com.jsql.model.injection.strategy.blind.callable.CallableBlindBin; | |
7 | import com.jsql.util.LogLevelUtil; | |
8 | import org.apache.commons.lang3.StringUtils; | |
9 | import org.apache.logging.log4j.LogManager; | |
10 | import org.apache.logging.log4j.Logger; | |
11 | ||
12 | import java.util.ArrayList; | |
13 | import java.util.Collection; | |
14 | import java.util.List; | |
15 | import java.util.concurrent.CompletionService; | |
16 | import java.util.concurrent.ExecutionException; | |
17 | import java.util.concurrent.ExecutorService; | |
18 | import java.util.concurrent.Future; | |
19 | import java.util.concurrent.atomic.AtomicInteger; | |
20 | ||
21 | import static name.fraser.neil.plaintext.diff_match_patch.Diff; | |
22 | ||
23 | /** | |
24 | * A blind attack class using concurrent threads. | |
25 | */ | |
26 | public class InjectionBlindBin extends AbstractInjectionMonobit<CallableBlindBin> { | |
27 | ||
28 | /** | |
29 | * Log4j logger sent to view. | |
30 | */ | |
31 | private static final Logger LOGGER = LogManager.getRootLogger(); | |
32 | private static final int LOW = 0; | |
33 | private static final int HIGH = 127; | |
34 | ||
35 | // Source code of the TRUE web page (usually ?id=1) | |
36 | private String sourceReferencePage; | |
37 | ||
38 | /** | |
39 | * List of string differences found in all the FALSE queries, compared | |
40 | * to the reference page. Each FALSE pages should contain | |
41 | * at least one same string, which shouldn't be present in all | |
42 | * the TRUE queries. | |
43 | */ | |
44 | private List<Diff> falseDiffs = new ArrayList<>(); | |
45 | ||
46 | /** | |
47 | * Create blind attack initialization. | |
48 | * If every false diffs are not in true diffs and every true diffs are in | |
49 | * true diffs, then Blind attack is confirmed. | |
50 | */ | |
51 | public InjectionBlindBin(InjectionModel injectionModel, BlindOperator blindMode) { | |
52 | super(injectionModel, blindMode); | |
53 | | |
54 | // No blind | |
55 |
2
1. <init> : negated conditional → NO_COVERAGE 2. <init> : negated conditional → NO_COVERAGE |
if (this.falsyBin.isEmpty() || this.injectionModel.isStoppedByUser()) { |
56 | return; | |
57 | } | |
58 | | |
59 | // Call the SQL request which must be TRUE (usually ?id=1) | |
60 | this.sourceReferencePage = this.callUrl(StringUtils.EMPTY, "bin#ref"); | |
61 | ||
62 | // Concurrent calls to the FALSE statements, | |
63 | // it will use inject() from the model | |
64 | ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindBinTagFalse"); | |
65 | Collection<CallableBlindBin> callablesFalseTest = new ArrayList<>(); | |
66 | for (String falseTest: this.falsyBin) { | |
67 | callablesFalseTest.add(new CallableBlindBin( | |
68 | falseTest, | |
69 | injectionModel, | |
70 | this, | |
71 | blindMode, | |
72 | -1, -1, -1, | |
73 | "bin#falsy" | |
74 | )); | |
75 | } | |
76 | | |
77 | // Delete junk from the results of FALSE statements, | |
78 | // keep only diffs found in each and every FALSE pages. | |
79 | // Allow the user to stop the loop | |
80 | try { | |
81 | List<Future<CallableBlindBin>> futuresFalseTest = taskExecutor.invokeAll(callablesFalseTest); | |
82 |
1
1. <init> : removed call to com/jsql/util/ThreadUtil::shutdown → NO_COVERAGE |
this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor); |
83 | for (Future<CallableBlindBin> futureFalseTest: futuresFalseTest) { | |
84 |
1
1. <init> : negated conditional → NO_COVERAGE |
if (this.injectionModel.isStoppedByUser()) { |
85 | return; | |
86 | } | |
87 |
1
1. <init> : negated conditional → NO_COVERAGE |
if (this.falseDiffs.isEmpty()) { |
88 | this.falseDiffs = futureFalseTest.get().getDiffsWithReference(); // Init diffs | |
89 | } else { | |
90 | this.falseDiffs.retainAll(futureFalseTest.get().getDiffsWithReference()); // Clean un-matching diffs | |
91 | } | |
92 | } | |
93 | } catch (ExecutionException e) { | |
94 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e); | |
95 | } catch (InterruptedException e) { | |
96 | LOGGER.log(LogLevelUtil.IGNORE, e, e); | |
97 |
1
1. <init> : removed call to java/lang/Thread::interrupt → NO_COVERAGE |
Thread.currentThread().interrupt(); |
98 | } | |
99 | ||
100 |
1
1. <init> : negated conditional → NO_COVERAGE |
if (this.injectionModel.isStoppedByUser()) { |
101 | return; | |
102 | } | |
103 | | |
104 |
1
1. <init> : removed call to com/jsql/model/injection/strategy/blind/InjectionBlindBin::cleanTrueDiffs → NO_COVERAGE |
this.cleanTrueDiffs(injectionModel, blindMode); |
105 | } | |
106 | ||
107 | private void cleanTrueDiffs(InjectionModel injectionModel, BlindOperator blindMode) { | |
108 | // Concurrent calls to the TRUE statements, | |
109 | // it will use inject() from the model. | |
110 | ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindBinTagTrue"); | |
111 | Collection<CallableBlindBin> callablesTrueTest = new ArrayList<>(); | |
112 | for (String trueTest: this.truthyBin) { | |
113 | callablesTrueTest.add(new CallableBlindBin( | |
114 | trueTest, | |
115 | injectionModel, | |
116 | this, | |
117 | blindMode, | |
118 | -1, -1, -1, | |
119 | "bin#truthy" | |
120 | )); | |
121 | } | |
122 | | |
123 | // Remove TRUE diffs in the FALSE diffs, because | |
124 | // a significant FALSE statement shouldn't contain any TRUE diff. | |
125 | // Allow the user to stop the loop. | |
126 | try { | |
127 | List<Future<CallableBlindBin>> futuresTrueTest = taskExecutor.invokeAll(callablesTrueTest); | |
128 |
1
1. cleanTrueDiffs : removed call to com/jsql/util/ThreadUtil::shutdown → NO_COVERAGE |
this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor); |
129 | for (Future<CallableBlindBin> futureTrueTest: futuresTrueTest) { | |
130 |
1
1. cleanTrueDiffs : negated conditional → NO_COVERAGE |
if (this.injectionModel.isStoppedByUser()) { |
131 | return; | |
132 | } | |
133 | this.falseDiffs.removeAll(futureTrueTest.get().getDiffsWithReference()); | |
134 | } | |
135 | } catch (ExecutionException e) { | |
136 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e); | |
137 | } catch (InterruptedException e) { | |
138 | LOGGER.log(LogLevelUtil.IGNORE, e, e); | |
139 |
1
1. cleanTrueDiffs : removed call to java/lang/Thread::interrupt → NO_COVERAGE |
Thread.currentThread().interrupt(); |
140 | } | |
141 | } | |
142 | ||
143 | @Override | |
144 | public CallableBlindBin getCallableBitTest(String sqlQuery, int indexChar, int bit) { | |
145 | return null; // unused | |
146 | } | |
147 | ||
148 | @Override | |
149 | public boolean isInjectable() throws StoppedByUserSlidingException { | |
150 |
1
1. isInjectable : negated conditional → NO_COVERAGE |
if (this.injectionModel.isStoppedByUser()) { |
151 | throw new StoppedByUserSlidingException(); | |
152 | } | |
153 | var blindTest = new CallableBlindBin( | |
154 | this.injectionModel.getMediatorVendor().getVendor().instance().sqlBlindConfirm(), | |
155 | this.injectionModel, | |
156 | this, | |
157 | this.blindOperator, | |
158 | -1, -1, -1, | |
159 | "bin#confirm" | |
160 | ); | |
161 | try { | |
162 | blindTest.call(); | |
163 | } catch (Exception e) { | |
164 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e); | |
165 | } | |
166 |
3
1. isInjectable : negated conditional → NO_COVERAGE 2. isInjectable : negated conditional → NO_COVERAGE 3. isInjectable : replaced boolean return with true for com/jsql/model/injection/strategy/blind/InjectionBlindBin::isInjectable → NO_COVERAGE |
return blindTest.isTrue() && !this.falseDiffs.isEmpty(); |
167 | } | |
168 | ||
169 | @Override | |
170 | public void initNextChar( | |
171 | String sqlQuery, | |
172 | List<char[]> bytes, | |
173 | AtomicInteger indexChar, | |
174 | CompletionService<CallableBlindBin> taskCompletionService, | |
175 | AtomicInteger countTasksSubmitted, | |
176 | CallableBlindBin currentCallable | |
177 | ) { | |
178 | int low; | |
179 | int mid; | |
180 | int high; | |
181 | ||
182 |
1
1. initNextChar : negated conditional → NO_COVERAGE |
if (currentCallable != null) { |
183 | low = currentCallable.getLow(); | |
184 | mid = currentCallable.getMid(); | |
185 | high = currentCallable.getHigh(); | |
186 | ||
187 |
2
1. initNextChar : changed conditional boundary → NO_COVERAGE 2. initNextChar : negated conditional → NO_COVERAGE |
if (low >= high) { |
188 |
1
1. initNextChar : negated conditional → NO_COVERAGE |
if (low == high) { // n-1 >= n |
189 |
2
1. initNextChar : negated conditional → NO_COVERAGE 2. initNextChar : Replaced integer subtraction with addition → NO_COVERAGE |
low = currentCallable.isTrue() ? low : low - 1; |
190 | } else { | |
191 |
1
1. initNextChar : negated conditional → NO_COVERAGE |
low = currentCallable.isTrue() ? low : high; |
192 | } | |
193 |
1
1. initNextChar : Replaced integer subtraction with addition → NO_COVERAGE |
char[] asciiCodeMask = bytes.get(currentCallable.getCurrentIndex() - 1); // bits for current url |
194 |
1
1. initNextChar : removed call to com/jsql/model/injection/strategy/blind/InjectionBlindBin::setAsciiCodeMask → NO_COVERAGE |
this.setAsciiCodeMask(asciiCodeMask, low); |
195 | ||
196 | try { | |
197 | AtomicInteger countBadAsciiCode = new AtomicInteger(); // todo unused | |
198 | this.injectCharacter(bytes, countTasksSubmitted, countBadAsciiCode, currentCallable); | |
199 | } catch (InjectionFailureException e) { | |
200 | return; | |
201 | } | |
202 | ||
203 | bytes.add(AbstractInjectionBit.getBitsUnset()); | |
204 | indexChar.incrementAndGet(); | |
205 | low = InjectionBlindBin.LOW; | |
206 | high = InjectionBlindBin.HIGH; | |
207 |
1
1. initNextChar : negated conditional → NO_COVERAGE |
} else if (currentCallable.isTrue()) { // key < mid |
208 |
1
1. initNextChar : Replaced integer addition with subtraction → NO_COVERAGE |
low = mid + 1; |
209 | } else { // key > mid | |
210 |
1
1. initNextChar : Replaced integer subtraction with addition → NO_COVERAGE |
high = mid - 1; |
211 | } | |
212 |
3
1. initNextChar : Replaced integer division with multiplication → NO_COVERAGE 2. initNextChar : Replaced integer addition with subtraction → NO_COVERAGE 3. initNextChar : Replaced integer subtraction with addition → NO_COVERAGE |
mid = low + (high - low) / 2; |
213 | } else { | |
214 | low = InjectionBlindBin.LOW; | |
215 | mid = InjectionBlindBin.LOW + (InjectionBlindBin.HIGH - InjectionBlindBin.LOW) / 2; | |
216 | high = InjectionBlindBin.HIGH; | |
217 | bytes.add(AbstractInjectionBit.getBitsUnset()); | |
218 | indexChar.incrementAndGet(); | |
219 | } | |
220 | ||
221 | taskCompletionService.submit( | |
222 | new CallableBlindBin( | |
223 | sqlQuery, | |
224 | indexChar.get(), | |
225 | this.injectionModel, | |
226 | this, | |
227 | this.blindOperator, | |
228 | low, mid, high, | |
229 | "bin#" + indexChar + "~" + mid | |
230 | ) | |
231 | ); | |
232 | countTasksSubmitted.addAndGet(1); | |
233 | } | |
234 | ||
235 | private void setAsciiCodeMask(char[] asciiCodeMask, int value) { | |
236 | String binary = StringUtils.leftPad(Integer.toBinaryString((char) value), 8, "0"); | |
237 |
2
1. setAsciiCodeMask : negated conditional → NO_COVERAGE 2. setAsciiCodeMask : changed conditional boundary → NO_COVERAGE |
for (int i = 0; i <= 7; i++) { |
238 | asciiCodeMask[i] = binary.charAt(i); | |
239 | } | |
240 | } | |
241 | ||
242 | @Override | |
243 | public char[] initMaskAsciiChar(List<char[]> bytes, CallableBlindBin currentCallable) { | |
244 |
2
1. initMaskAsciiChar : Replaced integer subtraction with addition → NO_COVERAGE 2. initMaskAsciiChar : replaced return value with null for com/jsql/model/injection/strategy/blind/InjectionBlindBin::initMaskAsciiChar → NO_COVERAGE |
return bytes.get(currentCallable.getCurrentIndex() - 1); |
245 | } | |
246 | ||
247 | @Override | |
248 | public String getInfoMessage() { | |
249 |
1
1. getInfoMessage : replaced return value with "" for com/jsql/model/injection/strategy/blind/InjectionBlindBin::getInfoMessage → NO_COVERAGE |
return "- Strategy Blind bin: query True when Diffs are matching " + this.falseDiffs + "\n\n"; |
250 | } | |
251 | | |
252 | | |
253 | // Getter and setter | |
254 | ||
255 | public String getSourceReferencePage() { | |
256 |
1
1. getSourceReferencePage : replaced return value with "" for com/jsql/model/injection/strategy/blind/InjectionBlindBin::getSourceReferencePage → NO_COVERAGE |
return this.sourceReferencePage; |
257 | } | |
258 | | |
259 | public List<Diff> getFalseDiffs() { | |
260 |
1
1. getFalseDiffs : replaced return value with Collections.emptyList for com/jsql/model/injection/strategy/blind/InjectionBlindBin::getFalseDiffs → NO_COVERAGE |
return this.falseDiffs; |
261 | } | |
262 | } | |
Mutations | ||
55 |
1.1 2.2 |
|
82 |
1.1 |
|
84 |
1.1 |
|
87 |
1.1 |
|
97 |
1.1 |
|
100 |
1.1 |
|
104 |
1.1 |
|
128 |
1.1 |
|
130 |
1.1 |
|
139 |
1.1 |
|
150 |
1.1 |
|
166 |
1.1 2.2 3.3 |
|
182 |
1.1 |
|
187 |
1.1 2.2 |
|
188 |
1.1 |
|
189 |
1.1 2.2 |
|
191 |
1.1 |
|
193 |
1.1 |
|
194 |
1.1 |
|
207 |
1.1 |
|
208 |
1.1 |
|
210 |
1.1 |
|
212 |
1.1 2.2 3.3 |
|
237 |
1.1 2.2 |
|
244 |
1.1 2.2 |
|
249 |
1.1 |
|
256 |
1.1 |
|
260 |
1.1 |