|
18 | 18 | */ |
19 | 19 | package org.cyclonedx.parsers; |
20 | 20 |
|
| 21 | +import org.apache.commons.io.IOUtils; |
21 | 22 | import org.cyclonedx.Version; |
22 | 23 | import org.cyclonedx.model.Bom; |
23 | 24 | import org.cyclonedx.model.Component; |
24 | 25 | import org.cyclonedx.model.Component.Type; |
25 | 26 | import org.cyclonedx.model.Dependency; |
26 | 27 | import org.cyclonedx.model.ExternalReference; |
27 | | -import org.cyclonedx.model.License; |
28 | 28 | import org.cyclonedx.model.LicenseChoice; |
29 | 29 | import org.cyclonedx.model.OrganizationalEntity; |
30 | 30 | import org.cyclonedx.model.Pedigree; |
|
65 | 65 | import org.cyclonedx.model.license.Acknowledgement; |
66 | 66 | import org.cyclonedx.model.license.Expression; |
67 | 67 | import org.junit.jupiter.api.Test; |
| 68 | + |
68 | 69 | import java.io.File; |
| 70 | +import java.io.InputStream; |
| 71 | +import java.net.ServerSocket; |
| 72 | +import java.net.Socket; |
69 | 73 | import java.util.ArrayList; |
70 | 74 | import java.util.Arrays; |
71 | 75 | import java.util.List; |
72 | 76 | import java.util.Objects; |
| 77 | +import java.util.concurrent.Callable; |
| 78 | +import java.util.concurrent.CountDownLatch; |
| 79 | +import java.util.concurrent.ExecutorService; |
| 80 | +import java.util.concurrent.Executors; |
| 81 | +import java.util.concurrent.TimeUnit; |
| 82 | +import java.util.concurrent.atomic.AtomicBoolean; |
73 | 83 | import java.util.stream.Collectors; |
74 | 84 |
|
75 | 85 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 86 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
76 | 87 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
77 | 88 | import static org.junit.jupiter.api.Assertions.assertNull; |
78 | 89 | import static org.junit.jupiter.api.Assertions.assertTrue; |
@@ -746,4 +757,48 @@ public void testIssue492Regression() throws Exception { |
746 | 757 | final Bom bom = getXmlBom("regression/issue492.xml"); |
747 | 758 | assertEquals(2, bom.getMetadata().getTools().size()); |
748 | 759 | } |
| 760 | + |
| 761 | + @Test |
| 762 | + void validateShouldNotBeVulnerableToXxe() throws Exception { |
| 763 | + final byte[] bomBytes; |
| 764 | + try (final InputStream bomInputStream = getClass().getResourceAsStream("/security/xxe-protection.xml")) { |
| 765 | + assertNotNull(bomInputStream); |
| 766 | + bomBytes = IOUtils.toByteArray(bomInputStream); |
| 767 | + } |
| 768 | + |
| 769 | + // To verify that the validation not only doesn't fail, |
| 770 | + // but also doesn't cause any unintended side effects, |
| 771 | + // open a TCP socket on the port referenced in the |
| 772 | + // XML file. Verify that once validation completes, |
| 773 | + // no connection attempt has been made. |
| 774 | + |
| 775 | + final CountDownLatch countDownLatch = new CountDownLatch(1); |
| 776 | + final AtomicBoolean receivedConnection = new AtomicBoolean(false); |
| 777 | + |
| 778 | + final ExecutorService executor = Executors.newSingleThreadExecutor(); |
| 779 | + try (final ServerSocket serverSocket = new ServerSocket(1010)) { |
| 780 | + executor.submit((Callable<Void>) () -> { |
| 781 | + countDownLatch.countDown(); |
| 782 | + |
| 783 | + // Blocks until either a connection is received, |
| 784 | + // or the socket is closed. |
| 785 | + final Socket socket = serverSocket.accept(); |
| 786 | + receivedConnection.set(true); |
| 787 | + socket.close(); |
| 788 | + return null; |
| 789 | + }); |
| 790 | + |
| 791 | + // Wait for the socket to be ready. |
| 792 | + countDownLatch.await(); |
| 793 | + |
| 794 | + final XmlParser parser = new XmlParser(); |
| 795 | + parser.validate(bomBytes); |
| 796 | + } finally { |
| 797 | + executor.shutdownNow(); |
| 798 | + executor.awaitTermination(3, TimeUnit.SECONDS); |
| 799 | + } |
| 800 | + |
| 801 | + assertFalse(receivedConnection.get()); |
| 802 | + } |
| 803 | + |
749 | 804 | } |
0 commit comments