Keys Included: No Assembly Required
Bottom Line Up Front (BLUF)
Infor Syteline ERP uses hard-coded encryption keys (CVE-2026-2103) embedded in application binaries to protect sensitive credentials stored in its database. An attacker with access to the database can decrypt all stored passwords including application user credentials, database connection strings, API keys, and payment gateway passwords. Because these keys appear toThe application leaks padding validity through errors, status, or timing differences be identical across all installations, a single copy of the software provides universal decryption capability. Organizations running Syteline should assume that any database exposure constitutes full credential compromise and should rotate all credentials stored within the system. No vendor patch is currently available.
Background
During a recent assessment, we discovered a database that appeared to store encrypted passwords instead of hashing them appropriately. This design is fundamentally flawed, as it allows passwords to be recovered if the encryption mechanism or keys are compromised. The database server was identified as Infor’s Syteline ERP, so the next step was to locate the application interacting with it. We were able to find the application and obtain a copy of it. To our benefit, the application is written in C# which allowed us to quickly reverse the binary back to the original code base.
The Discovery
Exploring the source code we observed functions and code that validated our suspicion that user passwords being stored in a reversible format using encryption. The use of static, hard-coded keys means that anyone with access to the application binaries can decrypt these protected values.
While reviewing .NET assemblies from the application, we encountered a class responsible for managing “protected” secrets. The class stores several encrypted values as static strings:
private static readonly string encryptor = “<base64>|<base64>”;
private static readonly string sessionEncryptor = “<base64>|<base64>”;
private static readonly string ionApiKey = “<base64>|<base64>”;
private static readonly string urlSecret = “<base64>|<base64>”;
private static readonly string webServiceKey = “<base64>|<base64>”;These secrets were stored in a common format: encrypted_key|encrypted_data, with both halves Base64-encoded. A retrieval method was used to select which key applied to a given operation. This design appears simple at first; however, multiple obfuscation techniques were implemented in an attempt to complicate the decryption process. This is a perfect example of security through obscurity: in the best case, it may frustrate an attacker for a time, but does not contribute any real security.
Peeling the Encryption Onion
The decryption flow splits the stored value, decodes both parts from Base64, and passes them through an AES decryption routine:
public static string GetProtectedData(string name)
{
// ... select the appropriate encrypted string based on name ...
string[] array = empty.Split(new char[1] { ‘|’ });
byte[] encryptedDataKey = Convert.FromBase64String(array[0]);
byte[] encrypted = Convert.FromBase64String(array[1]);
byte[] key = DecryptDataKey(encryptedDataKey);
byte[] bytes = DecryptAes(encrypted, key);
return Encoding.UTF8.GetString(bytes);
}The DecryptDataKey function decrypts the first portion using a master key. This is a meaningless gesture, since this master key is also available to us, hard-coded into the code.
private static byte[] GetKey()
{
return Convert.FromBase64String(”<redacted>”);
}It returns a hard-coded Base64 string, which is used as a 256-bit AES key, embedded directly in the assembly. If a threat actor is able to obtain the binaries, this gives you everything you need to decrypt. We assume is identical across every installation.1
Instead, an appropriate method for this step would be to utilize Windows DPAPI, a hardware security module, or an external key management service.
The primary secret retrieved from this “vault” is used as a password for encrypting logon strings throughout the application. The encryption function reveals a layered approach:
public static string EncryptLogonString_AES(string text, ushort maxChars)
{
string encryptedString = EncryptLogonString(text, maxChars); // Inner layer
byte[] data = TextUtil.BufferFromHexString(encryptedString);
byte[] inArray = EncryptWithPassword(data, Encryptor); // Outer layer
return Convert.ToBase64String(inArray);
}Two encryption layers are better than one…
Outer Layer: AES with PBKDF2
The outer layer uses AES encryption with a key derived via PBKDF2. Key derivation functions strengthen password-based encryption by stretching low-entropy inputs into larger key spaces. The implementation looks reasonable at first glance. However, notice that the Key and initialization vector (IV) are derived from the same PBKDF2 stream, making the IV effectively fixed per password.
private static AesCryptoServiceProvider CreateAesCryptoAlgorithm(string password)
{
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, passwordDeriveBytesSalt);
AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider();
aesCryptoServiceProvider.Key = rfc2898DeriveBytes.GetBytes(32);
aesCryptoServiceProvider.IV = rfc2898DeriveBytes.GetBytes(16);
return aesCryptoServiceProvider;
}A deterministic IV isn’t the end of the world, but it is undesirable because it causes encryption to be repeatable for identical inputs under the same password. This can leak information about repeated plaintext or structural similarities between encrypted values, particularly when encrypting predictable or low-entropy data. Still, the use of key derivation and the selection of AES 256 are good choices in general.
But then we find the salt:
private static byte[] passwordDeriveBytesSalt = new byte[8]
{
36, 245, 144, XX, XX, XX, XX, XX
};
// Redacted full saltA hard-coded 8-byte salt. Combined with the hard-coded password from the vault. While the salt still provides some protection against generic precomputation, using a fixed salt eliminates per-secret uniqueness, which is the primary benefit of having a salt. This reduces the effectiveness of the key derivation function and makes derived keys reusable across records, limiting the security benefit provided by PBKDF2.
Best practice in this scenario is to use a randomly generated IV (unique per encryption operation) and store it alongside the ciphertext. The salt should likewise be randomly generated on a per-password or per-secret basis and stored with the ciphertext.
It is important to remember that these kinds of implementation mistakes only come into play while the encryption keys remain unknown; here, the exposure of the keys renders these weaknesses irrelevant.
Inner Layer: Legacy Custom Encryption
Peeling back the outer AES layer reveals a legacy encryption scheme. The implementation uses XOR operations with byte rotation and value mapping:
public static string DecryptLogonString(string encryptedString, ushort key)
{
string result = string.Empty;
if (encryptedString.Length > 0)
{
byte[] array = TextUtil.BufferFromHexString(encryptedString);
byte b = Convert.ToByte(key >> 8);
byte b2 = Convert.ToByte(key & 0xFF);
for (int i = 0; i < array.Length; i++)
{
if ((i & 1) == 1)
{
array[i] = UnmapByteValue(RotateLeft((byte)(b ^ array[i]), i));
}
else
{
array[i] = UnmapByteValue(RotateLeft((byte)(b2 ^ array[i]), i));
}
}
result = Encoding.Unicode.GetString(array);
}
return result;
}At first glance, this appears to be a keyed transformation. However, the source of the key parameter reveals the core weakness: it is extracted directly from the ciphertext itself.
private static ushort ExtractRandomKey(byte[] encryptedBytes)
{
int num = encryptedBytes.Length;
ushort result = 0;
if (num > 4)
{
result = (ushort)(encryptedBytes[num - 1] << 8);
result = (ushort)(result | (ushort)(encryptedBytes[num - 2] & 0xFFu));
result = (ushort)(result ^ 0xFEBEu);
}
return result;
}The decryption key is embedded in the last two bytes of the ciphertext, obfuscated with a simple XOR against 0xFEBE. This means the legacy layer provides no real cryptographic protection; the key travels with the data.
The Pattern Repeats
Further analysis revealed additional instances of hard-coded cryptographic material in related components handling payment processing:
private static ICryptoTransform GetCryptoTransform(SymmetricAlgorithm csp, bool encrypting)
{
csp.IV = Encoding.ASCII.GetBytes(”<redacted-16-bytes>”);
csp.Key = Encoding.ASCII.GetBytes(”<redacted-16-bytes>”);
if (encrypting)
{
return csp.CreateEncryptor();
}
return csp.CreateDecryptor();
}A 128-bit key and IV, hard-coded as ASCII strings, used for encrypting payment gateway credentials.
The Big Finale
The use of hard-coded cryptographic keys creates several impacts:
Universal Decryption: Any attacker with access to a copy of the software can decrypt credentials from any installation (Assumed)
No Key Rotation: Keys cannot be rotated without updating all deployed binaries
Credential Harvesting: Database backups, configuration exports, or file system access yields encrypted credentials that can be decrypted offline
We observed these mechanisms being used to encrypt:
Application User Passwords
Database connection credentials
API authentication keys
Payment processing gateway passwords
Session encryption secrets
URL signing keys
The Right Way
Credential encryption should use keys that are:
1. Unique per installation: Generated during setup, not compiled in
2. Protected by the platform: Windows DPAPI, Azure Key Vault, AWS KMS, HSMs
3. Rotatable: Changeable without redeploying application binaries
4. Access controlled: Retrievable only by authorized processes
5. Integrity Protection: Use a message authentication code (MAC) to sign the encrypted message, or select an authenticated encryption algorithm, to ensure the integrity of values encrypted at rest.
.NET provides ProtectedData.Protect (DPAPI) for user- or machine-scoped encryption where key management is handled by the operating system rather than the application. For enterprise deployments, dedicated key management services (e.g., Azure Key Vault, AWS Key Management Service (KMS), Google Cloud KMS, HashiCorp Vault) exist to centralize key storage, access control, rotation, and auditing.
The AES cipher is initialized using CBC mode with PKCS7 padding, a configuration that can be vulnerable to Padding Oracle Attack under the right conditions:
CBC + PKCS7 padding is used (met)
The application exposes a decryption path that accepts attacker-controlled ciphertext
The application leaks padding validity through errors, status, or timing differences
Timeline
2025-10-14 : Vulnerability discovered during assessment
2025-10-16 : Vendor notified via security contact
2025-10-27 : Follow up email to Vendor requesting update
2025-10-27 : Vendor replied with notification team is still investigating
2025-10-28 : Vendor confirmed vulnerability in product
2025-10-29 : Vendor created ticket to track vulnerability
2025-11-25 Vendor updated status of vulnerability and assessment. Vendor stated process to remediate is started
2026-01-13 : Request for status of vulnerability
2026-01-14 : Request for updated status
2026-01-14 : Update from vendor stating requesting update from team
2026-01-20 : Request for updated status
2026-01-26 : 90 Day disclosure period passed
2026-01-27 : Request for updated status
2026-01-27 : Vendor responded with status of still remediating issue
2026-01-27 : Notification to vendor that blog will be published Feburary 6th, 2026
2026-01-27 : Vendor responded with confirmation of blog release date
2026-02-06 : CVE assigned and blog released
BLS did not have an opportunity to review other installations.





