Package org.parosproxy.paros.network

Examples of org.parosproxy.paros.network.HttpMessage


      btnSend.setText("Send");
      btnSend.setEnabled(isSendEnabled);
      btnSend.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent e) {
                    btnSend.setEnabled(false);
              HttpMessage msg = new HttpMessage();
              getRequestPanel().getMessage(msg, true);
              msg.getRequestHeader().setContentLength(msg.getRequestBody().length());
              send(msg);
           
        }
      });
    }
View Full Code Here


                displayQueue.remove(0);
            }
           
            try {
                final HistoryReference finalRef = ref;
                final HttpMessage msg = ref.getHttpMessage();
                EventQueue.invokeAndWait(new Runnable() {
                    public void run() {
                        displayMessage(msg);
                        checkAndShowBrowser(finalRef, msg);
                        listLog.requestFocus();
View Full Code Here

          public void actionPerformed(java.awt.event.ActionEvent e) {
             
              JList listLog = extension.getLogPanel().getListLog();
              HistoryReference ref = (HistoryReference) listLog.getSelectedValue();
              HttpMessage msg = null;
              try {
                    msg = ref.getHttpMessage();
                    JTextField[] inputs = new JTextField[1];
                    inputs[0] = new JTextField(msg.getTag());
                    String tag = JOptionPane.showInputDialog(extension.getView().getMainFrame(), "Tag this message as:", msg.getTag());
                    if (tag != null) {
                        ref.setTag(tag);
                        extension.getHistoryList().notifyItemChanged(ref);
                    }
                } catch (HttpMalformedHeaderException e1) {
View Full Code Here

             
              ManualRequestEditorDialog dialog = extension.getResendDialog();
             
              JList listLog = extension.getLogPanel().getListLog();
              HistoryReference ref = (HistoryReference) listLog.getSelectedValue();
              HttpMessage msg = null;
              try {
                    msg = ref.getHttpMessage().cloneRequest();
                    dialog.setMessage(msg);
                    dialog.setVisible(true);
                } catch (HttpMalformedHeaderException e1) {
View Full Code Here

        @Override
        public void actionPerformed(ActionEvent evt) {
          WebSocketChannelDTO channel = (WebSocketChannelDTO) channelSelect.getSelectedItem();
          HistoryReference handshakeRef = channel.getHandshakeReference();
          if (handshakeRef != null) {
            HttpMessage msg;
            try {
                            msg = handshakeRef.getHttpMessage();
                        } catch (Exception e) {
                          logger.warn(e.getMessage(), e);
                            return;
View Full Code Here

  @Override
  public void scan() {
     
      boolean result = false;
      HttpMessage msg = getNewMsg();
      int reliability = Alert.WARNING;
      StringBuilder evidence = new StringBuilder();
     
      try {
            checkIfDirectory(msg);
            writeProgress(msg.getRequestHeader().getURI().toString());
        sendAndReceive(msg);

        if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) {
          return;
        }
       
        if (matchBodyPattern(msg, patternIIS, evidence)) {
          result = true;
        } else if (matchBodyPattern(msg, patternApache, evidence)) {
          result = true;
        } else if (matchBodyPattern(msg, patternGeneralParent, evidence)) {
          result = true;
          reliability = Alert.SUSPICIOUS;
        } else if (matchBodyPattern(msg, patternGeneralDir1, evidence)) {
          // Dont append the second matching pattern to the evidence as they will be in different places
          if (matchBodyPattern(msg, patternGeneralDir2, null)) {
            result = true;
            reliability = Alert.SUSPICIOUS;
          }
        }


        } catch (IOException e) {
        }
   
    if (result) {
            bingo(Alert.RISK_MEDIUM, reliability, msg.getRequestHeader().getURI().toString(), "", evidence.toString(), "", msg);
    }
  }
View Full Code Here

    //Note: the "value" we are passed here is escaped. we need to unescape it before handling it.
    //as soon as we find a single SQL injection on the url, skip out. Do not look for SQL injection on a subsequent parameter on the same URL
    //for performance reasons.
    boolean sqlInjectionFoundForUrl = false;
    String sqlInjectionAttack = null;
    HttpMessage refreshedmessage = null;
    String mResBodyNormalUnstripped = null;
    String mResBodyNormalStripped = null;

    try {
      //reinitialise the count for each type of request, for each parameter.  We will be sticking to limits defined in the attach strength logic
      int countErrorBasedRequests = 0;
      int countExpressionBasedRequests = 0;
      int countBooleanBasedRequests = 0;
      int countUnionBasedRequests = 0;
      int countOrderByBasedRequests = 0;
      //int countStackedBasedRequests = 0;  //TODO: use in the stacked based queries implementation

      //Check 1: Check for Error Based SQL Injection (actual error messages).
      //for each SQL metacharacter combination to try
      for (int sqlErrorStringIndex = 0;
          sqlErrorStringIndex < SQL_CHECK_ERR.length && !sqlInjectionFoundForUrl && doSpecificErrorBased && countErrorBasedRequests < doErrorMaxRequests;
          sqlErrorStringIndex++) {

        //work through the attack using each of the following strings as a prefix: the empty string, and the original value
        //Note: this doubles the amount of work done by the scanner, but is necessary in some cases
        String[] prefixStrings;
        if (origParamValue != null) {
          //ZAP: Removed getURLDecode()
          prefixStrings = new String[]{"", origParamValue};
        } else {
          prefixStrings = new String[]{""};
        }
        for (int prefixIndex = 0; prefixIndex < prefixStrings.length && !sqlInjectionFoundForUrl; prefixIndex++) {

          //new message for each value we attack with
          HttpMessage msg1 = getNewMsg();
          String sqlErrValue = prefixStrings[prefixIndex] + SQL_CHECK_ERR[sqlErrorStringIndex];
          setParameter(msg1, param, sqlErrValue);

          //System.out.println("Attacking [" + msg + "], parameter [" + param + "] with value ["+ sqlErrValue + "]");

          //send the message with the modified parameters
          sendAndReceive(msg1);
          countErrorBasedRequests++;

          //now check the results against each pattern in turn, to try to identify a database, or even better: a specific database.
          //Note: do NOT check the HTTP error code just yet, as the result could come back with one of various codes.
          Iterator<Pattern> errorPatternIterator = SQL_ERROR_TO_SPECIFIC_DBMS.keySet().iterator();

          while (errorPatternIterator.hasNext() && !sqlInjectionFoundForUrl) {
            Pattern errorPattern = errorPatternIterator.next();
            String errorPatternRDBMS = SQL_ERROR_TO_SPECIFIC_DBMS.get(errorPattern);

            //if the "error message" occurs in the result of sending the modified query, but did NOT occur in the original result of the original query
            //then we may may have a SQL Injection vulnerability
            StringBuilder sb = new StringBuilder();
            if (!matchBodyPattern(getBaseMsg(), errorPattern, null) && matchBodyPattern(msg1, errorPattern, sb)) {
              //Likely a SQL Injection. Raise it
              String extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.errorbased.extrainfo", errorPatternRDBMS, errorPattern.toString());
              //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
              sqlInjectionAttack = sqlErrValue;
              bingo(Alert.RISK_HIGH, Alert.WARNING, getName() + " - " + errorPatternRDBMS, getDescription(),
                  null,
                  param, sqlInjectionAttack,
                  extraInfo, getSolution(), sb.toString(), msg1);

              //log it, as the RDBMS may be useful to know later (in subsequent checks, when we need to determine RDBMS specific behaviour, for instance)
              getKb().add(getBaseMsg().getRequestHeader().getURI(), "sql/" + errorPatternRDBMS, Boolean.TRUE);

              sqlInjectionFoundForUrl = true;
              continue;
            }
            //bale out if we were asked nicely
            if (isStop()) {
              log.debug("Stopping the scan due to a user request");
              return;
            }
          } //end of the loop to check for RDBMS specific error messages
         
          if (this.doGenericErrorBased && !sqlInjectionFoundForUrl) {
            errorPatternIterator = SQL_ERROR_TO_GENERIC_DBMS.keySet().iterator();

            while (errorPatternIterator.hasNext() && !sqlInjectionFoundForUrl) {
              Pattern errorPattern = errorPatternIterator.next();
              String errorPatternRDBMS = SQL_ERROR_TO_GENERIC_DBMS.get(errorPattern);

              //if the "error message" occurs in the result of sending the modified query, but did NOT occur in the original result of the original query
              //then we may may have a SQL Injection vulnerability
              StringBuilder sb = new StringBuilder();
              if (!matchBodyPattern(getBaseMsg(), errorPattern, null) && matchBodyPattern(msg1, errorPattern, sb)) {
                //Likely a SQL Injection. Raise it
                String extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.errorbased.extrainfo", errorPatternRDBMS, errorPattern.toString());
                //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
                sqlInjectionAttack = sqlErrValue;
                bingo(Alert.RISK_HIGH, Alert.WARNING, getName() + " - " + errorPatternRDBMS, getDescription(),
                    null,
                    param, sqlInjectionAttack,
                    extraInfo, getSolution(), sb.toString(), msg1);

                //log it, as the RDBMS may be useful to know later (in subsequent checks, when we need to determine RDBMS specific behaviour, for instance)
                getKb().add(getBaseMsg().getRequestHeader().getURI(), "sql/" + errorPatternRDBMS, Boolean.TRUE);

                sqlInjectionFoundForUrl = true;
                continue;
              }
              //bale out if we were asked nicely
              if (isStop()) {
                log.debug("Stopping the scan due to a user request");
                return;
              }
            } //end of the loop to check for RDBMS specific error messages
           
          }

        }  //for each of the SQL_CHECK_ERR values (SQL metacharacters)
      }

      //###############################
      //Check 4     
      //New!  I haven't seen this technique documented anywhere else, but it's dead simple. Let me explain.
      //See if the parameter value can simply be changed to one that *evaluates* to be the same value,
      //if evaluated on a database
      //the simple check is to see if parameter "1" gives the same results as for param "2-1", and different results for param "2-2"
      //for now, we try this for integer values only.
      //###############################
      //Since the previous checks are attempting SQL injection, and may have actually succeeded in modifying the database (ask me how I know?!)
      //then we cannot rely on the database contents being the same as when the original query was last run (could be hours ago)
      //so to work around this, simply re-run the query again now at this point.
      //Note that we are not counting this request in our max number of requests to be issued
      refreshedmessage = getNewMsg();
      sendAndReceive(refreshedmessage);

      //String mResBodyNormal = getBaseMsg().getResponseBody().toString();
      mResBodyNormalUnstripped = refreshedmessage.getResponseBody().toString();
      mResBodyNormalStripped = this.stripOff(mResBodyNormalUnstripped, origParamValue);

      if (!sqlInjectionFoundForUrl && doExpressionBased && countExpressionBasedRequests < doExpressionMaxRequests) {

        //first figure out the type of the parameter..        
        try {
          //is it an integer type?
          //ZAP: removed URLDecoding because on Variants
          //int paramAsInt = new Integer (TestSQLInjection.getURLDecode(origParamValue));
          int paramAsInt = new Integer(origParamValue);

          if (this.debugEnabled) {
            log.debug("The parameter value [" + origParamValue + "] is of type Integer");
          }

          //get a value 2 sizes bigger
          int paramPlusTwo = paramAsInt + 2;
          String modifiedParamValue = String.valueOf(paramPlusTwo) + "-2";

          //and prepare a request to set the parameter value to a string value like "3-2", if the original parameter value was "1"
          //those of you still paying attention will note that if handled as expressions (such as by a database), these represent the same value.
          HttpMessage msg4 = getNewMsg();
          setParameter(msg4, param, modifiedParamValue);

          sendAndReceive(msg4);
          countExpressionBasedRequests++;

          String modifiedExpressionOutputUnstripped = msg4.getResponseBody().toString();
          String modifiedExpressionOutputStripped = this.stripOff(modifiedExpressionOutputUnstripped, modifiedParamValue);

          //set up two little arrays to ease the work of checking the unstripped output, and then the stripped output
          String normalBodyOutput[] = {mResBodyNormalUnstripped, mResBodyNormalStripped};
          String expressionBodyOutput[] = {modifiedExpressionOutputUnstripped, modifiedExpressionOutputStripped};
          boolean strippedOutput[] = {false, true};

           for (int booleanStrippedUnstrippedIndex = 0; booleanStrippedUnstrippedIndex < 2 && !sqlInjectionFoundForUrl; booleanStrippedUnstrippedIndex++) {
            //if the results of the modified request match the original query, we may be onto something.
            if (expressionBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex]) == 0) {
              if (this.debugEnabled) {
                log.debug("Check 4, " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for modified expression parameter [" + modifiedParamValue + "] matched (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI());
              }
              //confirm that a different parameter value generates different output, to minimise false positives

              //get a value 3 sizes bigger this time
              int paramPlusFour = paramAsInt + 3;
              String modifiedParamValueConfirm = String.valueOf(paramPlusFour) + "-2";

              //and prepare a request to set the parameter value to a string value like "4-2", if the original parameter value was "1"
              //Note that the two values are NOT equivalent, and the param value is different to the original
              HttpMessage msg4Confirm = getNewMsg();
              setParameter(msg4Confirm, param, modifiedParamValueConfirm);

              sendAndReceive(msg4Confirm);
              countExpressionBasedRequests++;

              String confirmExpressionOutputUnstripped = msg4Confirm.getResponseBody().toString();
              String confirmExpressionOutputStripped = this.stripOff(confirmExpressionOutputUnstripped, modifiedParamValueConfirm);

              //set up two little arrays to ease the work of checking the unstripped output or the stripped output
              String confirmExpressionBodyOutput[] = {confirmExpressionOutputUnstripped, confirmExpressionOutputStripped};

              if (confirmExpressionBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex]) != 0) {
                //the confirm query did not return the same results.  This means that arbitrary queries are not all producing the same page output.
                //this means the fact we earier reproduced the original page output with a modified parameter was not a coincidence

                //Likely a SQL Injection. Raise it
                String extraInfo = null;
                if (strippedOutput[booleanStrippedUnstrippedIndex]) {
                  extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.expressionbased.extrainfo", modifiedParamValue, "");
                } else {
                  extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.expressionbased.extrainfo", modifiedParamValue, "NOT ");
                }

                //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
                sqlInjectionAttack = modifiedParamValue;
                bingo(Alert.RISK_HIGH, Alert.WARNING, getName(), getDescription(),
                    null, //url
                    param, sqlInjectionAttack,
                    extraInfo, getSolution(), "", msg4);

                sqlInjectionFoundForUrl = true;
              }
            }
            //bale out if we were asked nicely
            if (isStop()) {
              log.debug("Stopping the scan due to a user request");
              return;
            }
          }
        } catch (Exception e) {

          if (this.debugEnabled) {
            log.debug("The parameter value [" + origParamValue + "] is NOT of type Integer");
          }
          //TODO: implement a similar check for string types?  This probably needs to be RDBMS specific (ie, it should not live in this scanner)
        }
      }


      //Check 2: boolean based checks.
      //the check goes like so:
      // append " and 1 = 1" to the param.  Send the query.  Check the results. Hopefully they match the original results from the unmodified query,
      // *suggesting* (but not yet definitely) that we have successfully modified the query, (hopefully not gotten an error message),
      // and have gotten the same results back, which is what you would expect if you added the constraint " and 1 = 1" to most (but not every) SQL query.
      // So was it a fluke that we got the same results back from the modified query? Perhaps the original query returned 0 rows, so adding any number of
      // constraints would change nothing?  It is still a possibility!
      // check to see if we can change the original parameter again to *restrict* the scope of the query using an AND with an always false condition (AND_ERR)
      // (decreasing the results back to nothing), or to *broaden* the scope of the query using an OR with an always true condition (AND_OR)
      // (increasing the results). 
      // If we can successfully alter the results to our requirements, by one means or another, we have found a SQL Injection vulnerability.
      //Some additional complications: assume there are 2 HTML parameters: username and password, and the SQL constructed is like so:
      // select * from username where user = "$user" and password = "$password"
      // and lets assume we successfully know the type of the user field, via SQL_OR_TRUE value '" OR "1"="1' (single quotes not part of the value)
      // we still have the problem that the actual SQL executed would look like so:
      // select * from username where user = "" OR "1"="1" and password = "whateveritis"
      // Since the password field is still taken into account (by virtue of the AND condition on the password column), and we only inject one parameter at a time,
      // we are still not in control.
      // the solution is simple: add an end-of-line comment to the field added in (in this example: the user field), so that the SQL becomes:
      // select * from username where user = "" OR "1"="1" -- and password = "whateveritis"
      // the result is that any additional constraints are commented out, and the last condition to have any effect is the one whose
      // HTTP param we are manipulating.
      // Note also that because this comment only needs to be added to the "SQL_OR_TRUE" and not to the equivalent SQL_AND_FALSE, because of the nature of the OR
      // and AND conditions in SQL.
      // Corollary: If a particular RDBMS does not offer the ability to comment out the remainder of a line, we will not attempt to comment out anything in the query
      //            and we will simply hope that the *last* constraint in the SQL query is constructed from a HTTP parameter under our control.

      if (this.debugEnabled) {
        log.debug("Doing Check 2, since check 1 did not match for " + getBaseMsg().getRequestHeader().getURI());
      }

      //Since the previous checks are attempting SQL injection, and may have actually succeeded in modifying the database (ask me how I know?!)
      //then we cannot rely on the database contents being the same as when the original query was last run (could be hours ago)
      //so to work around this, simply re-run the query again now at this point.
      //Note that we are not counting this request in our max number of requests to be issued
      refreshedmessage = getNewMsg();
      sendAndReceive(refreshedmessage);

      //String mResBodyNormal = getBaseMsg().getResponseBody().toString();
      mResBodyNormalUnstripped = refreshedmessage.getResponseBody().toString();
      mResBodyNormalStripped = this.stripOff(mResBodyNormalUnstripped, origParamValue);

      //boolean booleanBasedSqlInjectionFoundForParam = false;

      //try each of the AND syntax values in turn.
      //Which one is successful will depend on the column type of the table/view column into which we are injecting the SQL.
      for (int i = 0;
          i < SQL_LOGIC_AND_TRUE.length && !sqlInjectionFoundForUrl && doBooleanBased
          && countBooleanBasedRequests < doBooleanMaxRequests;
          i++) {
        //needs a new message for each type of AND to be issued
        HttpMessage msg2 = getNewMsg();
        //ZAP: Removed getURLDecode()
        String sqlBooleanAndTrueValue = origParamValue + SQL_LOGIC_AND_TRUE[i];
        String sqlBooleanAndFalseValue = origParamValue + SQL_LOGIC_AND_FALSE[i];

        setParameter(msg2, param, sqlBooleanAndTrueValue);

        //send the AND with an additional TRUE statement tacked onto the end. Hopefully it will return the same results as the original (to find a vulnerability)
        sendAndReceive(msg2);
        countBooleanBasedRequests++;

        //String resBodyAND = msg2.getResponseBody().toString();
        String resBodyANDTrueUnstripped = msg2.getResponseBody().toString();
        String resBodyANDTrueStripped = this.stripOff(resBodyANDTrueUnstripped, sqlBooleanAndTrueValue);

        //set up two little arrays to ease the work of checking the unstripped output, and then the stripped output
        String normalBodyOutput[] = {mResBodyNormalUnstripped, mResBodyNormalStripped};
        String andTrueBodyOutput[] = {resBodyANDTrueUnstripped, resBodyANDTrueStripped};
        boolean strippedOutput[] = {false, true};

        for (int booleanStrippedUnstrippedIndex = 0; booleanStrippedUnstrippedIndex < 2; booleanStrippedUnstrippedIndex++) {
          //if the results of the "AND 1=1" match the original query (using either the stipped or unstripped versions), we may be onto something.
          if (andTrueBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex]) == 0) {
            if (this.debugEnabled) {
              log.debug("Check 2, " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for AND TRUE condition [" + sqlBooleanAndTrueValue + "] matched (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI());
            }
            //so they match. Was it a fluke? See if we get the same result by tacking on "AND 1 = 2" to the original
            HttpMessage msg2_and_false = getNewMsg();

            setParameter(msg2_and_false, param, sqlBooleanAndFalseValue);

            sendAndReceive(msg2_and_false);
            countBooleanBasedRequests++;

            //String resBodyANDFalse = stripOff(msg2_and_false.getResponseBody().toString(), SQL_LOGIC_AND_FALSE[i]);
            //String resBodyANDFalse = msg2_and_false.getResponseBody().toString();
            String resBodyANDFalseUnstripped = msg2_and_false.getResponseBody().toString();
            String resBodyANDFalseStripped = this.stripOff(resBodyANDFalseUnstripped, sqlBooleanAndFalseValue);

            String andFalseBodyOutput[] = {resBodyANDFalseUnstripped, resBodyANDFalseStripped};

            //which AND False output should we compare? the stripped or the unstripped version?
            //depends on which one we used to get to here.. use the same as that..           

            // build an always false AND query.  Result should be different to prove the SQL works.
            if (andFalseBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex]) != 0) {
              if (this.debugEnabled) {
                log.debug("Check 2, " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for AND FALSE condition [" + sqlBooleanAndFalseValue + "] differed from (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI());
              }

              //it's different (suggesting that the "AND 1 = 2" appended on gave different results because it restricted the data set to nothing
              //Likely a SQL Injection. Raise it
              String extraInfo = null;
              if (strippedOutput[booleanStrippedUnstrippedIndex]) {
                extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo", sqlBooleanAndTrueValue, sqlBooleanAndFalseValue, "");
              } else {
                extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo", sqlBooleanAndTrueValue, sqlBooleanAndFalseValue, "NOT ");
              }
              extraInfo = extraInfo + "\n" + Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo.dataexists");

              //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
              sqlInjectionAttack = sqlBooleanAndTrueValue;
              bingo(Alert.RISK_HIGH, Alert.WARNING, getName(), getDescription(),
                  null, //url
                  param, sqlInjectionAttack,
                  extraInfo, getSolution(), "", msg2);

              sqlInjectionFoundForUrl = true;

              continue; //to the next entry in SQL_AND

            } else {
              //the results of the always false condition are the same as for the original unmodified parameter
              //this could be because there was *no* data returned for the original unmodified parameter
              //so consider the effect of adding comments to both the always true condition, and the always false condition
              //the first value to try..
              //ZAP: Removed getURLDecode()
              String orValue = origParamValue + SQL_LOGIC_OR_TRUE[i];

              //this is where that comment comes in handy: if the RDBMS supports one-line comments, add one in to attempt to ensure that the
              //condition becomes one that is effectively always true, returning ALL data (or as much as possible), allowing us to pinpoint the SQL Injection
              if (this.debugEnabled) {
                log.debug("Check 2 , " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for AND FALSE condition [" + sqlBooleanAndFalseValue + "] SAME as (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI() + " ### (forcing OR TRUE check) ");
              }
              HttpMessage msg2_or_true = getNewMsg();
              setParameter(msg2_or_true, param, orValue);
              sendAndReceive(msg2_or_true);
              countBooleanBasedRequests++;

              //String resBodyORTrue = stripOff(msg2_or_true.getResponseBody().toString(), orValue);
              //String resBodyORTrue = msg2_or_true.getResponseBody().toString();
              String resBodyORTrueUnstripped = msg2_or_true.getResponseBody().toString();
              String resBodyORTrueStripped = this.stripOff(resBodyORTrueUnstripped, orValue);

              String orTrueBodyOutput[] = {resBodyORTrueUnstripped, resBodyORTrueStripped};

              int compareOrToOriginal = orTrueBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex]);
              if (compareOrToOriginal != 0) {

                if (this.debugEnabled) {
                  log.debug("Check 2, " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for OR TRUE condition [" + orValue + "] different to (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI());
                }

                //it's different (suggesting that the "OR 1 = 1" appended on gave different results because it broadened the data set from nothing to something
                //Likely a SQL Injection. Raise it
                String extraInfo = null;
                if (strippedOutput[booleanStrippedUnstrippedIndex]) {
                  extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo", sqlBooleanAndTrueValue, orValue, "");
                } else {
                  extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo", sqlBooleanAndTrueValue, orValue, "NOT ");
                }
                extraInfo = extraInfo + "\n" + Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo.datanotexists");

                //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
                sqlInjectionAttack = orValue;
                bingo(Alert.RISK_HIGH, Alert.WARNING, getName(), getDescription(),
                    null, //url
                    param, sqlInjectionAttack,
                    extraInfo, getSolution(), "", msg2);

                sqlInjectionFoundForUrl = true;
                //booleanBasedSqlInjectionFoundForParam = true;  //causes us to skip past the other entries in SQL_AND.  Only one will expose a vuln for a given param, since the database column is of only 1 type

                continue;
              }
            }
          } //if the results of the "AND 1=1" match the original query, we may be onto something.
          else {
            //andTrueBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex])
            //the results of the "AND 1=1" do NOT match the original query, for whatever reason (no sql injection, or the web page is not stable)
            if (this.debugEnabled) {
              log.debug("Check 2, " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for AND condition [" + sqlBooleanAndTrueValue + "] does NOT match the (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI());
              Patch diffpatch = DiffUtils.diff(
                  new LinkedList<String>(Arrays.asList(normalBodyOutput[booleanStrippedUnstrippedIndex].split("\\n"))),
                  new LinkedList<String>(Arrays.asList(andTrueBodyOutput[booleanStrippedUnstrippedIndex].split("\\n"))));

              //int numberofDifferences = diffpatch.getDeltas().size();

              //and convert the list of patches to a String, joining using a newline
              StringBuilder tempDiff = new StringBuilder(250);
              for (Delta delta : diffpatch.getDeltas()) {
                String changeType = null;
                if (delta.getType() == Delta.TYPE.CHANGE) {
                  changeType = "Changed Text";
                } else if (delta.getType() == Delta.TYPE.DELETE) {
                  changeType = "Deleted Text";
                } else if (delta.getType() == Delta.TYPE.INSERT) {
                  changeType = "Inserted text";
                } else {
                  changeType = "Unknown change type [" + delta.getType() + "]";
                }

                tempDiff.append("\n(" + changeType + ")\n")//blank line before
                tempDiff.append("Output for Unmodified parameter: " + delta.getOriginal() + "\n");
                tempDiff.append("Output for   modified parameter: " + delta.getRevised() + "\n");
              }
              log.debug("DIFFS: " + tempDiff);
            }
          }
        //bale out if we were asked nicely
        if (isStop()) {
          log.debug("Stopping the scan due to a user request");
          return;
          }
        }  //end of boolean logic output index (unstripped + stripped)
      }
      //end of check 2
     
     
      //check 2a: boolean based logic, where the original query returned *no* data. Here we append " OR 1=1" in an attempt to extract *more* data
      //and then verify the results by attempting to reproduce the original results by appending an " AND 1=2" condition (ie "open up first, then restrict to verify")
      //this differs from the previous logic based check since the previous check assumes that the original query produced data, and tries first to restrict that data
      //(ie, it uses "restrict first, open up to verify" ).
      for (int i = 0;
          i < SQL_LOGIC_OR_TRUE.length && !sqlInjectionFoundForUrl && doBooleanBased
          && countBooleanBasedRequests < doBooleanMaxRequests;
          i++) {
        HttpMessage msg2 = getNewMsg();
        String sqlBooleanOrTrueValue = origParamValue + SQL_LOGIC_OR_TRUE[i];
        String sqlBooleanAndFalseValue = origParamValue + SQL_LOGIC_AND_FALSE[i];

        setParameter(msg2, param, sqlBooleanOrTrueValue);       
        sendAndReceive(msg2);
        countBooleanBasedRequests++;

        String resBodyORTrueUnstripped = msg2.getResponseBody().toString();
               
        //if the results of the "OR 1=1" exceed the original query (unstripped, by more than a 20% size difference, say), we may be onto something.
        //TODO: change the percentage difference threshold based on the alert threshold        
        if ((resBodyORTrueUnstripped.length() > ( mResBodyNormalUnstripped.length() * 1.2))) {
          if (this.debugEnabled) {
            log.debug("Check 2a, unstripped html output for OR TRUE condition [" + sqlBooleanOrTrueValue + "] produced sufficiently larger results than the original message");
          }
          //if we can also restrict it back to the original results by appending a " and 1=2", then "Winner Winner, Chicken Dinner".
          HttpMessage msg2_and_false = getNewMsg();
          setParameter(msg2_and_false, param, sqlBooleanAndFalseValue);
          sendAndReceive(msg2_and_false);
          countBooleanBasedRequests++;

          String resBodyANDFalseUnstripped = msg2_and_false.getResponseBody().toString();
          String resBodyANDFalseStripped = this.stripOff(resBodyANDFalseUnstripped, sqlBooleanAndFalseValue);
         
          //does the "AND 1=2" version produce the same as the original (for stripped/unstripped versions)
          boolean verificationUsingUnstripped = resBodyANDFalseUnstripped.compareTo(mResBodyNormalUnstripped) == 0;
          boolean verificationUsingStripped = resBodyANDFalseStripped.compareTo(mResBodyNormalStripped) == 0;
          if ( verificationUsingUnstripped || verificationUsingStripped ) {
            if (this.debugEnabled) {
              log.debug("Check 2, " + (verificationUsingStripped ? "STRIPPED" : "UNSTRIPPED") + " html output for AND FALSE condition [" + sqlBooleanAndFalseValue + "] matches the (refreshed) original results");
            }             
            //Likely a SQL Injection. Raise it
            String extraInfo = null;
            if (verificationUsingStripped) {
              extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo", sqlBooleanOrTrueValue, sqlBooleanAndFalseValue, "");
            } else {
              extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo", sqlBooleanOrTrueValue, sqlBooleanAndFalseValue, "NOT ");
            }
            extraInfo = extraInfo + "\n" + Constant.messages.getString("ascanrules.sqlinjection.alert.booleanbased.extrainfo.datanotexists");
 
            //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
            sqlInjectionAttack = sqlBooleanOrTrueValue;
            bingo(Alert.RISK_HIGH, Alert.WARNING, getName(), getDescription(),
                null, //url
                param, sqlInjectionAttack,
                extraInfo, getSolution(), "", msg2);
 
            sqlInjectionFoundForUrl = true;
 
            continue; //to the next entry
            }
          }
        } 
      //end of check 2a


      //Check 3: UNION based
      //for each SQL UNION combination to try
      for (int sqlUnionStringIndex = 0;
          sqlUnionStringIndex < SQL_UNION_APPENDAGES.length && !sqlInjectionFoundForUrl && doUnionBased && countUnionBasedRequests < doUnionMaxRequests;
          sqlUnionStringIndex++) {

        //new message for each value we attack with
        HttpMessage msg3 = getNewMsg();
        String sqlUnionValue = origParamValue + SQL_UNION_APPENDAGES[sqlUnionStringIndex];
        setParameter(msg3, param, sqlUnionValue);
        //send the message with the modified parameters
        sendAndReceive(msg3);
        countUnionBasedRequests++;

        //now check the results.. look first for UNION specific error messages in the output that were not there in the original output
        //and failing that, look for generic RDBMS specific error messages
        //TODO: maybe also try looking at a differentiation based approach?? Prone to false positives though.
        Iterator<Pattern> errorPatternUnionIterator = SQL_UNION_ERROR_TO_DBMS.keySet().iterator();

        while (errorPatternUnionIterator.hasNext() && !sqlInjectionFoundForUrl) {
          Pattern errorPattern = errorPatternUnionIterator.next();
          String errorPatternRDBMS = SQL_UNION_ERROR_TO_DBMS.get(errorPattern);

          //if the "error message" occurs in the result of sending the modified query, but did NOT occur in the original result of the original query
          //then we may may have a SQL Injection vulnerability
          String sqlUnionBodyUnstripped = msg3.getResponseBody().toString();
          String sqlUnionBodyStripped = this.stripOff(sqlUnionBodyUnstripped, sqlUnionValue);

          Matcher matcherOrig = errorPattern.matcher(mResBodyNormalStripped);
          Matcher matcherSQLUnion = errorPattern.matcher(sqlUnionBodyStripped);
          boolean patternInOrig = matcherOrig.find();
          boolean patternInSQLUnion = matcherSQLUnion.find();

          //if (! matchBodyPattern(getBaseMsg(), errorPattern, null) && matchBodyPattern(msg3, errorPattern, sb)) {       
          if (!patternInOrig && patternInSQLUnion) {
            //Likely a UNION Based SQL Injection (by error message). Raise it
            String extraInfo = Constant.messages.getString("ascanrules.sqlinjection.alert.unionbased.extrainfo", errorPatternRDBMS, errorPattern.toString());

            //raise the alert, and save the attack string for the "Authentication Bypass" alert, if necessary
            sqlInjectionAttack = sqlUnionValue;
            bingo(Alert.RISK_HIGH, Alert.WARNING, getName() + " - " + errorPatternRDBMS, getDescription(),
                refreshedmessage.getRequestHeader().getURI().getURI(), //url
                param, sqlInjectionAttack,
                extraInfo, getSolution(), matcherSQLUnion.group(), msg3);

            //log it, as the RDBMS may be useful to know later (in subsequent checks, when we need to determine RDBMS specific behaviour, for instance)
            getKb().add(refreshedmessage.getRequestHeader().getURI(), "sql/" + errorPatternRDBMS, Boolean.TRUE);

            sqlInjectionFoundForUrl = true;
            continue;
          }
        //bale out if we were asked nicely
        if (isStop()) {
          log.debug("Stopping the scan due to a user request");
          return;
          }
        } //end of the loop to check for RDBMS specific UNION error messages       
      } ////for each SQL UNION combination to try
      //end of check 3


      //###############################

      //check for columns used in the "order by" clause of a SQL statement. earlier tests will likely not catch these

      //append on " ASC -- " to the end of the original parameter. Grab the results.
      //if the results are different to the original (unmodified parameter) results, then bale
      //if the results are the same as for the original parameter value, then the parameter *might* be influencing the order by
      //  try again for "DESC": append on " DESC -- " to the end of the original parameter. Grab the results.
      //  if the results are the same as the original (unmodified parameter) results, then bale
      //  (the results are not under our control, or there is no difference in the ordering, for some reason: 0 or 1 rows only, or ordering
      //  by the first column alone is not sufficient to change the ordering of the data.)
      //  if the results were different to the original (unmodified parameter) results, then
      //    SQL injection!!

      //Since the previous checks are attempting SQL injection, and may have actually succeeded in modifying the database (ask me how I know?!)
      //then we cannot rely on the database contents being the same as when the original query was last run (could be hours ago)
      //so to work around this, simply re-run the query again now at this point.
      //Note that we are not counting this request in our max number of requests to be issued
      refreshedmessage = getNewMsg();
      sendAndReceive(refreshedmessage);

      //String mResBodyNormal = getBaseMsg().getResponseBody().toString();
      mResBodyNormalUnstripped = refreshedmessage.getResponseBody().toString();
      mResBodyNormalStripped = this.stripOff(mResBodyNormalUnstripped, origParamValue);

      if (!sqlInjectionFoundForUrl && doOrderByBased && countOrderByBasedRequests < doOrderByMaxRequests) {

        //ZAP: Removed getURLDecode()
        String modifiedParamValue = origParamValue + " ASC " + SQL_ONE_LINE_COMMENT;

        HttpMessage msg5 = getNewMsg();
        setParameter(msg5, param, modifiedParamValue);

        sendAndReceive(msg5);
        countOrderByBasedRequests++;

        String modifiedAscendingOutputUnstripped = msg5.getResponseBody().toString();
        String modifiedAscendingOutputStripped = this.stripOff(modifiedAscendingOutputUnstripped, modifiedParamValue);

        //set up two little arrays to ease the work of checking the unstripped output, and then the stripped output
        String normalBodyOutput[] = {mResBodyNormalUnstripped, mResBodyNormalStripped};
        String ascendingBodyOutput[] = {modifiedAscendingOutputUnstripped, modifiedAscendingOutputStripped};
        boolean strippedOutput[] = {false, true};

        for (int booleanStrippedUnstrippedIndex = 0; booleanStrippedUnstrippedIndex < 2; booleanStrippedUnstrippedIndex++) {
          //if the results of the modified request match the original query, we may be onto something.
          if (ascendingBodyOutput[booleanStrippedUnstrippedIndex].compareTo(normalBodyOutput[booleanStrippedUnstrippedIndex]) == 0) {
            if (this.debugEnabled) {
              log.debug("Check X, " + (strippedOutput[booleanStrippedUnstrippedIndex] ? "STRIPPED" : "UNSTRIPPED") + " html output for modified Order By parameter [" + modifiedParamValue + "] matched (refreshed) original results for " + refreshedmessage.getRequestHeader().getURI());
            }
            //confirm that a different parameter value generates different output, to minimise false positives

            //use the descending order this time
            //ZAP: Removed getURLDecode()
            String modifiedParamValueConfirm = origParamValue + " DESC " + SQL_ONE_LINE_COMMENT;

            HttpMessage msg5Confirm = getNewMsg();
            setParameter(msg5Confirm, param, modifiedParamValueConfirm);

            sendAndReceive(msg5Confirm);
            countOrderByBasedRequests++;

            String confirmOrderByOutputUnstripped = msg5Confirm.getResponseBody().toString();
            String confirmOrderByOutputStripped = this.stripOff(confirmOrderByOutputUnstripped, modifiedParamValueConfirm);

            //set up two little arrays to ease the work of checking the unstripped output or the stripped output
            String confirmOrderByBodyOutput[] = {confirmOrderByOutputUnstripped, confirmOrderByOutputStripped};

View Full Code Here

    }
   
    @Override
    public void scan(HttpMessage msg, String param, String value) {
    try {
      HttpMessage msg1 = msg.cloneRequest();
      this.setParameter(msg1, param, PersistentXSSUtils.getUniqueValue(msg1, param));
      if (log.isDebugEnabled()) {
        log.debug("Prime msg=" + msg1.getRequestHeader().getURI() + " param=" + param);
      }
        sendAndReceive(msg1, false);
    } catch (Exception e) {
      log.error(e.getMessage(), e);
    }
View Full Code Here

    public void scan(HttpMessage msg, String param, String value) {

        String attack = null;

        // always try normal query first
        HttpMessage normalMsg = getNewMsg();

        try {
            sendAndReceive(normalMsg);
        } catch (Exception e) {
            // ZAP: Log exceptions
            log.warn(e.getMessage(), e);
            return;
        }

        if (normalMsg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) {
            return;
        }

        for (int i = 0; i < PARAM_LIST.length; i++) {
            msg = getNewMsg();
            if (i == 0) {
                // remove entire parameter when i=0;
                setParameter(msg, null, null);
                attack = null;
            } else {
                setParameter(msg, param, PARAM_LIST[i]);
                attack = PARAM_LIST[i];

            }
            try {
                sendAndReceive(msg);
                if (checkResult(msg, param, attack, normalMsg.getResponseBody().toString())) {
                    return;
                }

            } catch (Exception e) {
                // ZAP: Log exceptions
View Full Code Here

    }

    @Override
    public void scan() {

        HttpMessage msg = getBaseMsg();
        try {
            HttpMessage msg1 = msg.cloneRequest();
            sendAndReceive(msg1, false);
            PersistentXSSUtils.testForSink(msg1);

        } catch (Exception e) {
      log.error(e.getMessage(), e);
View Full Code Here

TOP

Related Classes of org.parosproxy.paros.network.HttpMessage

Copyright © 2018 www.massapicom. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.