View Javadoc
1   package com.jsql.model.injection.strategy;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.exception.JSqlException;
5   import com.jsql.model.injection.vendor.model.VendorYaml;
6   import com.jsql.model.suspendable.SuspendableGetCharInsertion;
7   import com.jsql.model.suspendable.SuspendableGetVendor;
8   import com.jsql.util.LogLevelUtil;
9   import com.jsql.util.StringUtil;
10  import org.apache.commons.lang3.StringUtils;
11  import org.apache.logging.log4j.LogManager;
12  import org.apache.logging.log4j.Logger;
13  
14  import java.util.AbstractMap.SimpleEntry;
15  import java.util.Arrays;
16  import java.util.List;
17  import java.util.regex.Matcher;
18  
19  public class MediatorStrategy {
20  
21      private static final Logger LOGGER = LogManager.getRootLogger();
22      
23      private final AbstractStrategy time;
24      private final AbstractStrategy blindBit;
25      private final AbstractStrategy blindBin;
26      private final AbstractStrategy multibit;
27      private final StrategyError error;
28      private final AbstractStrategy union;
29      private final AbstractStrategy stack;
30  
31      private final List<AbstractStrategy> strategies;
32      
33      /**
34       * Current injection strategy.
35       */
36      private AbstractStrategy strategy;
37  
38      private final InjectionModel injectionModel;
39      
40      public MediatorStrategy(InjectionModel injectionModel) {
41          this.injectionModel = injectionModel;
42          
43          this.time = new StrategyTime(this.injectionModel);
44          this.blindBit = new StrategyBlindBit(this.injectionModel);
45          this.blindBin = new StrategyBlindBin(this.injectionModel);
46          this.multibit = new StrategyMultibit(this.injectionModel);
47          this.error = new StrategyError(this.injectionModel);
48          this.union = new StrategyUnion(this.injectionModel);
49          this.stack = new StrategyStack(this.injectionModel);
50  
51          this.strategies = Arrays.asList(this.time, this.blindBin, this.blindBit, this.multibit, this.error, this.stack, this.union);
52      }
53      
54      public String getMeta() {
55          String strategyName = this.strategy == null ? StringUtils.EMPTY : this.strategy.toString().toLowerCase();
56          var strategyMode = "default";
57          if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
58              strategyMode = "dios";
59          } else if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
60              strategyMode = "zip";
61          }
62          return String.format("%s#%s", strategyName.replace(" ", "-"), strategyMode);
63      }
64      
65      /**
66       * Build correct data for GET, POST, HEADER.
67       * Each can be either raw data (no injection), SQL query without index requirement,
68       * or SQL query with index requirement.
69       * @param urlBase Beginning of the request data
70       * @param isUsingIndex False if request doesn't use indexes
71       * @param sqlTrail SQL statement
72       * @return Final data
73       */
74      public String buildPath(String urlBase, boolean isUsingIndex, String sqlTrail) {
75          String result = urlBase;
76          if (urlBase.contains(InjectionModel.STAR)) {
77              if (!isUsingIndex) {
78                  result = urlBase.replace(InjectionModel.STAR, this.encodePath(sqlTrail));
79              } else {
80                  result = urlBase.replace(
81                      InjectionModel.STAR,
82                      this.encodePath(
83                          this.injectionModel.getIndexesInUrl().replaceAll(
84                              String.format(VendorYaml.FORMAT_INDEX, this.getSpecificUnion().getVisibleIndex()),
85                              Matcher.quoteReplacement(sqlTrail)  // Oracle column can contain regex char $ => quoteReplacement()
86                          )
87                      )
88                  );
89              }
90          }
91          return result;
92      }
93  
94      private String encodePath(String sqlTrail) {
95          String sqlTrailEncoded = StringUtil.cleanSql(sqlTrail);
96  
97          if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isUrlEncodingDisabled()) {
98              sqlTrailEncoded = sqlTrailEncoded
99                  .replace("'", "%27")
100                 .replace("(", "%28")
101                 .replace(")", "%29")
102                 .replace("{", "%7b")
103                 .replace("[", "%5b")
104                 .replace("]", "%5d")
105                 .replace("}", "%7d")
106                 .replace(">", "%3e")
107                 .replace("<", "%3c")
108                 .replace("?", "%3f")
109                 .replace("_", "%5f")
110                 .replace("\\", "%5c")
111                 .replace(",", "%2c");
112         }
113 
114         // URL forbidden characters
115         return (sqlTrailEncoded + this.injectionModel.getMediatorVendor().getVendor().instance().endingComment())
116             .replace("\"", "%22")
117             .replace("|", "%7c")
118             .replace("`", "%60")
119             .replace(StringUtils.SPACE, "%20")
120             .replace("+", "%20");
121     }
122     
123     /**
124      * Find the insertion character, test each strategy, inject metadata and list databases.
125      * @param parameterToInject to be tested, null when injection point
126      * @return true when successful injection
127      * @throws JSqlException when no params integrity, process stopped by user, or injection failure
128      */
129     public boolean testStrategies(SimpleEntry<String, String> parameterToInject) throws JSqlException {
130         // Define insertionCharacter, i.e, -1 in "[..].php?id=-1 union select[..]",
131         
132         String parameterOriginalValue = null;
133         
134         // Fingerprint database
135         this.injectionModel.getMediatorVendor().setVendor(this.injectionModel.getMediatorVendor().fingerprintVendor());
136         
137         // If not an injection point then find insertion character.
138         // Force to 1 if no insertion char works and empty value from user,
139         // Force to user's value if no insertion char works,
140         // Force to insertion char otherwise.
141         // parameterToInject null on true STAR injection
142         // TODO Use also on Json injection where parameter == null
143         if (parameterToInject != null) {
144             parameterOriginalValue = parameterToInject.getValue();
145                      
146             // Test for params integrity
147             String characterInsertionByUser = this.injectionModel.getMediatorUtils().getParameterUtil().initStar(parameterToInject);
148             
149             String characterInsertion = this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotSearchingCharInsertion()
150                 ? characterInsertionByUser
151                 : new SuspendableGetCharInsertion(this.injectionModel).run(characterInsertionByUser);
152             if (characterInsertion.contains(InjectionModel.STAR)) {  // When injecting all parameters or JSON
153                 parameterToInject.setValue(characterInsertion);
154             } else {  // When injecting last parameter
155                 parameterToInject.setValue(characterInsertion.replaceAll("(\\w)$", "$1+") + InjectionModel.STAR);
156             }
157         } else if (this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)) {
158             String characterInsertion = new SuspendableGetCharInsertion(this.injectionModel).run(StringUtils.EMPTY);
159             String urlBase = this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase();
160             this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(
161                 // Space %20 for URL, do not use +
162                 urlBase.replace(InjectionModel.STAR, characterInsertion.replaceAll("(\\w)$", "$1%20") + InjectionModel.STAR)
163             );
164         }
165 
166         if (this.injectionModel.getMediatorVendor().getVendorByUser() == this.injectionModel.getMediatorVendor().getAuto()) {
167             new SuspendableGetVendor(this.injectionModel).run();
168         }
169 
170         // Test each injection strategies: time < blind binary < blind bitwise < multibit < error < stack < union
171         this.time.checkApplicability();
172         this.blindBin.checkApplicability();
173         this.blindBit.checkApplicability();
174 
175         if (parameterToInject != null) {
176             // Multibit requires '0'
177             // TODO char insertion 0' should also work on "where x='$param'"
178             var backupCharacterInsertion = parameterToInject.getValue();
179             parameterToInject.setValue(InjectionModel.STAR);
180             this.multibit.checkApplicability();
181             parameterToInject.setValue(backupCharacterInsertion);
182         } else {
183             this.multibit.checkApplicability();
184         }
185 
186         this.error.checkApplicability();
187         this.stack.checkApplicability();
188         this.union.checkApplicability();
189 
190         // Set most efficient strategy first: union > stack > error > multibit > blind bitwise > blind binary > time
191         this.union.activateWhenApplicable();
192         this.stack.activateWhenApplicable();
193         this.error.activateWhenApplicable();
194         this.multibit.activateWhenApplicable();
195         this.blindBit.activateWhenApplicable();
196         this.blindBin.activateWhenApplicable();
197         this.time.activateWhenApplicable();
198 
199         if (this.injectionModel.getMediatorStrategy().getStrategy() == null) {  // no strategy found
200             // Restore initial parameter value on injection failure
201             // Only when not true STAR injection
202             if (parameterOriginalValue != null) {
203                 parameterToInject.setValue(parameterOriginalValue.replace(InjectionModel.STAR, StringUtils.EMPTY));
204             }
205 
206             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "No injection found");
207             return false;
208         }
209         
210         return true;
211     }
212     
213     
214     // Getter and setter
215 
216     public AbstractStrategy getUnion() {
217         return this.union;
218     }
219 
220     public StrategyUnion getSpecificUnion() {
221         return (StrategyUnion) this.union;
222     }
223 
224     public StrategyError getError() {
225         return this.error;
226     }
227 
228     public AbstractStrategy getBlindBit() {
229         return this.blindBit;
230     }
231 
232     public AbstractStrategy getBlindBin() {
233         return this.blindBin;
234     }
235 
236     public AbstractStrategy getMultibit() {
237         return this.multibit;
238     }
239 
240     public AbstractStrategy getTime() {
241         return this.time;
242     }
243 
244     public AbstractStrategy getStack() {
245         return this.stack;
246     }
247 
248     public List<AbstractStrategy> getStrategies() {
249         return this.strategies;
250     }
251 
252     public AbstractStrategy getStrategy() {
253         return this.strategy;
254     }
255 
256     public void setStrategy(AbstractStrategy strategy) {
257         this.strategy = strategy;
258     }
259 }