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