Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions ECoreNetto.Tests/Resource/XxeHardeningTestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// -------------------------------------------------------------------------------------------------
// <copyright file="XxeHardeningTestFixture.cs" company="Starion Group S.A.">
//
// Copyright 2017-2025 Starion Group S.A.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace ECoreNetto.Tests.Resource
{
using System.IO;
using System.Xml;

using ECoreNetto.Resource;

using NUnit.Framework;

/// <summary>
/// Suite of tests that verify the <see cref="ECoreNetto.ECoreParser"/> is hardened against
/// XML External Entity (XXE) attacks (see issue #29).
/// </summary>
[TestFixture]
public class XxeHardeningTestFixture
{
private ResourceSet resourceSet = null!;

[SetUp]
public void SetUp()
{
this.resourceSet = new ResourceSet();
}

[Test]
public void Verify_that_a_document_with_an_external_entity_is_rejected()
{
// a DOCTYPE declaring an external SYSTEM entity - the classic XXE payload
const string xxe =
"<?xml version=\"1.0\"?>\r\n" +
"<!DOCTYPE EPackage [ <!ENTITY xxe SYSTEM \"file:///etc/passwd\"> ]>\r\n" +
"<EPackage>&xxe;</EPackage>";

var resource = this.CreateResourceForContent("xxe-attack.ecore", xxe);

// DtdProcessing.Prohibit rejects the DOCTYPE before the entity is ever resolved
Assert.That(() => resource.Load(null), Throws.InstanceOf<XmlException>());
}

[Test]
public void Verify_that_an_entity_expansion_document_is_rejected()
{
// a "billion laughs" style nested-entity document; also blocked by prohibiting DTDs
const string billionLaughs =
"<?xml version=\"1.0\"?>\r\n" +
"<!DOCTYPE lolz [\r\n" +
" <!ENTITY lol \"lol\">\r\n" +
" <!ENTITY lol2 \"&lol;&lol;&lol;&lol;&lol;\">\r\n" +
" <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;\">\r\n" +
"]>\r\n" +
"<EPackage>&lol3;</EPackage>";

var resource = this.CreateResourceForContent("billion-laughs.ecore", billionLaughs);

Assert.That(() => resource.Load(null), Throws.InstanceOf<XmlException>());
}

/// <summary>
/// Writes the provided <paramref name="content"/> to a file in the test directory and
/// creates a <see cref="Resource"/> for it.
/// </summary>
private Resource CreateResourceForContent(string fileName, string content)
{
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, fileName);
File.WriteAllText(path, content);

var uri = new System.Uri(Path.GetFullPath(path));

return this.resourceSet.CreateResource(uri);
}
}
}
10 changes: 8 additions & 2 deletions ECoreNetto/ECoreParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,19 @@ internal EPackage ParseXml()

var sw = Stopwatch.StartNew();

var settings = new XmlReaderSettings();
// harden against XXE: prohibit DTD processing and disable external entity resolution
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
};

var fileInfo = new FileInfo(this.resource.URI.AbsolutePath.Replace("%20", " "));
var fullPath = Path.GetFullPath(fileInfo.FullName);

// now read the actual model file
var xmlReader = XmlReader.Create(fullPath, settings);
var xmlDocument = new XmlDocument();
var xmlDocument = new XmlDocument { XmlResolver = null };
xmlDocument.Load(xmlReader);
var package = new EPackage(this.resource, this.loggerFactory);
package.ReadXml(xmlDocument.DocumentElement);
Expand Down
Loading