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