Skip to content

Commit

Permalink
Merge pull request #9 from DataDog/file-read-vuln
Browse files Browse the repository at this point in the history
Add arbitrary file read vuln
  • Loading branch information
christophetd authored Jun 12, 2024
2 parents 09ccb17 + df889a0 commit 2d020bb
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 26 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ RUN wget -O dd-java-agent.jar https://github.com/DataDog/dd-trace-java/releases/

# Utility
RUN apk add curl wget
RUN mkdir -p /tmp/files && echo "hello" > /tmp/files/hello.txt && echo "world" > /tmp/files/foo.txt

#CMD ["sh", "-c", "export INSTANCE_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4); export DD_TRACE_AGENT_URL=http://$INSTANCE_IP:8126; java -javaagent:/app/dd-java-agent.jar -jar /app/spring-boot-application.jar"]
CMD ["java", "-javaagent:/app/dd-java-agent.jar", "-jar", "/app/spring-boot-application.jar"]
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ You can then access the web application at http://127.0.0.1:8000
3. Note that there is some level of input validation - entering `$(whoami)` returns `Invalid domain name: $(whoami) - don't try to hack us!`
4. However, the validation is buggy - notice how you can start the input with a domain name, and execute and command in the container!

### Local file read vulnerability

1. Browse to http://127.0.0.1:8000/file.html
2. Note how the input allows you to specify file names such as `/tmp/files/hello.txt` and read them
3. Note that there is some level of input validation - entering `/etc/passwd` returns `You are not allowed to read /etc/passwd`
4. However, the validation is buggy and vulnerable to path traversal. For instance, you can enter `/tmp/files/../../etc/passwd` to bypass the validation and read any file on the local filesystem.

