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