package org.owasp.webgoat.lessons;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.apache.ecs.Element;
import org.apache.ecs.ElementContainer;
import org.apache.ecs.StringElement;
import org.apache.ecs.html.Input;
import org.apache.ecs.html.P;
import org.owasp.webgoat.session.DatabaseUtilities;
import org.owasp.webgoat.session.ECSFactory;
import org.owasp.webgoat.session.WebSession;


/**
 *  Copyright (c) 2005 Free Software Foundation developed under the custody of the Open Web
 *  Application Security Project (http://www.owasp.org) This software package is published by OWASP
 *  under the GPL. You should read and accept the LICENSE before you use, modify and/or redistribute
 *  this software.
 *
 * @author     Chuck Willis <a href="http://www.securityfoundry.com">Chuck's web site</a> (this lesson is heavily based on Jeff Williams' SQL Injection lesson
 * @created    January 14, 2005
 */
public class BlindSqlInjection extends LessonAdapter
{
	private final static String ACCT_NUM = "account_number";
	private final static int TARGET_ACCT_NUM = 15613;
	private static Connection connection = null;


	/**
	 *  Description of the Method
	 *
	 * @param  s  Description of the Parameter
	 * @return    Description of the Return Value
	 */
	protected Element createContent( WebSession s )
	{
		ElementContainer ec = new ElementContainer();

		try
		{
			if ( connection == null )
			{
				connection = DatabaseUtilities.makeConnection( s );
			}

			ec.addElement( new P().addElement( "Enter your Account Number: " ) );

			String accountNumber = s.getParser().getRawParameter( ACCT_NUM, "101" );
			Input input = new Input( Input.TEXT, ACCT_NUM, accountNumber.toString() );
			ec.addElement( input );

			Element b = ECSFactory.makeButton( "Go!" );
			ec.addElement( b );

			String query = "SELECT * FROM user_data WHERE userid = " + accountNumber ;
			String answer_query;
			if(runningOnWindows()) {
				answer_query = "SELECT TOP 1 first_name FROM user_data WHERE userid = " + TARGET_ACCT_NUM; 
			} else {
				answer_query = "SELECT first_name FROM user_data WHERE userid = " + TARGET_ACCT_NUM; 
			}
			
			try
			{
				Statement answer_statement = connection.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY );
				ResultSet answer_results = answer_statement.executeQuery( answer_query );
				answer_results.first();
				if( accountNumber.toString().equals(answer_results.getString(1))) {
					makeSuccess( s );
				} else {
				
					Statement statement = connection.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY );
					ResultSet results = statement.executeQuery( query );
					
					if ( ( results != null ) && ( results.first() == true ) )
					{
						ec.addElement( new P().addElement("Account number is valid"));
					} else {
						ec.addElement( new P().addElement("Invalid account number"));
					}
				}
			}
			catch ( SQLException sqle )
			{
				ec.addElement( new P().addElement("An error occurred, please try again."));	
			}
		}
		catch ( Exception e )
		{
			s.setMessage( "Error generating " + this.getClass().getName() );
			e.printStackTrace();
		}

		return ( ec );
	}



	/**
	 *  Gets the category attribute of the SqlInjection object
	 *
	 * @return    The category value
	 */
	public Category getCategory()
	{
		return AbstractLesson.A6;
	}


	/**
	 *  Gets the credits attribute of the AbstractLesson object
	 *
	 * @return    The credits value
	 */
	public Element getCredits()
	{
		return new StringElement("By Chuck Willis (edited 14 Dec 05 - Hints need to updated for non-Windows systems)");
	}

	/**
	 * 
	 * Determines the OS that WebGoat is running on.  Needed because different DB backends
	 * are used on the different OSes (Access on Windows, InstantDB on others)
	 * 
	 * @return true if running on Windows, false otherwise
	 */
	private boolean runningOnWindows() {
		String os = System.getProperty("os.name","Windows");
		if ( os.toLowerCase().indexOf("window") != -1  )
		{			
			return true; 
		}
		else 
		{		
            return false;
		}
 	}
	
	
	/**
	 *  Gets the hints attribute of the DatabaseFieldScreen object
	 *
	 * @return    The hints value
	 */
	protected List getHints()
	{
		List hints = new ArrayList();
		if (runningOnWindows()) {
			hints.add( "Compound SQL statements can be made by joining multiple tests with keywords like AND and OR. " +
					"Create a SQL statement that you can use as a true/false test and then " +
					"select the first character of the target element and do a start narrowing " +
					"down the character using > and <" + 
					"<br><br>The backend database is Microsoft Access.  Keep that in mind if you research SQL functions " +
					"on the Internet since different databases use some different functions and syntax.");
			hints.add( "This is the code for the query being built and issued by WebGoat:<br><br> " +
			"\"SELECT * FROM user_data WHERE userid = \" + accountNumber " );
			hints.add( "The application is taking your input and inserting it at the end of a pre-formed SQL command. "+
					"You will need to make use of the following SQL functions: " + 
					"<br><br>SELECT - query for your target data and get a string "+
					"<br><br>mid(string, start, length) - returns a "
					+ "substring of string starting at the start character and going for length characters "+
					"<br><br>asc(string) will return the ascii value of the first character in string " +
					"<br><br>&gt and &lt - once you have a character's value, compare it to a choosen one");
			hints.add( "Example: is the first character of the first_name of userid " + TARGET_ACCT_NUM + " less than 'M' (ascii 77)? " + 
					"<br><br>101 AND (asc( mid((SELECT first_name FROM user_data WHERE userid=" + TARGET_ACCT_NUM + ") , 1 , 1) ) < 77 ); " +
					"<br><br>If you get back that account number is valid, then yes.  If get back that the number is" +
					"invalid then answer is no.");
			hints.add( "Another example: is the second character of the first_name of userid " + TARGET_ACCT_NUM + " greater than 'm' (ascii 109)? " + 
					"<br><br>101 AND (asc( mid((SELECT first_name FROM user_data WHERE userid=" + TARGET_ACCT_NUM + ") , 2 , 1) ) > 109 ); " +
					"<br><br>If you get back that account number is valid, then yes.  If get back that the number is " +
					"invalid then answer is no.");
		} else {
			hints.add("Compound SQL statements can be made by joining multiple tests with keywords like AND and OR. " +
					"Create a SQL statement that you can use as a true/false test and then " +
					"select the first character of the target element and do a start narrowing " +
					"down the character using > and <" );

			hints.add("The database backend is InstantDB.  Here is a reference guide : <a href=\"http://www.instantdb.com/doc/syntax.html\" target=\"_blank\">http://www.instantdb.com/doc/syntax.html</a>");
			hints.add( "This is the code for the query being built and issued by WebGoat:<br><br> " +
				"\"SELECT * FROM user_data WHERE userid = \" + accountNumber " );
			hints.add( "THIS HINT IS FOR THE MS ACCESS DB.  IT NEEDS TO BE ALTERED FOR THE INSTANTDB BACKEND. <br><br>The application is taking your input and inserting it at the end of a pre-formed SQL command. "+
					"You will need to make use of the following SQL functions: " + 
					"<br><br>SELECT - query for your target data and get a string "+
					"<br><br>mid(string, start, length) - returns a "
					+ "substring of string starting at the start character and going for length characters "+
					"<br><br>asc(string) will return the ascii value of the first character in string " +
					"<br><br>&gt and &lt - once you have a character's value, compare it to a choosen one");
			hints.add( "THIS HINT IS FOR THE MS ACCESS DB.  IT NEEDS TO BE ALTERED FOR THE INSTANTDB BACKEND. <br><br>Example: is the first character of the first_name of userid " + TARGET_ACCT_NUM + " less than 'M' (ascii 77)? " + 
					"<br><br>101 AND (asc( mid((SELECT first_name FROM user_data WHERE userid=" + TARGET_ACCT_NUM + ") , 1 , 1) ) < 77 ); " +
					"<br><br>If you get back that account number is valid, then yes.  If get back that the number is" +
					"invalid then answer is no.");
			hints.add( "THIS HINT IS FOR THE MS ACCESS DB.  IT NEEDS TO BE ALTERED FOR THE INSTANTDB BACKEND. <br><br> example: is the second character of the first_name of userid " + TARGET_ACCT_NUM + " greater than 'm' (ascii 109)? " + 
					"<br><br>101 AND (asc( mid((SELECT first_name FROM user_data WHERE userid=" + TARGET_ACCT_NUM + ") , 2 , 1) ) > 109 ); " +
					"<br><br>If you get back that account number is valid, then yes.  If get back that the number is " +
					"invalid then answer is no.");
		}
		return hints;
	}


	/**
	 *  Gets the instructions attribute of the SqlInjection object
	 *
	 * @return    The instructions value
	 */
	public String getInstructions(WebSession s)
	{
		String instructions = "The form below allows a user to enter an account number and determine if "+
		"it is valid or not.  Use this form to develop a true / false test check other entries in the database.  "+
		"<br><br>Reference Ascii Values: 'A' = 65   'Z' = 90   'a' = 97   'z' = 122 " + 
		"<br><br>The goal is to find the value of "+
		"the first_name in table user_data for userid " + TARGET_ACCT_NUM + ".  Put that name in the form to pass the lesson.";

		return ( instructions );
	}




	private final static Integer DEFAULT_RANKING = new Integer(70);

	protected Integer getDefaultRanking()
	{
		return DEFAULT_RANKING;
	}


	/**
	 *  Gets the title attribute of the DatabaseFieldScreen object
	 *
	 * @return    The title value
	 */
	public String getTitle()
	{
		return ( "How to Perform Blind SQL Injection" );
	}


	/**
	 *  Constructor for the DatabaseFieldScreen object
	 *
	 * @param  s  Description of the Parameter
	 */
	public void handleRequest( WebSession s )
	{
		try
		{
			super.handleRequest( s );

			if ( connection == null )
			{
				connection = DatabaseUtilities.makeConnection( s );
			}
		}
		catch ( Exception e )
		{
			System.out.println( "Exception caught: " + e );
			e.printStackTrace( System.out );
		}
	}
}


