Security is an important topic for developers however security is often an afterthought in a project. This presentation will focus on practices which developers need to be aware of, and make security fun again. This is an in depth talk about 10 topics not an overview for security best practices.
2. About me
¨ Java developer
¨ Developer à Security consultant à Developer
¨ Project lead of WebGoat à
https://github.com/WebGoat/
3. WebGoat is…
¨ A deliberately vulnerable web application maintained
by OWASP designed to teach web application security lessons.
¨ In each lesson, users must demonstrate their understanding of a
security issue by exploiting a real vulnerability in the WebGoat
application
20. Within projects as developers…
¨ Make sure secrets do not end up in Git
¤ Encrypt your secrets (for example like Travis CI)
¤ More fancy use Vault, KeyCloak etc
¨ Use tooling to scan your repository
¨ Define a policy what should be done in case it happens
¤ Git history
21. As a team…
¨ Think about what to do when a team member leaves…
¤ Think about how many systems you have access to, is the access to AWS,
Kubernetes, Github, Gitlab, Jira etc centrally provided?
¨ Again, have a clear policy in place
26. “In this post I will show you how to use RSA in Java…..”
public static String encrypt(String plainText, PublicKey publicKey) {
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = encryptCipher.doFinal(plainText.getBytes(UTF_8));
return Base64.getEncoder().encodeToString(cipherText);
}
27. public static void main(String [] args) throws Exception {
// generate public and private keys
…
// sign the message
byte [] signed = encrypt(privateKey, "This is a secret message");
System.out.println(new String(signed)); // <<signed message>>
// verify the message
byte[] verified = decrypt(pubKey, encrypted);
System.out.println(new String(verified)); // This is a secret message
}
public static byte[] encrypt(PrivateKey privateKey, String message) {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(message.getBytes());
}
public static byte[] decrypt(PublicKey publicKey, byte [] encrypted) {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(encrypted);
}
31. Path traversal
¨ A path(directory) traversal is a vulnerability where an attacker is able
to access or store files and directories outside the location where the
application is running.
¨ For example: http://example.com/file=report.pdf
¨ Change into: http://example.com/file=../../../../../etc/passwd
¨ In case of a file upload you might be able to overwrite other files
34. Mitigation in file upload
var multiPartFile = ...
var targetFile = new File("/tmp", multiPartFile.getOriginalName());
var canonicalPath = targetFile.getCanonicalPath();
if (!canonicalPath.startWith("/tmp") {
throw new IllegalArgumentException("Invalid filename");
}
IOUtils.copy(multiPartFile.getBytes(), targetFile);
35. Input validation
¨ Check for ../
¨ Be aware of encoding: %2e%2e/%2f
¨ Spring Security has: StrictHttpFirewall which automatically drops a
request if the path variable contains ../
36. @Getter("/f")
public void f(@RequestParam("name") String name) {
//name is automatically decoded so %2E%2E%2F%2E%2E%2Ftest
//will become ../../test
}
@Getter("/g")
public void g(HttpServletRequest request) {
var queryString = request.getQueryString();
// will return %2E%2E%2F%2E%2E%2Ftest
}
@Getter("/h")
public void h(HttpServletRequest request) {
var name = request.getParam("name");
//will return ../../test
37. Host-Header Injection
¨ In web applications, developers use the HTTP Host header available in
HTTP request
¨ A remote attacker can exploit this by sending a fake header with a
domain name under the attackers control.
38. Often found during password reset
curl 'https://webgoat-cloud.net/create-password-reset-link' --data-raw 'email=test1234@webgoat-cloud.net'
39. Let’s do that again…
curl 'http://webgoat-cloud.net/create-password-reset-link'
-H'Host: attacker.com'
--data-raw 'email=test1234@webgoat.org'
43. Easy to setup
¨ Standard Spring Boot / Azure auto configuration provided
1. Register your application with your Azure Active Directory Tenant
2. Configure application.properties
spring.security.oauth2.client.registration.azure.client-id=xxxxxx-your-client-id-xxxxxx
spring.security.oauth2.client.registration.azure.client-secret=xxxxxx-your-client-secret-xxxxxx
azure.activedirectory.tenant-id=xxxxxx-your-tenant-id-xxxxxx
azure.activedirectory.active-directory-groups=group1, group2
44. Spring Boot configuration
¨ We enabled this setting in the application.properties:
server.use-forward-headers=true
45. curl -i http://localhost:8080
HTTP/1.1 302 Found
Location: http://localhost:8080/oauth2/authorization/azure
curl -i http://localhost:8080/oauth2/authorization/azure
HTTP/1.1 302 Found
Location:
https://login.microsoftonline.com/common/oauth2/authorize?response_type=code
https://graph.microsoft.com/user.read&state=&
redirect_uri=http://localhost:8080/login/oauth2/code/azure
46. Now let’s try
curl -i -H"X-Forwarded-Host: attacker.com" http://localhost:8080/
HTTP/1.1 302 Found
Location: http://attacker.com/oauth2/authorization/azure
47. But wait how does the redirect_uri work?
curl -i http://localhost:8080/oauth2/authorization/azure
HTTP/1.1 302 Found
Location:
https://login.microsoftonline.com/common/oauth2/authorize?response_type=codehttps://graph.
microsoft.com/user.read&state=&redirect_uri=http://localhost:8080/login/oauth2/code/azure
spring.security.oauth2.client.registration.azure.redirect-uri-template={baseUrl}/login/oauth2/code/{registrationId}
51. Recap
¨ This is not a bug in Spring security
¨ Something which happened because we added:
server.use-forward-headers=true
52. Solution
/**
* <p>
* Determines which hostnames should be allowed. The default is to allow any
* hostname.
* </p>
*
* @param allowedHostnames the predicate for testing hostnames
* @since 5.2
*/
public void setAllowedHostnames(Predicate<String> allowedHostnames) {
if (allowedHostnames == null) {
throw new IllegalArgumentException("allowedHostnames cannot be null");
}
this.allowedHostnames = allowedHostnames;
}
53. @Bean
public HttpFirewall firewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
firewall.setAllowedHostnames(s -> s.equals("localhost"));
curl -i -H"X-Forwarded-Host: attacker.com" http://localhost:8080/
java.lang.RuntimeException: org.springframework.security.web.firewall.RequestRejectedException:
The request was rejected because the domain attacker.com is untrusted.
at io.undertow.servlet.spec.RequestDispatcherImpl.error(RequestDispatcherImpl.java:507)
at io.undertow.servlet.spec.RequestDispatcherImpl.error(RequestDispatcherImpl.java:427)
54. Solution
¨ As developers we are responsible to validate those headers
¨ Verify all headers you can receive from the outside.
¤ This includes: X-Forwarded-For, X-Forwarded-Host etc
¨ Do not rely on thinking reversed proxy will solve this!
¨ Check to see whether the framework has built in protection
55. Where to start...
1. Make developers security aware
n Code review
n Practice / learn / adapt
2. Adopt a security guideline in your team
3. Test your own application
4. Start using tools to find to most obvious mistakes