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.html.Input; import org.apache.ecs.html.P; import org.apache.ecs.html.A; import org.apache.ecs.html.IMG; import org.owasp.webgoat.session.DatabaseUtilities; import org.owasp.webgoat.session.ECSFactory; import org.owasp.webgoat.session.WebSession; /******************************************************************************* * * * This file is part of WebGoat, an Open Web Application Security Project * utility. For details, please see http://www.owasp.org/ * * Copyright (c) 2002 - 2007 Bruce Mayhew * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * 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 GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. * * Getting Source ============== * * Source for this application is maintained at code.google.com, a repository * for free software projects. * * For details, please see http://code.google.com/p/webgoat/ * * @author Chuck Willis Chuck's web * site (this lesson is heavily based on Bruce Mayhews' SQL * Injection lesson * @created January 14, 2005 */ public class BlindStringSqlInjection extends LessonAdapter { public final static A MANDIANT_LOGO = new A().setHref("http://www.mandiant.com").addElement(new IMG("images/logos/mandiant.png").setAlt("MANDIANT").setBorder(0).setHspace(0).setVspace(0)); private final static String ACCT_NUM = "account_number"; private final static String TARGET_CC_NUM = "4321432143214321"; /** * 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 { Connection connection = DatabaseUtilities.getConnection(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_CC_NUM; // } else // { answer_query = "SELECT name FROM pins WHERE cc_number = '" + TARGET_CC_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(); System.out.println("Account: " + accountNumber ); System.out.println("Answer : " + answer_results.getString(1)); 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.")); // comment out two lines below ec.addElement(new P().addElement(sqle.getMessage())); sqle.printStackTrace(); } } 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 */ protected Category getDefaultCategory() { return Category.INJECTION; } /** * Gets the credits attribute of the AbstractLesson object * * @return The credits value */ public Element getCredits() { return super.getCustomCredits("Created by Chuck Willis ", MANDIANT_LOGO); } /** * * 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(WebSession s) { 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 <" + "

The backend database is HSQLDB. Keep that in mind if you research SQL functions " + "on the Internet since different databases use some different functions and syntax." + "

You can find more about HSQLDB's SQL Syntax at " + "http://hsqldb.org/doc/guide/ch09.html. " + "Pay attention to the section titled \"String built-in Functions / Stored Procedures\"."); 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 contstructs: " + "

AND - combine the logic of the normal query with a boolean result" + "

( and ) - group subexpressions so they evaluate properly" + "

SELECT - make a subquery for your target data and get a string " + "

SUBSTRING(string, start, length) - returns a " + "substring of string starting at the start character and going for length characters " + "

> and = and < - once you have a character's value, compare it to a choosen one" + "

You can find more about HSQLDB's SQL Syntax at " + "http://hsqldb.org/doc/guide/ch09.html"); hints.add("This is the code for the query being built and issued by WebGoat:

" + "\"SELECT * FROM user_data WHERE userid = \" + accountNumber "); hints .add("Here is an example for another table:" + "

In the table user_data, is the first character of the first_name for the record with a cc_number of " + "333498703333" + " greater than 'M'? " + "

101 AND (SUBSTRING((SELECT first_name FROM user_data WHERE cc_number='" + "333498703333" + "'), 1, 1) > 'M' ); " + "

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("Partial Solution for First Character:" + "

Is the first character of the name of the record with a cc_number of " + TARGET_CC_NUM + " less than 'M'? " + "

101 AND (SUBSTRING((SELECT name FROM pins WHERE cc_number='" + TARGET_CC_NUM + "'), 1, 1) < 'M' ); " + "

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 Part of Solution for First Character:" + "

Is the first character of the name of the record with a cc_number of " + TARGET_CC_NUM + " less than 'H'? " + "

101 AND (SUBSTRING((SELECT name FROM pins WHERE cc_number='" + TARGET_CC_NUM + "'), 1, 1) < 'H' ); " + "

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("Partial Solution for Second Character:" + "

Is the second character of the name of the record with a cc_number of " + TARGET_CC_NUM + " greater than 'k'? " + "

101 AND (SUBSTRING((SELECT name FROM pins WHERE cc_number='" + TARGET_CC_NUM + "'), 2, 1) > 'k' ); " + "

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. " + "

Reference Ascii Values: 'A' = 65 'Z' = 90 'a' = 97 'z' = 122 " + "

The goal is to find the value of " + "the field name in table pins for the row with the cc_number of " + TARGET_CC_NUM + ". The field is of type varchar, which is a string." + "

Put the discovered name in the form to pass the lesson. Only the discovered name " + "should be put into the form field, paying close attention to the spelling and capitalization."; return (instructions); } private final static Integer DEFAULT_RANKING = new Integer(95); protected Integer getDefaultRanking() { return DEFAULT_RANKING; } /** * Gets the title attribute of the DatabaseFieldScreen object * * @return The title value */ public String getTitle() { return ("Blind String SQL Injection"); } /** * Constructor for the DatabaseFieldScreen object * * @param s * Description of the Parameter */ public void handleRequest(WebSession s) { try { super.handleRequest(s); } catch (Exception e) { System.out.println("Exception caught: " + e); e.printStackTrace(System.out); } } }