From ee912f734b348c21ca90a410979fc3e1897b5947 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Tue, 13 Jun 2017 06:43:03 +0200 Subject: [PATCH] Added SQL injection from challenge to lesson and added content for a blind sql injection --- .../webgoat/plugin/SqlInjectionChallenge.java | 136 ++++++++++++++++++ .../src/main/resources/css/challenge.css | 96 +++++++++++++ .../resources/html/SqlInjectionAdvanced.html | 127 +++++++++++++--- .../html/SqlInjectionMitigation.html | 85 ----------- .../html/SqlInjectionMitigations.html | 34 +++++ .../src/main/resources/js/challenge.js | 18 +++ .../en/SqlInjection_challenge.adoc | 4 + .../en/SqlInjection_content6c.adoc | 59 ++++++++ 8 files changed, 451 insertions(+), 108 deletions(-) create mode 100644 webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/plugin/SqlInjectionChallenge.java create mode 100644 webgoat-lessons/sql-injection/src/main/resources/css/challenge.css delete mode 100644 webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigation.html create mode 100644 webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigations.html create mode 100644 webgoat-lessons/sql-injection/src/main/resources/js/challenge.js create mode 100644 webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_challenge.adoc create mode 100644 webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_content6c.adoc diff --git a/webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/plugin/SqlInjectionChallenge.java b/webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/plugin/SqlInjectionChallenge.java new file mode 100644 index 000000000..f47076500 --- /dev/null +++ b/webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/plugin/SqlInjectionChallenge.java @@ -0,0 +1,136 @@ +package org.owasp.webgoat.plugin; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentPath; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.session.DatabaseUtilities; +import org.owasp.webgoat.session.WebSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.sql.*; + +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author nbaars + * @since 4/8/17. + */ +@AssignmentPath("SqlInjection/challenge") +@Slf4j +public class SqlInjectionChallenge extends AssignmentEndpoint { + + private static final String PASSWORD_TOM = "thisisasecretfortomonly"; + //Make it more random at runtime (good luck guessing) + private static final String USERS_TABLE_NAME = "challenge_users_6" + RandomStringUtils.randomAlphabetic(16); + + @Autowired + private WebSession webSession; + + public SqlInjectionChallenge() { + log.info("Challenge 6 tablename is: {}", USERS_TABLE_NAME); + } + + @PutMapping //assignment path is bounded to class so we use different http method :-) + @ResponseBody + public AttackResult registerNewUser(@RequestParam String username_reg, @RequestParam String email_reg, @RequestParam String password_reg) throws Exception { + AttackResult attackResult = checkArguments(username_reg, email_reg, password_reg); + + if (attackResult == null) { + Connection connection = DatabaseUtilities.getConnection(webSession); + checkDatabase(connection); + + String checkUserQuery = "select userid from " + USERS_TABLE_NAME + " where userid = '" + username_reg + "'"; + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(checkUserQuery); + + if (resultSet.next()) { + attackResult = failed().feedback("user.exists").feedbackArgs(username_reg).build(); + } else { + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO " + USERS_TABLE_NAME + " VALUES (?, ?, ?)"); + preparedStatement.setString(1, username_reg); + preparedStatement.setString(2, email_reg); + preparedStatement.setString(3, password_reg); + preparedStatement.execute(); + attackResult = success().feedback("user.created").feedbackArgs(username_reg).build(); + } + } + return attackResult; + } + + private AttackResult checkArguments(String username_reg, String email_reg, String password_reg) { + if (StringUtils.isEmpty(username_reg) || StringUtils.isEmpty(email_reg) || StringUtils.isEmpty(password_reg)) { + return failed().feedback("input.invalid").build(); + } + if (username_reg.length() > 250 || email_reg.length() > 30 || password_reg.length() > 30) { + return failed().feedback("input.invalid").build(); + } + return null; + } + + @RequestMapping(method = POST) + @ResponseBody + public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception { + Connection connection = DatabaseUtilities.getConnection(webSession); + checkDatabase(connection); + + PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = ? and password = ?"); + statement.setString(1, username_login); + statement.setString(2, password_login); + ResultSet resultSet = statement.executeQuery(); + + if (resultSet.next() && "tom".equals(username_login)) { + return success().build(); + } else { + return failed().feedback("NoResultsMatched").build(); + } + } + + private void checkDatabase(Connection connection) throws SQLException { + try { + Statement statement = connection.createStatement(); + statement.execute("select 1 from " + USERS_TABLE_NAME); + } catch (SQLException e) { + createChallengeTable(connection); + } + } + + private void createChallengeTable(Connection connection) { + Statement statement = null; + try { + statement = connection.createStatement(); + String dropTable = "DROP TABLE " + USERS_TABLE_NAME; + statement.executeUpdate(dropTable); + } catch (SQLException e) { + log.info("Delete failed, this does not point to an error table might not have been present..."); + } + log.debug("Challenge 6 - Creating tables for users {}", USERS_TABLE_NAME); + try { + String createTableStatement = "CREATE TABLE " + USERS_TABLE_NAME + + " (" + "userid varchar(250)," + + "email varchar(30)," + + "password varchar(30)" + + ")"; + statement.executeUpdate(createTableStatement); + + String insertData1 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('larry', 'larry@webgoat.org', 'larryknows')"; + String insertData2 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('tom', 'tom@webgoat.org', '" + PASSWORD_TOM + "')"; + String insertData3 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**')"; + String insertData4 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('eve', 'eve@webgoat.org', '**********')"; + statement.executeUpdate(insertData1); + statement.executeUpdate(insertData2); + statement.executeUpdate(insertData3); + statement.executeUpdate(insertData4); + } catch (SQLException e) { + log.error("Unable create table", e); + } + } + +} + diff --git a/webgoat-lessons/sql-injection/src/main/resources/css/challenge.css b/webgoat-lessons/sql-injection/src/main/resources/css/challenge.css new file mode 100644 index 000000000..6a8635ae6 --- /dev/null +++ b/webgoat-lessons/sql-injection/src/main/resources/css/challenge.css @@ -0,0 +1,96 @@ +.panel-login { + border-color: #ccc; + -webkit-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); + box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); +} +.panel-login>.panel-heading { + color: #00415d; + background-color: #fff; + border-color: #fff; + text-align:center; +} +.panel-login>.panel-heading a{ + text-decoration: none; + color: #666; + font-weight: bold; + font-size: 15px; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; +} +.panel-login>.panel-heading a.active{ + color: #029f5b; + font-size: 18px; +} +.panel-login>.panel-heading hr{ + margin-top: 10px; + margin-bottom: 0px; + clear: both; + border: 0; + height: 1px; + background-image: -webkit-linear-gradient(left,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15),rgba(0, 0, 0, 0)); + background-image: -moz-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); + background-image: -ms-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); + background-image: -o-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); +} +.panel-login input[type="text"],.panel-login input[type="email"],.panel-login input[type="password"] { + height: 45px; + border: 1px solid #ddd; + font-size: 16px; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; +} +.panel-login input:hover, +.panel-login input:focus { + outline:none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + border-color: #ccc; +} +.btn-login { + background-color: #59B2E0; + outline: none; + color: #fff; + font-size: 14px; + height: auto; + font-weight: normal; + padding: 14px 0; + text-transform: uppercase; + border-color: #59B2E6; +} +.btn-login:hover, +.btn-login:focus { + color: #fff; + background-color: #53A3CD; + border-color: #53A3CD; +} +.forgot-password { + text-decoration: underline; + color: #888; +} +.forgot-password:hover, +.forgot-password:focus { + text-decoration: underline; + color: #666; +} + +.btn-register { + background-color: #1CB94E; + outline: none; + color: #fff; + font-size: 14px; + height: auto; + font-weight: normal; + padding: 14px 0; + text-transform: uppercase; + border-color: #1CB94A; +} +.btn-register:hover, +.btn-register:focus { + color: #fff; + background-color: #1CA347; + border-color: #1CA347; +} diff --git a/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionAdvanced.html b/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionAdvanced.html index 3c4a1d6ec..a4b749dc6 100644 --- a/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionAdvanced.html +++ b/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionAdvanced.html @@ -52,34 +52,115 @@
- - -
-
-
-
-
-
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
+
-
-
-
- -
-
-
- -
-
-
- -
-
-
diff --git a/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigation.html b/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigation.html deleted file mode 100644 index 3c4a1d6ec..000000000 --- a/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigation.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - -
-
-
- - -
-
-
- -
-
-
-
-
- - - - - - - -
Name:
-
-
-
-
-
-
-
- - - - - - - -
Password:
-
-
-
-
- -
-
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- - diff --git a/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigations.html b/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigations.html new file mode 100644 index 000000000..6bb7187f3 --- /dev/null +++ b/webgoat-lessons/sql-injection/src/main/resources/html/SqlInjectionMitigations.html @@ -0,0 +1,34 @@ + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/webgoat-lessons/sql-injection/src/main/resources/js/challenge.js b/webgoat-lessons/sql-injection/src/main/resources/js/challenge.js new file mode 100644 index 000000000..9107e1176 --- /dev/null +++ b/webgoat-lessons/sql-injection/src/main/resources/js/challenge.js @@ -0,0 +1,18 @@ +$(function() { + + $('#login-form-link').click(function(e) { + $("#login-form").delay(100).fadeIn(100); + $("#register-form").fadeOut(100); + $('#register-form-link').removeClass('active'); + $(this).addClass('active'); + e.preventDefault(); + }); + $('#register-form-link').click(function(e) { + $("#register-form").delay(100).fadeIn(100); + $("#login-form").fadeOut(100); + $('#login-form-link').removeClass('active'); + $(this).addClass('active'); + e.preventDefault(); + }); + +}); \ No newline at end of file diff --git a/webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_challenge.adoc b/webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_challenge.adoc new file mode 100644 index 000000000..0fdb4f2d3 --- /dev/null +++ b/webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_challenge.adoc @@ -0,0 +1,4 @@ +We now explained the basic steps involved in an SQL injection. In this assignment you will need to combine all +the things we explained in the SQL lessons. + +Have fun! \ No newline at end of file diff --git a/webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_content6c.adoc b/webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_content6c.adoc new file mode 100644 index 000000000..d81a9d1c9 --- /dev/null +++ b/webgoat-lessons/sql-injection/src/main/resources/lessonPlans/en/SqlInjection_content6c.adoc @@ -0,0 +1,59 @@ +=== Blind SQL Injection + +Blind SQL injection is a type of SQL injection attack that asks the database true or false +questions and determines the answer based on the applications response. This attack is often used when the web +application is configured to show generic error messages, but has not mitigated the code that is vulnerable to SQL +injection. + +==== Difference + +Let's first start with the difference between a normal SQL injection and a blind SQL injection. In a normal +SQL injection the error messages from the database are displayed and gives enough information to find out how +the query is working. Or in the case of an union based SQL injection the application does not reflect the information +directly on the webpage. So in the case where nothing is displayed you will need to start asking the database questions +based on a true or false statement. That's why a blind SQL injection is much more difficult to exploit. + +There are several different types of blind SQL injections: content based and time based SQL injections. + + +==== Example + +In this case we are trying to ask the database a boolean question based on for example a unique id, for example +suppose we have the following url: `https://my-shop.com?article=4` +On the server side this query will be translated as follows: + +---- +SELECT * from articles where article_id = 4 +---- + +When we want to exploit this we change the url into: `https://my-shop.com?article=4 AND 1=1` +This will be translated to: + +---- +SELECT * from articles where article_id = 4 AND 1 = 1 +---- + +If the browser will return the same page as it used to when using `https://my-shop.com?article=4` you know the +website is vulnerable for a blind SQL injection. +If the browser responds with a page not found or something else you know a blind SQL injection might not work. +You can now change the SQL query and test for example: `https://my-shop.com?article=4 AND 1=2` which will not return +anything because the query returns false. + +So but how do we actually take advantage of this? Above we only asked the database for trivial question but you can +for example also use the following url: `https://my-shop.com?article=4 AND substring(database_version(),1,1) = 2` + +Most of the time you start by finding which type of database is used, based on the type of database you can find +the system tables of the database you can enumerate all the tables present in the database. With this information +you can start getting information from all the tables and you are able to dump the database. +Be aware that this approach might not work if the privileges of the database are setup correctly (meaning the +system tables cannot be queried with the user used to connect from the web application to the database). + + +Another way is called a time based SQL injection, in this case you will ask the database to wait before returning +the result. You might need to use this if you are totally blind so there is no difference between the response you +can use for example: + +---- +article = 4; sleep(10) -- +---- +