Add two more assignments for SQL injection where only filtering is applied.

This commit is contained in:
Nanne Baars 2020-04-13 15:17:43 +02:00 committed by Nanne Baars
parent 122cc323f2
commit 407e19638f
16 changed files with 341 additions and 75 deletions

View File

@ -227,14 +227,9 @@ public abstract class IntegrationTest {
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult));
}
//TODO is prefix useful? not every lesson endpoint needs to start with a certain prefix (they are only required to be in the same package)
public void checkResults(String prefix) {
Assert.assertThat(RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc"))
.then()
.statusCode(200).extract().jsonPath().getList("solved"), CoreMatchers.everyItem(CoreMatchers.is(true)));
checkResults();
Assert.assertThat(RestAssured.given()
.when()
@ -246,6 +241,16 @@ public abstract class IntegrationTest {
}
public void checkResults() {
Assert.assertThat(RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc"))
.then()
.statusCode(200).extract().jsonPath().getList("solved"), CoreMatchers.everyItem(CoreMatchers.is(true)));
}
public void checkAssignment(String url, ContentType contentType, String body, boolean expectedResult) {
Assert.assertThat(
RestAssured.given()

View File

@ -38,6 +38,14 @@ public class SqlInjectionMitigationTest extends IntegrationTest {
"}");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10b"), params, true);
params.clear();
params.put("userid_sql_only_input_validation", "Smith';SELECT/**/*/**/from/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidation/attack"), params, true);
params.clear();
params.put("userid_sql_only_input_validation_on_keywords", "Smith';SESELECTLECT/**/*/**/FRFROMOM/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidationOnKeywords/attack"), params, true);
RestAssured.given()
.when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
@ -57,7 +65,6 @@ public class SqlInjectionMitigationTest extends IntegrationTest {
params.put("ip", "104.130.219.202");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack12a"), params, true);
checkResults("/SqlInjectionMitigations/");
checkResults();
}
}

View File

@ -53,7 +53,7 @@ public class SqlInjectionLesson6a extends AssignmentEndpoint {
// The answer: Smith' union select userid,user_name, password,cookie,cookie, cookie,userid from user_system_data --
}
protected AttackResult injectableQuery(String accountName) {
public AttackResult injectableQuery(String accountName) {
String query = "";
try (Connection connection = dataSource.getConnection()) {
boolean usedUnion = true;

View File

@ -38,13 +38,13 @@ import java.sql.ResultSet;
import java.sql.SQLException;
@RestController
@AssignmentHints(value = {"SqlStringInjectionHint-mitigation-12a-1", "SqlStringInjectionHint-mitigation-12a-2", "SqlStringInjectionHint-mitigation-12a-3", "SqlStringInjectionHint-mitigation-12a-4"})
@AssignmentHints(value = {"SqlStringInjectionHint-mitigation-13-1", "SqlStringInjectionHint-mitigation-13-2", "SqlStringInjectionHint-mitigation-13-3", "SqlStringInjectionHint-mitigation-13-4"})
@Slf4j
public class SqlInjectionLesson12a extends AssignmentEndpoint {
public class SqlInjectionLesson13 extends AssignmentEndpoint {
private final DataSource dataSource;
public SqlInjectionLesson12a(DataSource dataSource) {
public SqlInjectionLesson13(DataSource dataSource) {
this.dataSource = dataSource;
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2002 - 2019 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 https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
package org.owasp.webgoat.sql_injection.mitigation;
import org.apache.commons.lang3.StringEscapeUtils;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.sql_injection.advanced.SqlInjectionLesson6a;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AssignmentHints(value = {"SqlOnlyInputValidation-1", "SqlOnlyInputValidation-2", "SqlOnlyInputValidation-3"})
public class SqlOnlyInputValidation extends AssignmentEndpoint {
private final SqlInjectionLesson6a lesson6a;
public SqlOnlyInputValidation(SqlInjectionLesson6a lesson6a) {
this.lesson6a = lesson6a;
}
@PostMapping("/SqlOnlyInputValidation/attack")
@ResponseBody
public AttackResult attack(@RequestParam("userid_sql_only_input_validation") String userId) {
if (userId.contains(" ")) {
return failed(this).feedback("SqlOnlyInputValidation-failed").build();
}
AttackResult attackResult = lesson6a.injectableQuery(userId);
return new AttackResult(attackResult.isLessonCompleted(), attackResult.getFeedback(), attackResult.getOutput(), getClass().getSimpleName());
}
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2002 - 2019 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 https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
package org.owasp.webgoat.sql_injection.mitigation;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.sql_injection.advanced.SqlInjectionLesson6a;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AssignmentHints(value = {"SqlOnlyInputValidationOnKeywords-1", "SqlOnlyInputValidationOnKeywords-2", "SqlOnlyInputValidationOnKeywords-3"})
public class SqlOnlyInputValidationOnKeywords extends AssignmentEndpoint {
private final SqlInjectionLesson6a lesson6a;
public SqlOnlyInputValidationOnKeywords(SqlInjectionLesson6a lesson6a) {
this.lesson6a = lesson6a;
}
@PostMapping("/SqlOnlyInputValidationOnKeywords/attack")
@ResponseBody
public AttackResult attack(@RequestParam("userid_sql_only_input_validation_on_keywords") String userId) {
userId = userId.toUpperCase().replace("FROM", "").replace("SELECT", "");
if (userId.contains(" ")) {
return failed(this).feedback("SqlOnlyInputValidationOnKeywords-failed").build();
}
AttackResult attackResult = lesson6a.injectableQuery(userId);
return new AttackResult(attackResult.isLessonCompleted(), attackResult.getFeedback(), attackResult.getOutput(), getClass().getSimpleName());
}
}

View File

@ -66,14 +66,60 @@
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:SqlInjection_content12.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:SqlInjection_content12a.adoc"></div>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/SqlOnlyInputValidation/attack"
enctype="application/json;charset=UTF-8">
<table>
<tr>
<td>Name:</td>
<td><input name="userid_sql_only_input_validation" value="" type="TEXT"/></td>
<td><input
name="Get Account Info" value="Get Account Info" type="SUBMIT"/></td>
<td></td>
</tr>
</table>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:SqlInjection_content12b.adoc"></div>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/SqlOnlyInputValidationOnKeywords/attack"
enctype="application/json;charset=UTF-8">
<table>
<tr>
<td>Name:</td>
<td><input name="userid_sql_only_input_validation_on_keywords" value="" type="TEXT"/></td>
<td><input
name="Get Account Info" value="Get Account Info" type="SUBMIT"/></td>
<td></td>
</tr>
</table>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:SqlInjection_content13.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:SqlInjection_order_by.adoc"></div>
<script th:src="@{/lesson_js/assignment12.js}" language="JavaScript"></script>
<script th:src="@{/lesson_js/assignment13.js}" language="JavaScript"></script>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
@ -145,7 +191,7 @@
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:SqlInjection_content13.adoc"></div>
<div class="adoc-content" th:replace="doc:SqlInjection_content14.adoc"></div>
</div>
</html>

View File

@ -93,12 +93,22 @@ SqlStringInjectionHint-mitigation-10b-3=The wildcard symbol '?' in a prepared st
SqlStringInjectionHint-mitigation-10b-4=Make sure to execute your statement.
SqlStringInjectionHint-mitigation-10b-5=View the previous lesson to check back on how you can build set up a connection.
SqlStringInjectionHint-mitigation-12a-1=Try sorting and look at the request
SqlStringInjectionHint-mitigation-12a-2=Intercept the request and try to specify a different order by
SqlStringInjectionHint-mitigation-12a-3=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
SqlStringInjectionHint-mitigation-12a-4=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
SqlStringInjectionHint-mitigation-13-1=Try sorting and look at the request
SqlStringInjectionHint-mitigation-13-2=Intercept the request and try to specify a different order by
SqlStringInjectionHint-mitigation-13-3=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
SqlStringInjectionHint-mitigation-13-4=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
SqlInjectionChallengeHint1=The table name is randomized at each start of WebGoat, try to figure out the name first.
SqlInjectionChallengeHint2=Find the field which is vulnerable to SQL injection use that to change the password.
SqlInjectionChallengeHint3=Change the password through an UPDATE Statement.
SqlInjectionChallengeHint4=The vulnerable field is the username field of the register form.
SqlInjectionChallengeHint4=The vulnerable field is the username field of the register form.
SqlOnlyInputValidation-failed=Using spaces is not allowed!
SqlOnlyInputValidation-1=Spaces are rejected, try to find a way around this restriction
SqlOnlyInputValidation-2=Try to use a comment in the query
SqlOnlyInputValidation-3=WebGoat uses HSQLDB as a database can you use one of them to make skip the filtering?
SqlOnlyInputValidationOnKeywords-failed=Use of spaces and/or SQL keywords are not allowed!
SqlOnlyInputValidationOnKeywords-1=Spaces are and SQL keywords are rejected, try to find a way around this restriction
SqlOnlyInputValidationOnKeywords-2=Try to use a comment in the query
SqlOnlyInputValidationOnKeywords-3=WebGoat uses HSQLDB as a database can you use one of them to make skip the filtering?

View File

@ -1,47 +1,11 @@
== Order by clause
== Input validation alone is not enough!!
Question: Does a prepared statement always prevent against an SQL injection?
Answer: No it does not
You need to do both use parametrized queries and validate the input received from the user. On StackOverflow you will
see alot of answers stating that input validation is enough. *However* it only takes you so far before you know it
the validation is broken and you have an SQL injection in your application.
Let us take a look at the following statement:
A nice read why it is not enough can be found https://twitter.com/marcan42/status/1238004834806067200?s=21
----
SELECT * FROM users ORDER BY lastname;
----
Let's repeat one of the previous assignments, the developer fixed the possible SQL injection with filtering, can you
spot the weakness in this approach?
If we look at the specification of the SQL grammar the definition is as follows:
----
SELECT ...
FROM tableList
[WHERE Expression]
[ORDER BY orderExpression [, ...]]
orderExpression:
{ columnNr | columnAlias | selectExpression }
[ASC | DESC]
selectExpression:
{ Expression | COUNT(*) | {
COUNT | MIN | MAX | SUM | AVG | SOME | EVERY |
VAR_POP | VAR_SAMP | STDDEV_POP | STDDEV_SAMP
} ([ALL | DISTINCT][2]] Expression) } [[AS] label]
Based on HSQLDB
----
This means an `orderExpression` can be a `selectExpression` which can be a function as well, so for example with
a `case` statement we might be able to ask the database some questions, like:
----
SELECT * FROM users ORDER BY (CASE WHEN (TRUE) THEN lastname ELSE firstname)
----
So we can substitute any kind of boolean operation in the `when(....)` part. The statement will just work because
it is a valid query whether you use a prepared statement or not an order by clause can by definition contain a
expression.
=== Mitigation
If you need to provide a sorting column in your web application you should implement a whitelist to validate the value
of the `order by` statement it should always be limited to something like 'firstname' or 'lastname'.

View File

@ -0,0 +1,7 @@
== Input validation alone is not enough!!
So the last attempt to validate if the query did not contain any spaces failed, the development team went further
into the direction of only performing input validation, can you find out where it went wrong this time?

View File

@ -1,14 +1,47 @@
== Least Privilege
== Order by clause
=== Connect with a minimum set of privileges
* The application should connect to the database with different credentials for every trust distinction
* Applications rarely need delete rights to a table or database
Question: Does a prepared statement always prevent against an SQL injection?
Answer: No it does not
=== Database accounts should limit schema access
Let us take a look at the following statement:
=== Define database accounts for read and read/write access
----
SELECT * FROM users ORDER BY lastname;
----
=== Multiple connection pools based on access
* Use read only access for the authentication query
* Use read/write access for the data modification queries
* Use execute for access to stored procedure calls
If we look at the specification of the SQL grammar the definition is as follows:
----
SELECT ...
FROM tableList
[WHERE Expression]
[ORDER BY orderExpression [, ...]]
orderExpression:
{ columnNr | columnAlias | selectExpression }
[ASC | DESC]
selectExpression:
{ Expression | COUNT(*) | {
COUNT | MIN | MAX | SUM | AVG | SOME | EVERY |
VAR_POP | VAR_SAMP | STDDEV_POP | STDDEV_SAMP
} ([ALL | DISTINCT][2]] Expression) } [[AS] label]
Based on HSQLDB
----
This means an `orderExpression` can be a `selectExpression` which can be a function as well, so for example with
a `case` statement we might be able to ask the database some questions, like:
----
SELECT * FROM users ORDER BY (CASE WHEN (TRUE) THEN lastname ELSE firstname)
----
So we can substitute any kind of boolean operation in the `when(....)` part. The statement will just work because
it is a valid query whether you use a prepared statement or not an order by clause can by definition contain a
expression.
=== Mitigation
If you need to provide a sorting column in your web application you should implement a whitelist to validate the value
of the `order by` statement it should always be limited to something like 'firstname' or 'lastname'.

View File

@ -0,0 +1,14 @@
== Least Privilege
=== Connect with a minimum set of privileges
* The application should connect to the database with different credentials for every trust distinction
* Applications rarely need delete rights to a table or database
=== Database accounts should limit schema access
=== Define database accounts for read and read/write access
=== Multiple connection pools based on access
* Use read only access for the authentication query
* Use read/write access for the data modification queries
* Use execute for access to stored procedure calls

View File

@ -15,7 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @since 5/21/17.
*/
@RunWith(SpringJUnit4ClassRunner.class)
public class SqlInjectionLesson12aTest extends SqlLessonTest {
public class SqlInjectionLesson13Test extends SqlLessonTest {
@Test
public void knownAccountShouldDisplayData() throws Exception {

View File

@ -0,0 +1,34 @@
package org.owasp.webgoat.sql_injection.mitigation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.owasp.webgoat.sql_injection.SqlLessonTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
public class SqlOnlyInputValidationOnKeywordsTest extends SqlLessonTest {
@Test
public void solve() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/SqlOnlyInputValidationOnKeywords/attack")
.param("userid_sql_only_input_validation_on_keywords", "Smith';SESELECTLECT/**/*/**/FRFROMOM/**/user_system_data;--"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(true)))
.andExpect(jsonPath("$.feedback", containsString("passW0rD")));
}
@Test
public void containsForbiddenSqlKeyword() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/SqlOnlyInputValidationOnKeywords/attack")
.param("userid_sql_only_input_validation_on_keywords", "Smith';SELECT/**/*/**/from/**/user_system_data;--"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(false)))
.andExpect(jsonPath("$.output", containsString("unexpected token: *<br> Your query was: SELECT * FROM user_data WHERE last_name = 'SMITH';\\\\\\/**\\\\\\/*\\\\\\/**\\\\\\/\\\\\\/**\\\\\\/USER_SYSTEM_DATA;--'")));
}
}

View File

@ -0,0 +1,34 @@
package org.owasp.webgoat.sql_injection.mitigation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.owasp.webgoat.sql_injection.SqlLessonTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
public class SqlOnlyInputValidationTest extends SqlLessonTest {
@Test
public void solve() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/SqlOnlyInputValidation/attack")
.param("userid_sql_only_input_validation", "Smith';SELECT/**/*/**/from/**/user_system_data;--"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(true)))
.andExpect(jsonPath("$.feedback", containsString("passW0rD")));
}
@Test
public void containsSpace() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/SqlOnlyInputValidation/attack")
.param("userid_sql_only_input_validation", "Smith' ;SELECT from user_system_data;--"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(false)))
.andExpect(jsonPath("$.feedback", containsString("Using spaces is not allowed!")));
}
}