Adding extra lesson for order by clauses

This commit is contained in:
Nanne Baars 2017-06-15 19:02:51 +02:00
parent ee912f734b
commit a484467419
20 changed files with 1246 additions and 938 deletions

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.FileSystemUtils;
import javax.annotation.PostConstruct;
import java.io.File;
@ -23,14 +24,6 @@ public class CleanupLocalProgressFiles {
@PostConstruct
public void clean() {
File dir = new File(webgoatHome);
if (dir.exists()) {
File[] progressFiles = dir.listFiles(f -> f.getName().endsWith(".progress"));
if (progressFiles != null) {
log.info("Removing stored user preferences...");
for (File f : progressFiles) {
f.delete();
}
}
}
FileSystemUtils.deleteRecursively(dir);
}
}

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.advanced;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.advanced;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.introduction;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,5 +1,5 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.introduction;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.introduction;
import org.owasp.webgoat.assignments.AssignmentEndpoint;

View File

@ -1,5 +1,5 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.introduction;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
@ -14,8 +14,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.sql.*;
import static org.owasp.webgoat.plugin.SqlInjectionLesson5a.writeTable;
/***************************************************************************************************
*
@ -74,7 +72,7 @@ public class SqlInjectionLesson6a extends AssignmentEndpoint {
ResultSetMetaData resultsMetaData = results.getMetaData();
StringBuffer output = new StringBuffer();
output.append(writeTable(results, resultsMetaData));
output.append(SqlInjectionLesson5a.writeTable(results, resultsMetaData));
results.last();
// If they get back more than one user they succeeded

View File

@ -1,5 +1,5 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.introduction;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;

View File

@ -0,0 +1,56 @@
package org.owasp.webgoat.plugin.mitigation;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import org.owasp.webgoat.session.DatabaseUtilities;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
/**
* @author nbaars
* @since 6/13/17.
*/
@RestController
@RequestMapping("SqlInjection/servers")
public class Servers {
@AllArgsConstructor
@Getter
private class Server {
private String id;
private String hostname;
private String ip;
private String mac;
private String status;
private String description;
}
@Autowired
private WebSession webSession;
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@SneakyThrows
@ResponseBody
public List<Server> sort(@RequestParam String column) {
Connection connection = DatabaseUtilities.getConnection(webSession);
PreparedStatement preparedStatement = connection.prepareStatement("select id, hostname, ip, mac, status, description from servers where status <> 'out of order' order by " + column);
ResultSet rs = preparedStatement.executeQuery();
List<Server> servers = Lists.newArrayList();
while (rs.next()) {
Server server = new Server(rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5), rs.getString(6));
servers.add(server);
}
return servers;
}
}

View File

@ -0,0 +1,46 @@
package org.owasp.webgoat.plugin.mitigation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.sql.*;
/**
* @author nbaars
* @since 6/13/17.
*/
@AssignmentPath("SqlInjection/attack12a")
@AssignmentHints(value = {"SqlStringInjectionHint8", "SqlStringInjectionHint9", "SqlStringInjectionHint10", "SqlStringInjectionHint11"})
@Slf4j
public class SqlInjectionLesson12a extends AssignmentEndpoint {
@Autowired
private WebSession webSession;
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
@SneakyThrows
public AttackResult completed(@RequestParam String ip) {
Connection connection = DatabaseUtilities.getConnection(webSession);
PreparedStatement preparedStatement = connection.prepareStatement("select ip from servers where ip = ?");
preparedStatement.setString(1, ip);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
return trackProgress(success().build());
}
return trackProgress(failed().build());
}
}

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.plugin;
package org.owasp.webgoat.plugin.mitigation;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;

View File

@ -27,6 +27,84 @@
<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>
<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>
<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/SqlInjection/attack12a"
enctype="application/json;charset=UTF-8">
<div class="container-fluid">
<div class="row">
<div class="panel panel-primary">
<div class="panel-heading">
<h3>List of servers
<div class="pull-right">
<button id="btn-admin" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit
</button>
</div>
</h3>
</div>
<div id="toolbar-admin" class="panel-body">
<div class="btn-toolbar" role="toolbar" aria-label="admin">
<div class="btn-group pull-right" role="group">
<button id="btn-online" type="button" class="btn btn-success">Online</button>
<button id="btn-offline" type="button" class="btn btn-warning">Offline</button>
<button id="btn-out-of-order" type="button" class="btn btn-danger">Out Of Order
</button>
</div>
</div>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th class="col-check"></th>
<th></th>
<th>Hostname <span onclick="getServers('hostname')"><i
class="fa fa-fw fa-sort"></i></span>
</th>
<th>IP <span onclick="getServers('ip')"><i class="fa fa-fw fa-sort"></i></span></th>
<th>MAC <span onclick="getServers('mac')"><i class="fa fa-fw fa-sort"></i></span></th>
<th>Status <span onclick="getServers('status')"><i class="fa fa-fw fa-sort"></i></span>
</th>
<th>Description <span onclick="getServers('description')"><i
class="fa fa-fw fa-sort"></i></span>
</th>
</tr>
</thead>
<tbody id="servers">
</tbody>
</table>
</div>
</div>
<br/>
<br/>
</div>
</form>
<form class="attack-form" method="POST" name="form" action="SqlInjection/attack12a">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">IP address webgoat-prd server:</div>
<input type="text" class="form-control" id="ip" name="ip"
placeholder="192.1.0.12"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</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>