![image](https://user-images.githubusercontent.com/136675/186954376-e3d82d03-7d9e-49b3-a106-6da080980dae.png)

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package com.datadoghq.workshops.samplevulnerablejavaapp;
package com.datadoghq.workshops.samplevulnerablejavaapp.controller;

import com.datadoghq.workshops.samplevulnerablejavaapp.exception.FileForbiddenFileException;
import com.datadoghq.workshops.samplevulnerablejavaapp.exception.FileReadException;
import com.datadoghq.workshops.samplevulnerablejavaapp.exception.InvalidDomainException;
import com.datadoghq.workshops.samplevulnerablejavaapp.exception.UnableToTestDomainException;
import com.datadoghq.workshops.samplevulnerablejavaapp.http.DomainTestRequest;
import com.datadoghq.workshops.samplevulnerablejavaapp.http.ViewFileRequest;
import com.datadoghq.workshops.samplevulnerablejavaapp.http.WebsiteTestRequest;
import com.datadoghq.workshops.samplevulnerablejavaapp.service.DomainTestService;
import com.datadoghq.workshops.samplevulnerablejavaapp.service.FileService;
import com.datadoghq.workshops.samplevulnerablejavaapp.service.WebsiteTestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -20,6 +28,9 @@ public class MainController {
@Autowired
private WebsiteTestService websiteTestService;

@Autowired
private FileService fileService;

@RequestMapping(method=RequestMethod.POST, value="/test-domain", consumes="application/json")
public ResponseEntity<String> testDomain(@RequestBody DomainTestRequest request) {
log.info("Testing domain " + request.domainName);
Expand All @@ -42,4 +53,17 @@ public ResponseEntity<String> testWebsite(@RequestBody WebsiteTestRequest reques
return new ResponseEntity<>(result, HttpStatus.OK);
}

@RequestMapping(method=RequestMethod.POST, value="/view-file", consumes="application/json")
public ResponseEntity<String> viewFile(@RequestBody ViewFileRequest request) {
log.info("Reading file " + request.path);
try {
String result = fileService.readFile(request.path);
return new ResponseEntity<>(result, HttpStatus.OK);
} catch (FileForbiddenFileException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
} catch (FileReadException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.exception;

import lombok.experimental.StandardException;

public class DomainTestException extends Exception {
public DomainTestException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.exception;

public class FileForbiddenFileException extends Exception {
public FileForbiddenFileException(String message) {
super(message);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.exception;

public class FileReadException extends Exception {
public FileReadException(String message) {
super(message);
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.exception;

import com.datadoghq.workshops.samplevulnerablejavaapp.exception.DomainTestException;

public class InvalidDomainException extends DomainTestException {
public InvalidDomainException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.http;

import lombok.Data;

@Data
public class DomainTestRequest {
public String domainName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.http;

import lombok.Data;

@Data
public class ViewFileRequest {
public String path;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.http;

import lombok.Data;

@Data
public class WebsiteTestRequest {
public String url;
public String customHeaderKey;
public String customHeaderValue;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.datadoghq.workshops.samplevulnerablejavaapp;
package com.datadoghq.workshops.samplevulnerablejavaapp.service;

import com.datadoghq.workshops.samplevulnerablejavaapp.exception.DomainTestException;
import com.datadoghq.workshops.samplevulnerablejavaapp.exception.InvalidDomainException;
import com.datadoghq.workshops.samplevulnerablejavaapp.exception.UnableToTestDomainException;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.datadoghq.workshops.samplevulnerablejavaapp.service;

import com.datadoghq.workshops.samplevulnerablejavaapp.exception.FileForbiddenFileException;
import com.datadoghq.workshops.samplevulnerablejavaapp.exception.FileReadException;
import org.springframework.stereotype.Service;

import java.io.*;

@Service
public class FileService {
final static String ALLOWED_PREFIX = "/tmp/files/";

public String readFile(String path) throws FileForbiddenFileException, FileReadException {
if(!path.startsWith(ALLOWED_PREFIX)) {
throw new FileForbiddenFileException("You are not allowed to read " + path);
}
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
StringBuilder sb = new StringBuilder();
String line = br.readLine();

while (line != null) {
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
}
return sb.toString();
} catch (IOException e) {
throw new FileReadException(e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.datadoghq.workshops.samplevulnerablejavaapp;
package com.datadoghq.workshops.samplevulnerablejavaapp.service;

import com.datadoghq.workshops.samplevulnerablejavaapp.http.WebsiteTestRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
Expand Down
69 changes: 69 additions & 0 deletions src/main/resources/static/file.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<html>
<head>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">

<meta name="viewport" content="width=device-width, initial-scale=1">
<title>File viewer</title>
</head>
<body>
<div class="container">

<div class="page-header">
<h1>File viewer</h1>
<p class="lead"></p>
</div>

<div class="row">
<div class="col">
<p>View files on this server. <b>For security reasons, you can only access samples files under <code>/tmp/files</code></b>.</p>

<p>Available files:
<ul>
<li><code>/tmp/files/hello.txt</code></li>
<li><code>/tmp/files/foo.txt</code></li>
</ul>
</p>
</div>
</div>


<div class="row">
<form>
<div class="form-group row">
<label for="path" class="col-sm-2 col-form-label">File path:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="path" placeholder="/tmp/files/something.txt">
</div>
</div>

<button type="submit" class="btn btn-primary mb-2">View file</button>
</form>
</div>

<div class="row">
<div id="output-container" class="alert alert-secondary hidden" role="alert">
<pre id="output" style="white-space: pre-wrap"></pre>
</div>
</div>

<div class="row">
<div id="error-container" class="alert alert-danger hidden" role="alert">
<div id="error"></div>
</div>
</div>
</div>

</body>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
crossorigin="anonymous"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"
integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
crossorigin="anonymous"></script>

<script src="/js/file.js"></script>
</html>
37 changes: 37 additions & 0 deletions src/main/resources/static/js/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
var outputContainer = document.getElementById('output-container')
var outputElement = document.getElementById('output')
var errorContainer = document.getElementById('error-container')
var errorElement = document.getElementById('error')

function updateOutput(result) {
// If there is any error, hide it first
errorContainer.classList.add('hidden');
outputElement.innerText = result;
outputContainer.classList.remove('hidden');
}

function handleError(error) {
// If there is any successful output, hide it first
outputContainer.classList.add('hidden');
errorElement.innerText = error.responseText;
errorContainer.classList.remove('hidden');
}


function submitRequest() {
$.ajax({
url: '/view-file',
method: 'POST',
contentType: 'application/json',
accept: 'application/json',
data: JSON.stringify({
'path': document.getElementById('path').value || ''
}),
success: updateOutput,
error: handleError
})
}


var form = document.querySelectorAll('form')[0]
form.addEventListener('submit', function(evt) { evt.preventDefault(); submitRequest(); })

0 comments on commit 2d020bb

Please sign in to comment.