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      /**
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          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         while (countTasksSubmitted.get() > 0) {
102             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                 if (isComplete || currentCallable.isBinary()) {  // prevents bitwise overload new char init on each bit
115                     this.initNextChar(sqlQuery, bytes, indexChar, taskCompletionService, countTasksSubmitted, currentCallable);
116                 }
117 
118                 String result = AbstractInjectionBit.convert(bytes);
119                 if (result.matches("(?s).*"+ DataAccess.TRAIL_RGX +".*")) {
120                     countTasksSubmitted.set(0);
121                     break;
122                 }
123             } catch (InterruptedException e) {
124                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
125                 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         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         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         if (asciiCodeBit.matches("^[01]{8}$")) {
159             var asciiCode = Integer.parseInt(asciiCodeBit, 2);
160             if (asciiCode == 127 || asciiCode == 0) {  // Stop if many 11111111, 01111111 or 00000000
161                 if (countTasksSubmitted.get() != 0 && countBadAsciiCode.get() > 15) {
162                     throw new InjectionFailureException("Boolean false positive, stopping...");
163                 }
164                 countBadAsciiCode.incrementAndGet();
165             }
166 
167             currentCallable.setCharText(Character.toString((char) asciiCode));
168             
169             var interaction = new Request();
170             interaction.setMessage(Interaction.MESSAGE_BINARY);
171             interaction.setParameters(
172                 asciiCodeBit
173                 + "="
174                 + currentCallable.getCharText()
175                 .replace("\\n", "\\\\\\n")
176                 .replace("\\r", "\\\\\\r")
177                 .replace("\\t", "\\\\\\t")
178             );
179             this.injectionModel.sendToViews(interaction);
180             isComplete = true;
181         }
182         return isComplete;
183     }
184 
185     private String stop(List<char[]> bytes, ExecutorService taskExecutor) {
186         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         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         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
211     }
212 
213     public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBit<?> callableBoolean) {
214         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
215     }
216 
217     public BlindOperator getBooleanMode() {
218         return this.blindOperator;
219     }
220 
221     protected static char[] getBitsUnset() {
222         return new char[]{ '0', 'x', 'x', 'x', 'x', 'x', 'x', 'x' };
223     }
224 }