View Javadoc
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          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          while (countTasksSubmitted.get() > 0) {
86              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                  if (isComplete || currentCallable.isBinary()) {  // prevents bitwise overload new char init on each bit
99                      this.initNextChar(sqlQuery, bytes, indexChar, taskCompletionService, countTasksSubmitted, countBadAsciiCode, currentCallable);
100                 }
101 
102                 String result = AbstractInjectionBit.convert(bytes);
103                 if (result.matches("(?s).*"+ DataAccess.TRAIL_RGX +".*")) {
104                     countTasksSubmitted.set(0);
105                     break;
106                 }
107             } catch (InterruptedException e) {
108                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
109                 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         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         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         if (asciiCodeBit.matches("^[01]{8}$")) {
147             var asciiCode = Integer.parseInt(asciiCodeBit, 2);
148             if (asciiCode == 127 || asciiCode == 0) {  // Stop if many 11111111, 01111111 or 00000000
149                 if (countBadAsciiCode.get() > 15) {
150                     throw new InjectionFailureException("Boolean false positive, stopping...");
151                 }
152                 countBadAsciiCode.incrementAndGet();
153             }
154 
155             currentCallable.setCharText(Character.toString((char) asciiCode));
156             
157             var interaction = new Request();
158             interaction.setMessage(Interaction.MESSAGE_BINARY);
159             interaction.setParameters(
160                 asciiCodeBit
161                 + "="
162                 + currentCallable.getCharText()
163                 .replace("\n", "\\n")
164                 .replace("\r", "\\r")
165                 .replace("\t", "\\t")
166             );
167             this.injectionModel.sendToViews(interaction);
168             isComplete = true;
169         }
170         return isComplete;
171     }
172 
173     private String stop(List<char[]> bytes, ExecutorService taskExecutor) {
174         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         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         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
199     }
200 
201     public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBit<?> callableBoolean) {
202         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
203     }
204 
205     public BlindOperator getBlindOperator() {
206         return this.blindOperator;
207     }
208 
209     protected static char[] getBitsUnset() {
210         return new char[]{ '0', 'x', 'x', 'x', 'x', 'x', 'x', 'x' };
211     }
212 }