View File

@ -9,6 +9,10 @@ SqlStringInjectionHint4=Try entering [ smith' OR '1' = '1 ].
SqlStringInjectionHint5=First try to find out the number of columns by adding a group by 1,2,3 etc to the query.
SqlStringInjectionHint6=Try adding a union to the query, the number of columns should match.
SqlStringInjectionHint7=Try entering [ Smith' union select userid,user_name, password,cookie,cookie, cookie,userid from user_system_data -- ].
SqlStringInjectionHint8=Try sorting and look at the request
SqlStringInjectionHint9=Intercept the request and try to specify a different order by
SqlStringInjectionHint10=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
SqlStringInjectionHint11=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
sql-injection.5a.success=You have succeed: {0}
sql-injection.5a.no.results=No results matched. Try Again.

View File

@ -0,0 +1,61 @@
$(function () {
$('.col-check').hide();
$('#btn-admin').on('click', function () {
if ($("#toolbar-admin").is(":visible")) {
$("#toolbar-admin").hide();
$(".col-check").hide();
}
else {
$("#toolbar-admin").show();
$(".col-check").show();
}
});
$('#btn-online').on('click', function () {
$('table tr').filter(':has(:checkbox:checked)').find('td').parent().removeClass().addClass('success');
$('table tr').filter(':has(:checkbox:checked)').find('td.status').text('online');
});
$('#btn-offline').on('click', function () {
$('table tr').filter(':has(:checkbox:checked)').find('td').parent().removeClass().addClass('warning');
$('table tr').filter(':has(:checkbox:checked)').find('td.status').text('offline');
});
$('#btn-out-of-order').on('click', function () {
$('table tr').filter(':has(:checkbox:checked)').find('td').parent().removeClass().addClass('danger');
$('table tr').filter(':has(:checkbox:checked)').find('td.status').text('out of order');
});
});
$(document).ready(function () {
getServers('id');
});
var html = '<tr class="STATUS">' +
'<td class="col-check"><input type="checkbox" class="form-check-input"/></td>' +
'<td>HOSTNAME</td>' +
'<td>IP</td>' +
'<td>MAC</td>' +
'<td class="status">ONLINE</td>' +
'<td>DESCRIPTION</td>' +
'</tr>';
function getServers(column) {
$.get("SqlInjection/servers?column=" + column, function (result, status) {
$("#servers").empty();
for (var i = 0; i < result.length; i++) {
var server = html.replace('ID', result[i].id);
var status = "success";
if (result[i].status === 'offline') {
status = "danger";
}
server = server.replace('ONLINE', status);
server = server.replace('STATUS', status);
server = server.replace('HOSTNAME', result[i].hostname);
server = server.replace('IP', result[i].ip);
server = server.replace('MAC', result[i].mac);
server = server.replace('DESCRIPTION', result[i].description);
$("#servers").append(server);
}
});
}

View File

@ -0,0 +1,48 @@
== Order by clause
Question: Does a preparared statement always prevent against an SQL injection?
Answer: No it does not
Let's take a look at the following statement:
----
select * from users order by lastname;
----
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` van 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
(select 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

@ -1,11 +1,11 @@
=== Blind SQL Injection
== 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
=== 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
@ -16,7 +16,7 @@ based on a true or false statement. That's why a blind SQL injection is much mor
There are several different types of blind SQL injections: content based and time based SQL injections.
==== Example
=== 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`

View File

@ -0,0 +1,4 @@
In this assignment try to perform an SQL injection through the ORDER BY field.
Try to find the ip address of the `webgoat-prd` server.
Note: The submit field of this assignment is *NOT* vulnerable for an SQL injection.

View File

@ -0,0 +1,80 @@
package org.owasp.webgoat.plugin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.owasp.webgoat.plugin.introduction.SqlInjection;
import org.owasp.webgoat.plugins.LessonTest;
import org.owasp.webgoat.session.WebgoatContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author nbaars
* @since 5/21/17.
*/
@RunWith(SpringJUnit4ClassRunner.class)
public class SqlInjectionLesson12aTest extends LessonTest {
@Autowired
private WebgoatContext context;
@Before
public void setup() throws Exception {
SqlInjection sql = new SqlInjection();
when(webSession.getCurrentLesson()).thenReturn(sql);
when(webSession.getWebgoatContext()).thenReturn(context);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void knownAccountShouldDisplayData() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
.param("column", "id"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
@Test
public void trueShouldSortByHostname() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
.param("column", "(case when (true) then hostname else id end)"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
}
@Test
public void falseShouldSortById() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
.param("column", "(case when (true) then hostname else id end)"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
}
@Test
public void passwordIncorrectShouldOrderByHostname() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
.param("column", "CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-prd') LIKE '192.%' THEN hostname ELSE id END"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-dev")));
}
@Test
public void passwordCorrectShouldOrderByHostname() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
.param("column", "CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-prd') LIKE '104.%' THEN hostname ELSE id END"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
}
}

View File

@ -3,6 +3,7 @@ package org.owasp.webgoat.plugin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.owasp.webgoat.plugin.introduction.SqlInjection;
import org.owasp.webgoat.plugins.LessonTest;
import org.owasp.webgoat.session.WebgoatContext;
import org.springframework.beans.factory.annotation.Autowired;