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