View Javadoc
1   package com.jsql.model.injection.method;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.exception.JSqlException;
5   import com.jsql.model.exception.StoppedByUserSlidingException;
6   import com.jsql.util.I18nUtil;
7   import com.jsql.util.JsonUtil;
8   import com.jsql.util.LogLevelUtil;
9   import org.apache.commons.lang3.StringUtils;
10  import org.apache.logging.log4j.LogManager;
11  import org.apache.logging.log4j.Logger;
12  import org.json.JSONException;
13  
14  import java.io.Serializable;
15  import java.util.AbstractMap.SimpleEntry;
16  import java.util.List;
17  import java.util.regex.Pattern;
18  
19  public abstract class AbstractMethodInjection implements Serializable {
20  
21      private static final Logger LOGGER = LogManager.getRootLogger();
22      public static final String LOG_CHECKING = "LOG_CHECKING";
23  
24      protected final InjectionModel injectionModel;
25      
26      protected AbstractMethodInjection(InjectionModel injectionModel) {
27          this.injectionModel = injectionModel;
28      }
29      
30      public abstract boolean isCheckingAllParam();
31      public abstract String getParamsAsString();
32      public abstract List<SimpleEntry<String, String>> getParams();
33      public abstract String name();
34      
35      public boolean testParameters(boolean hasFoundInjection) throws JSqlException {
36          if (!hasFoundInjection) {
37              LOGGER.log(
38                  LogLevelUtil.CONSOLE_DEFAULT,
39                  "{} [{}] params...",
40                  () -> I18nUtil.valueByKey(AbstractMethodInjection.LOG_CHECKING),
41                  () -> this.name().toLowerCase()
42              );
43              return this.testParameters();
44          }
45          return true;
46      }
47  
48      /**
49       * Verify if injection works for specific Method using 3 modes: standard (last param), injection point
50       * and full params injection. Special injections like JSON and SOAP are checked.
51       * @return true if injection didn't fail
52       * @throws JSqlException when no params integrity, process stopped by user, or injection failure
53       */
54      public boolean testParameters() throws JSqlException {
55          this.injectionModel.setAnalysisReport(StringUtils.EMPTY);  // force reset to prevent aggregating report when testing all params
56          var hasFoundInjection = false;
57  
58          // Injects URL, Request or Header params only if user tests every params
59          // or method is selected by user.
60          if (
61              this != this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection()
62              && !this.isCheckingAllParam()
63              || this.getParams().isEmpty()
64          ) {
65              return false;
66          }
67          
68          // Force injection method of model to current running method
69          this.injectionModel.getMediatorUtils().connectionUtil().withMethodInjection(this);
70          
71          // Injection by injection point in params or in path
72          if (
73              this.getParamsAsString().contains(InjectionModel.STAR)
74              || this.injectionModel.getMediatorUtils().connectionUtil().getUrlBase().contains(InjectionModel.STAR)
75          ) {
76              hasFoundInjection = this.checkParamWithStar();
77          } else if (!this.isCheckingAllParam()) {
78              hasFoundInjection = this.checkLastParam();
79          } else {
80              hasFoundInjection = this.checkAllParams();
81          }
82          return hasFoundInjection;
83      }
84  
85      private boolean checkParamWithStar() throws JSqlException {
86          SimpleEntry<String, String> parameterToInject = this.getParams().stream()
87              .filter(entry -> entry.getValue().contains("*") || entry.getKey().contains("*"))  // key when soap
88              .findFirst()
89              .orElse(null);
90          if (parameterToInject != null) {
91              LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking selected param [{}]...", parameterToInject.getKey());
92          }
93          return this.injectionModel.getMediatorStrategy().testStrategies(parameterToInject);
94      }
95  
96      /**
97       *  Default injection: last param tested only
98       */
99      private boolean checkLastParam() throws JSqlException {
100         // Will check param value by user.
101         // Notice options 'Inject each URL params' and 'inject JSON' must be checked both
102         // for JSON injection of last param
103         SimpleEntry<String, String> parameterToInject = this.getParams().stream()
104             .reduce((a, b) -> b)
105             .orElseThrow(() -> new JSqlException("Missing last parameter"));
106         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking default last param [{}]. Use address bar button or preferences to check other params...", parameterToInject.getKey());
107         return this.injectionModel.getMediatorStrategy().testStrategies(parameterToInject);
108     }
109 
110     /**
111      * Injection of every params: isCheckingAllParam() == true.
112      * Params are tested one by one in two loops:
113      * - inner loop erases * from previous param
114      * - outer loop adds * to current param
115      */
116     private boolean checkAllParams() throws StoppedByUserSlidingException {
117         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking all params...");
118         // This param will be marked by * if injection is found,
119         // inner loop will erase mark * otherwise
120         for (SimpleEntry<String, String> paramBase: this.getParams()) {
121             // This param is the current tested one.
122             // For JSON value attributes are traversed one by one to test every value.
123             // For standard value mark * is simply added to the end of its value.
124             for (SimpleEntry<String, String> paramStar: this.getParams()) {
125                 if (paramStar == paramBase) {
126                     try {
127                         if (this.isParamInjectable(paramStar)) {
128                             return true;
129                         }
130                     } catch (JSONException e) {
131                         LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
132                     }
133                 }
134             }
135         }
136         return false;
137     }
138 
139     private boolean isParamInjectable(SimpleEntry<String, String> paramStar) throws StoppedByUserSlidingException {
140         boolean hasFoundInjection;
141         
142         // Will test if current value is a JSON entity
143         Object jsonEntity = JsonUtil.getJson(paramStar.getValue());
144         
145         // Define a tree of JSON attributes with path as the key: root.a => value of a
146         List<SimpleEntry<String, String>> attributesJson = JsonUtil.createEntries(jsonEntity, "root", null);
147         
148         // When option 'Inject JSON' is selected and there's a JSON entity to inject
149         // then loop through each path to add * at the end of value and test each strategy.
150         // Marks * are erased between each test.
151         if (!attributesJson.isEmpty() && this.injectionModel.getMediatorUtils().preferencesUtil().isCheckingAllJsonParam()) {
152             LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} [JSON] params...", () -> I18nUtil.valueByKey(AbstractMethodInjection.LOG_CHECKING));
153             hasFoundInjection = this.injectionModel.getMediatorUtils().jsonUtil().testJsonParam(this, paramStar);
154         } else {
155             hasFoundInjection = this.testJsonlessParam(paramStar);  // Standard non JSON injection
156         }
157         return hasFoundInjection;
158     }
159     
160     public boolean testJsonlessParam(SimpleEntry<String, String> paramStar) throws StoppedByUserSlidingException {
161         var hasFoundInjection = false;
162 
163         paramStar.setValue(paramStar.getValue() + InjectionModel.STAR);
164         
165         try {
166             LOGGER.log(
167                 LogLevelUtil.CONSOLE_INFORM,
168                 "{} {} param [key:{}, value:{}]",
169                 () -> I18nUtil.valueByKey(AbstractMethodInjection.LOG_CHECKING),
170                 () -> this.name().toLowerCase(),
171                 paramStar::getKey,
172                 () -> paramStar.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY)
173             );
174             
175             // Test current standard value marked with * for injection
176             // Keep original param
177             hasFoundInjection = this.injectionModel.getMediatorStrategy().testStrategies(paramStar);
178             
179         } catch (StoppedByUserSlidingException e) { // Break all params processing in upper methods
180             throw e;
181         } catch (JSqlException e) {  // Injection failure
182             LOGGER.log(
183                 LogLevelUtil.CONSOLE_ERROR,
184                 "No {} injection found for parameter {}={} ({})",
185                 this.name(),
186                 paramStar.getKey(),
187                 paramStar.getValue().replaceAll("\\+.?$|\\" + InjectionModel.STAR, StringUtils.EMPTY),
188                 e.getMessage()
189             );
190         } finally {
191             if (!hasFoundInjection) {  // Erase * from JSON if failure
192                 
193                 // Erase * at the end of each params
194                 this.getParams().forEach(e ->
195                     e.setValue(
196                         e.getValue().replaceAll(Pattern.quote(InjectionModel.STAR) +"$", StringUtils.EMPTY)
197                     )
198                 );
199                 
200                 // TODO It erases STAR from value => * can't be used in parameter
201                 paramStar.setValue(paramStar.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY));
202             }
203         }
204         return hasFoundInjection;
205     }
206 }