From 2277928577796191ec0a8277d71169e664bc6b9d Mon Sep 17 00:00:00 2001 From: "WINDOWS-MOSHE\\chen_zimmer" Date: Tue, 7 Feb 2023 19:49:22 +0000 Subject: [PATCH 01/20] extract credentials --- .../CredentialsIntelligenceProtocolFactory.cs | 24 + .../ExtractedCredentials.cs | 19 + .../ExtractorObject.cs | 31 + .../ICredentialsIntelligenceProtocol.cs | 16 + .../LoginCredentialsFields.cs | 28 + ...istepSSOCredentialsIntelligenceProtocol.cs | 19 + .../CredentialsIntelligence/PxLoginData.cs | 228 +++ .../V2CredentialsIntelligenceProtocol.cs | 19 + PerimeterXModule/Internals/Enums/CIVersion.cs | 18 + PerimeterXModule/PxModule.cs | 1275 +++++++++-------- .../PxModuleConfigurationSection.cs | 47 + 11 files changed, 1099 insertions(+), 625 deletions(-) create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs create mode 100644 PerimeterXModule/Internals/Enums/CIVersion.cs diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs new file mode 100644 index 0000000..a5dd7c2 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + public class CredentialsIntelligenceProtocolFactory + { + public static ICredentialsIntelligenceProtocol Create(string protocolVersion) + { + switch(protocolVersion) + { + case ("v2"): + return new V2CredentialsIntelligenceProtocol(); + case ("multistep_sso"): + return new MultistepSSoCredentialsIntelligenceProtocol(); + default: + throw new Exception("Unknown CI protocol version" + protocolVersion); + } + } + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs new file mode 100644 index 0000000..b4e9b34 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs @@ -0,0 +1,19 @@ +using System.Runtime.Serialization; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + public class ExtractedCredentials + { + [DataMember(Name = "username")] + private string Username; + + [DataMember(Name = "password")] + private string Password; + + public ExtractedCredentials(string username, string password) + { + this.Username = username; + this.Password = password; + } + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs new file mode 100644 index 0000000..9929113 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + [DataContract] + public class ExtractorObject + { + [DataMember(Name = "path")] + public string Path; + + [DataMember(Name = "path_type")] + public string PathType; + + [DataMember(Name = "method")] + public string Method; + + [DataMember(Name = "sent_through")] + public string SentThrough; + + [DataMember(Name = "pass_field")] + public string PassFieldName; + + [DataMember(Name = "user_field")] + public string UserFieldName; + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs new file mode 100644 index 0000000..d999ee0 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.UI; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + public interface ICredentialsIntelligenceProtocol + { + LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials); + } + + +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs new file mode 100644 index 0000000..e232d9d --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + public class LoginCredentialsFields + { + [DataMember(Name = "username")] + public string Username; + + [DataMember(Name = "password")] + public string Password; + + [DataMember(Name = "rawUsername", IsRequired = false)] + public string RawUsername; + + [DataMember(Name = "version")] + public string Version; + + [DataMember(Name = "ssoDtep", IsRequired = false)] + public string SsoStep; + + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs new file mode 100644 index 0000000..98e9b7c --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + + public class MultistepSSoCredentialsIntelligenceProtocol : ICredentialsIntelligenceProtocol + { + public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) + { + return null; + } + + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs new file mode 100644 index 0000000..42d0f45 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.IO; +using System.Linq; +using System.Security.Policy; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using System.Web.UI.WebControls; +using Jil; +using Microsoft.SqlServer.Server; +using PerimeterX.Internals.CredentialsIntelligence; + +namespace PerimeterX +{ + public class PxLoginData + { + private ICredentialsIntelligenceProtocol protocol; + private List loginCredentialsExtractor; + + public PxLoginData(string ciVersion, List loginCredentialsExraction) + { + this.protocol = CredentialsIntelligenceProtocolFactory.Create(ciVersion); + this.loginCredentialsExtractor = loginCredentialsExraction; + } + + public LoginCredentialsFields extractCredentials(PxContext context, HttpRequest request) + { + ExtractorObject extarctionDetails = FindMatchCredentialsDetails(request); + if (extarctionDetails != null) + { + ExtractedCredentials extractedCredentials = ExtractCredentials(extarctionDetails, context, request); + if (extractedCredentials != null) + { + return protocol.ProcessCredentials(extractedCredentials); + } + } + + return null; + } + + private ExtractorObject FindMatchCredentialsDetails(HttpRequest request) + { + foreach (ExtractorObject loginObject in this.loginCredentialsExtractor) + { + if (IsMatchedPath(loginObject, request)) + { + return loginObject; + } + } + + return null; + } + + public static bool IsMatchedPath(ExtractorObject extractorObject, HttpRequest request) + { + if (request.HttpMethod.ToLower() == extractorObject.Method) + { + if (extractorObject.PathType == "exact" && request.Path == extractorObject.Path) + { + return true; + } + + if (extractorObject.PathType == "regex" && Regex.IsMatch(request.RawUrl, extractorObject.Path)) + { + return true; + } + } + + return false; + } + + public ExtractedCredentials ExtractCredentials(ExtractorObject extractionDetails, PxContext pxContext, HttpRequest request) + { + string userFieldName = extractionDetails.UserFieldName; + string passwordFieldName = extractionDetails.PassFieldName; + + Dictionary headers = pxContext.GetHeadersAsDictionary(); + + if (userFieldName == null || passwordFieldName == null) + { + return null; + } + + if (extractionDetails.SentThrough == "header") + { + return ExtractFromHeader(userFieldName, passwordFieldName, headers); + } else if (extractionDetails.SentThrough == "query-param") + { + return new ExtractedCredentials(request.QueryString[userFieldName], request.QueryString[passwordFieldName]); + } else if (extractionDetails.SentThrough == "body") + { + return ExtractFromBodyAsync(userFieldName, passwordFieldName, headers, request).Result; + } + + return null; + } + + public static ExtractedCredentials ExtractFromHeader(string userFieldName, string passwordFieldName, Dictionary headers) + { + bool isUsernameHeaderExist = headers.TryGetValue(userFieldName, out string userName); + bool isPasswordHeaderExist = headers.TryGetValue(passwordFieldName, out string password); + + if (!isUsernameHeaderExist || !isPasswordHeaderExist) { return null; } + + return new ExtractedCredentials(userName, password); + } + + public async Task ExtractFromBodyAsync(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) + { + bool isContentTypeHeaderExist = headers.TryGetValue("Content-Type", out string contentType); + + string body = await ReadRequestBodyAsync(request); + + if (!isContentTypeHeaderExist) + { + return null; + } else if (contentType.Contains("application/json")) + { + return convertToJson(body, userFieldName, passwordFieldName); + } else if (contentType.Contains("x-www-form-urlencoded")) + { + return ReadValueFromUrlEncoded(body, userFieldName, passwordFieldName); + } else if (contentType.Contains("form-data")) + { + return ExtarctValueFromMultipart(body, contentType, userFieldName, passwordFieldName); + } + + return null; + } + + public static async Task ReadRequestBodyAsync(HttpRequest request) + { + using (var reader = new StreamReader(request.InputStream)) + { + try + { + var content = await reader.ReadToEndAsync(); + return content; + } + catch (Exception ex) + { + // Handle the exception + Console.WriteLine(ex.Message); + return null; + } + } + } + + public ExtractedCredentials convertToJson(string body, string userFieldName, string passwordFieldName) { + + dynamic json = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS); + + return new ExtractedCredentials((string)json[userFieldName], (string)json[passwordFieldName]); + } + + public ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFieldName, string passwordFieldName) + { + var parametersQueryString = HttpUtility.ParseQueryString(body); + var parametersDictionary = new Dictionary(); + foreach (var key in parametersQueryString.AllKeys) + { + parametersDictionary.Add(key, parametersQueryString[key]); + } + + return ExtractCredentialsFromDictinary(parametersDictionary, userFieldName, passwordFieldName); + } + + public ExtractedCredentials ExtarctValueFromMultipart(string body, string contentType, string userFieldName, string passwordFieldName) + { + var formData = new Dictionary(); + + var boundary = contentType.Split(';') + .SingleOrDefault(x => x.Trim().StartsWith("boundary="))? + .Split('=')[1]; + + var parts = body.Split(new[] { "--" + boundary }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var part in parts) + { + var lines = part.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); + + if (part.StartsWith("--")) + { + continue; + } + + var key = string.Empty; + var value = new StringBuilder(); + foreach (var line in lines) + { + + + if (line.StartsWith("Content-Disposition")) + { + key = line.Split(new[] { "name=" }, StringSplitOptions.RemoveEmptyEntries)[1].Trim('\"'); + } + else + { + value.Append(line); + } + } + + formData.Add(key, value.ToString()); + } + + return ExtractCredentialsFromDictinary(formData, userFieldName, passwordFieldName); + } + + public ExtractedCredentials ExtractCredentialsFromDictinary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) + { + bool isUsernameExist = parametersDictionary.TryGetValue(userFieldName, out string userField); + bool isPasswordExist = parametersDictionary.TryGetValue(passwordFieldName, out string passwordField); + + if (!isPasswordExist || !isPasswordExist) + { + return null; + } + + return new ExtractedCredentials(userField, passwordField); + } + + + + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs new file mode 100644 index 0000000..5564761 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX.Internals.CredentialsIntelligence +{ + + public class V2CredentialsIntelligenceProtocol : ICredentialsIntelligenceProtocol + { + public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) + { + return null; + } + + } +} diff --git a/PerimeterXModule/Internals/Enums/CIVersion.cs b/PerimeterXModule/Internals/Enums/CIVersion.cs new file mode 100644 index 0000000..d9080dd --- /dev/null +++ b/PerimeterXModule/Internals/Enums/CIVersion.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX +{ + [DataContract] + public enum CIVersion + { + [EnumMember(Value = "v2")] + V2, + [EnumMember(Value = "multistep_sso")] + MULTISTEP_SSO + } +} diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index 50a2440..6578f08 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -1,629 +1,654 @@ -// Copyright � 2016 PerimeterX, Inc. -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the -// Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions of -// the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR -// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Web; -using System.Security.Cryptography; -using System.Text; -using System.IO; -using System.Configuration; -using System.Diagnostics; -using System.Collections.Specialized; -using System.Net; -using System.Linq; -using System.Reflection; -using System.Collections; -using Jil; -using System.Collections.Generic; -using PerimeterX.Internals; -using System.Web.Script.Serialization; - -namespace PerimeterX -{ - public class PxModule : IHttpModule - { - private HttpHandler httpHandler; - private PxContext pxContext; - private static IActivityReporter reporter; - private readonly string validationMarker; - private readonly ICookieDecoder cookieDecoder; - private readonly IPXCookieValidator PxCookieValidator; - private readonly IPXS2SValidator PxS2SValidator; - private readonly IReverseProxy ReverseProxy; - private readonly PxBlock pxBlock; - - private readonly bool enabled; - private readonly bool sendPageActivites; - private readonly bool sendBlockActivities; - private readonly int blockingScore; - private readonly string appId; - private readonly IVerificationHandler customVerificationHandlerInstance; - private readonly bool suppressContentBlock; - private readonly bool challengeEnabled; - private readonly string[] sensetiveHeaders; - private readonly StringCollection fileExtWhitelist; - private readonly StringCollection routesWhitelist; - private readonly StringCollection useragentsWhitelist; - private readonly StringCollection enforceSpecificRoutes; - private readonly string cookieKey; - private readonly string customBlockUrl; - private readonly byte[] cookieKeyBytes; - private readonly string osVersion; - private string nodeName; - - static PxModule() - { - try - { - var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - PxLoggingUtils.init(config.AppId); - // allocate reporter if needed - if (config != null && (config.SendBlockActivites || config.SendPageActivites)) - { - reporter = new ActivityReporter(PxConstants.FormatBaseUri(config), config.ActivitiesCapacity, config.ActivitiesBulkSize, config.ReporterApiTimeout); - } - else - { - reporter = new NullActivityMonitor(); - } - } - catch (Exception ex) - { - PxLoggingUtils.LogDebug("Failed to extract assembly version " + ex.Message); - } - } - - public PxModule() - { - var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - if (config == null) - { - throw new ConfigurationErrorsException("Missing PerimeterX module configuration section " + PxConstants.CONFIG_SECTION); - } - - // load configuration - enabled = config.Enabled; - sendPageActivites = config.SendPageActivites; - sendBlockActivities = config.SendBlockActivites; - cookieKey = config.CookieKey; - cookieKeyBytes = Encoding.UTF8.GetBytes(cookieKey); - blockingScore = config.BlockingScore; - appId = config.AppId; - customVerificationHandlerInstance = GetCustomVerificationHandler(config.CustomVerificationHandler); - suppressContentBlock = config.SuppressContentBlock; - challengeEnabled = config.ChallengeEnabled; - sensetiveHeaders = config.SensitiveHeaders.Cast().ToArray(); - fileExtWhitelist = config.FileExtWhitelist; - routesWhitelist = config.RoutesWhitelist; - useragentsWhitelist = config.UseragentsWhitelist; - enforceSpecificRoutes = config.EnforceSpecificRoutes; - - // Set Decoder - if (config.EncryptionEnabled) - { - cookieDecoder = new EncryptedCookieDecoder(cookieKeyBytes); - } - else - { - cookieDecoder = new CookieDecoder(); - } - - using (var hasher = new SHA256Managed()) - { - validationMarker = ByteArrayToHexString(hasher.ComputeHash(cookieKeyBytes)); - } - - this.httpHandler = new HttpHandler(config, PxConstants.FormatBaseUri(config), config.ApiTimeout); - - // Set Validators - PxS2SValidator = new PXS2SValidator(config, httpHandler); - PxCookieValidator = new PXCookieValidator(config) - { - PXOriginalTokenValidator = new PXOriginalTokenValidator(config) - }; - - // Get OS type - osVersion = Environment.OSVersion.VersionString; - - // Build reverse proxy - ReverseProxy = new ReverseProxy(config); - - pxBlock = new PxBlock(config); - - PxLoggingUtils.LogDebug(ModuleName + " initialized"); - } - - public string ModuleName - { - get { return "PxModule"; } - } - - public void Init(HttpApplication application) - { - application.BeginRequest += this.Application_BeginRequest; - nodeName = application.Context.Server.MachineName; - } - - private void Application_BeginRequest(object source, EventArgs e) - { - try - { - var application = (HttpApplication)source; - - if (application == null) - { - return; - } - - var applicationContext = application.Context; - - if (applicationContext == null || IsFirstPartyProxyRequest(applicationContext)) - { - return; - } - - if (IsFilteredRequest(applicationContext)) - { - return; - } - try - { - //check if this is a telemetry command - if (IsTelemetryCommand(applicationContext)) - { - //command is valid. send telemetry - PostEnforcerTelemetryActivity(); - } - } - catch (Exception ex) - { - PxLoggingUtils.LogDebug("Failed to validate Telemetry command request: " + ex.Message); - } - - if (validationMarker == applicationContext.Request.Headers[PxConstants.PX_VALIDATED_HEADER]) - { - return; - } - - // Setting custom header for classic mode - if (HttpRuntime.UsingIntegratedPipeline) - { - applicationContext.Request.Headers.Add(PxConstants.PX_VALIDATED_HEADER, validationMarker); - } - else - { - var headers = applicationContext.Request.Headers; - Type hdr = headers.GetType(); - PropertyInfo ro = hdr.GetProperty("IsReadOnly", - BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy); - // Remove the ReadOnly property - ro.SetValue(headers, false, null); - // Invoke the protected InvalidateCachedArrays method - hdr.InvokeMember("InvalidateCachedArrays", - BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, - null, headers, null); - // Now invoke the protected "BaseAdd" method of the base class to add the - // headers you need. The header content needs to be an ArrayList or the - // the web application will choke on it. - hdr.InvokeMember("BaseAdd", - BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, - null, headers, - new object[] { PxConstants.PX_VALIDATED_HEADER, new ArrayList { validationMarker } }); - // repeat BaseAdd invocation for any other headers to be added - // Then set the collection back to ReadOnly - ro.SetValue(headers, true, null); - } - - VerifyRequest(application); - } - catch (Exception ex) - { - PxLoggingUtils.LogDebug("Failed to validate request: " + ex.Message); - } - } - - private bool IsTelemetryCommand(HttpContext applicationContext) - { - //extract header value and decode it from base64 string - string headerValue = applicationContext.Request.Headers[PxConstants.ENFORCER_TELEMETRY_HEADER]; - if(headerValue == null) - { - return false; - } - - //we got Telemetry command request - PxLoggingUtils.LogDebug("Received command to send enforcer telemetry"); - - //base 64 decode - string decodedString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); - - //value is in the form of timestamp:hmac_val - string[] splittedValue = decodedString.Split(new Char[] { ':' }); - if (splittedValue.Length != 2) - { - PxLoggingUtils.LogDebug("Malformed header value - " + PxConstants.ENFORCER_TELEMETRY_HEADER + " = " + headerValue); - return false; - } - - //timestamp - DateTime expirationTime = (new DateTime(1970, 1, 1)).AddMilliseconds(double.Parse(splittedValue[0])); - if ((expirationTime - DateTime.UtcNow).Ticks < 0) - { - //commmand is expired - PxLoggingUtils.LogDebug("Telemetry command is expired"); - return false; - } - - //check hmac integrity - string generatedHmac = BitConverter.ToString(new HMACSHA256(cookieKeyBytes).ComputeHash(Encoding.UTF8.GetBytes(splittedValue[0]))).Replace("-", ""); - if (generatedHmac != splittedValue[1].ToUpper()) - { - PxLoggingUtils.LogDebug("hmac validation failed. original = " + splittedValue[1] + ", generated = " + generatedHmac); - return false; - } - - return true; - } - - private void PostPageRequestedActivity(PxContext pxContext) - { - if (sendPageActivites) - { - PostActivity(pxContext, "page_requested", new ActivityDetails - { - ModuleVersion = PxConstants.MODULE_VERSION, - PassReason = pxContext.PassReason, - RiskRoundtripTime = pxContext.RiskRoundtripTime, +// Copyright � 2016 PerimeterX, Inc. +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the +// Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions of +// the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Web; +using System.Security.Cryptography; +using System.Text; +using System.IO; +using System.Configuration; +using System.Diagnostics; +using System.Collections.Specialized; +using System.Net; +using System.Linq; +using System.Reflection; +using System.Collections; +using Jil; +using System.Collections.Generic; +using PerimeterX.Internals; +using System.Web.Script.Serialization; +using PerimeterX.Internals.CredentialsIntelligence; + +namespace PerimeterX +{ + public class PxModule : IHttpModule + { + private HttpHandler httpHandler; + private PxContext pxContext; + private static IActivityReporter reporter; + private readonly string validationMarker; + private readonly ICookieDecoder cookieDecoder; + private readonly IPXCookieValidator PxCookieValidator; + private readonly IPXS2SValidator PxS2SValidator; + private readonly IReverseProxy ReverseProxy; + private readonly PxBlock pxBlock; + + private readonly bool enabled; + private readonly bool sendPageActivites; + private readonly bool sendBlockActivities; + private readonly int blockingScore; + private readonly string appId; + private readonly IVerificationHandler customVerificationHandlerInstance; + private readonly bool suppressContentBlock; + private readonly bool challengeEnabled; + private readonly string[] sensetiveHeaders; + private readonly StringCollection fileExtWhitelist; + private readonly StringCollection routesWhitelist; + private readonly StringCollection useragentsWhitelist; + private readonly StringCollection enforceSpecificRoutes; + private readonly string cookieKey; + private readonly string customBlockUrl; + private readonly byte[] cookieKeyBytes; + private readonly string osVersion; + private string nodeName; + private bool loginCredentialsExtractionEnabled; + private PxLoginData loginData; + + + static PxModule() + { + try + { + var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); + PxLoggingUtils.init(config.AppId); + // allocate reporter if needed + if (config != null && (config.SendBlockActivites || config.SendPageActivites)) + { + reporter = new ActivityReporter(PxConstants.FormatBaseUri(config), config.ActivitiesCapacity, config.ActivitiesBulkSize, config.ReporterApiTimeout); + } + else + { + reporter = new NullActivityMonitor(); + } + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug("Failed to extract assembly version " + ex.Message); + } + } + + public PxModule() + { + var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); + if (config == null) + { + throw new ConfigurationErrorsException("Missing PerimeterX module configuration section " + PxConstants.CONFIG_SECTION); + } + + // load configuration + enabled = config.Enabled; + sendPageActivites = config.SendPageActivites; + sendBlockActivities = config.SendBlockActivites; + cookieKey = config.CookieKey; + cookieKeyBytes = Encoding.UTF8.GetBytes(cookieKey); + blockingScore = config.BlockingScore; + appId = config.AppId; + customVerificationHandlerInstance = GetCustomVerificationHandler(config.CustomVerificationHandler); + suppressContentBlock = config.SuppressContentBlock; + challengeEnabled = config.ChallengeEnabled; + sensetiveHeaders = config.SensitiveHeaders.Cast().ToArray(); + fileExtWhitelist = config.FileExtWhitelist; + routesWhitelist = config.RoutesWhitelist; + useragentsWhitelist = config.UseragentsWhitelist; + enforceSpecificRoutes = config.EnforceSpecificRoutes; + + + extractCredentialsIntelligence(config); + + // Set Decoder + if (config.EncryptionEnabled) + { + cookieDecoder = new EncryptedCookieDecoder(cookieKeyBytes); + } + else + { + cookieDecoder = new CookieDecoder(); + } + + using (var hasher = new SHA256Managed()) + { + validationMarker = ByteArrayToHexString(hasher.ComputeHash(cookieKeyBytes)); + } + + this.httpHandler = new HttpHandler(config, PxConstants.FormatBaseUri(config), config.ApiTimeout); + + // Set Validators + PxS2SValidator = new PXS2SValidator(config, httpHandler); + PxCookieValidator = new PXCookieValidator(config) + { + PXOriginalTokenValidator = new PXOriginalTokenValidator(config) + }; + + // Get OS type + osVersion = Environment.OSVersion.VersionString; + + // Build reverse proxy + ReverseProxy = new ReverseProxy(config); + + pxBlock = new PxBlock(config); + + PxLoggingUtils.LogDebug(ModuleName + " initialized"); + } + + public string ModuleName + { + get { return "PxModule"; } + } + + private void extractCredentialsIntelligence(PxModuleConfigurationSection config) + { + List loginCredentialsExtraction; + loginCredentialsExtractionEnabled = config.LoginCredentialsExtractionEnabled; + + if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") + { + loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); + loginData = new PxLoginData(config.CiVersion, loginCredentialsExtraction); + } + } + + public void Init(HttpApplication application) + { + application.BeginRequest += this.Application_BeginRequest; + nodeName = application.Context.Server.MachineName; + } + + private void Application_BeginRequest(object source, EventArgs e) + { + try + { + var application = (HttpApplication)source; + + if (application == null) + { + return; + } + + var applicationContext = application.Context; + + if (applicationContext == null || IsFirstPartyProxyRequest(applicationContext)) + { + return; + } + + if (IsFilteredRequest(applicationContext)) + { + return; + } + try + { + //check if this is a telemetry command + if (IsTelemetryCommand(applicationContext)) + { + //command is valid. send telemetry + PostEnforcerTelemetryActivity(); + } + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug("Failed to validate Telemetry command request: " + ex.Message); + } + + if (validationMarker == applicationContext.Request.Headers[PxConstants.PX_VALIDATED_HEADER]) + { + return; + } + + // Setting custom header for classic mode + if (HttpRuntime.UsingIntegratedPipeline) + { + applicationContext.Request.Headers.Add(PxConstants.PX_VALIDATED_HEADER, validationMarker); + } + else + { + var headers = applicationContext.Request.Headers; + Type hdr = headers.GetType(); + PropertyInfo ro = hdr.GetProperty("IsReadOnly", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy); + // Remove the ReadOnly property + ro.SetValue(headers, false, null); + // Invoke the protected InvalidateCachedArrays method + hdr.InvokeMember("InvalidateCachedArrays", + BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, + null, headers, null); + // Now invoke the protected "BaseAdd" method of the base class to add the + // headers you need. The header content needs to be an ArrayList or the + // the web application will choke on it. + hdr.InvokeMember("BaseAdd", + BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, + null, headers, + new object[] { PxConstants.PX_VALIDATED_HEADER, new ArrayList { validationMarker } }); + // repeat BaseAdd invocation for any other headers to be added + // Then set the collection back to ReadOnly + ro.SetValue(headers, true, null); + } + + VerifyRequest(application); + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug("Failed to validate request: " + ex.Message); + } + } + + private bool IsTelemetryCommand(HttpContext applicationContext) + { + //extract header value and decode it from base64 string + string headerValue = applicationContext.Request.Headers[PxConstants.ENFORCER_TELEMETRY_HEADER]; + if(headerValue == null) + { + return false; + } + + //we got Telemetry command request + PxLoggingUtils.LogDebug("Received command to send enforcer telemetry"); + + //base 64 decode + string decodedString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); + + //value is in the form of timestamp:hmac_val + string[] splittedValue = decodedString.Split(new Char[] { ':' }); + if (splittedValue.Length != 2) + { + PxLoggingUtils.LogDebug("Malformed header value - " + PxConstants.ENFORCER_TELEMETRY_HEADER + " = " + headerValue); + return false; + } + + //timestamp + DateTime expirationTime = (new DateTime(1970, 1, 1)).AddMilliseconds(double.Parse(splittedValue[0])); + if ((expirationTime - DateTime.UtcNow).Ticks < 0) + { + //commmand is expired + PxLoggingUtils.LogDebug("Telemetry command is expired"); + return false; + } + + //check hmac integrity + string generatedHmac = BitConverter.ToString(new HMACSHA256(cookieKeyBytes).ComputeHash(Encoding.UTF8.GetBytes(splittedValue[0]))).Replace("-", ""); + if (generatedHmac != splittedValue[1].ToUpper()) + { + PxLoggingUtils.LogDebug("hmac validation failed. original = " + splittedValue[1] + ", generated = " + generatedHmac); + return false; + } + + return true; + } + + private void PostPageRequestedActivity(PxContext pxContext) + { + if (sendPageActivites) + { + PostActivity(pxContext, "page_requested", new ActivityDetails + { + ModuleVersion = PxConstants.MODULE_VERSION, + PassReason = pxContext.PassReason, + RiskRoundtripTime = pxContext.RiskRoundtripTime, ClientUuid = pxContext.UUID, - httpMethod = pxContext.HttpMethod - }); - } - } - - private void PostBlockActivity(PxContext pxContext, PxModuleConfigurationSection config) - { - if (sendBlockActivities) - { - PostActivity(pxContext, "block", new ActivityDetails - { - BlockAction = pxContext.BlockAction, - BlockReason = pxContext.BlockReason, - BlockUuid = pxContext.UUID, - ModuleVersion = PxConstants.MODULE_VERSION, - RiskScore = pxContext.Score, + httpMethod = pxContext.HttpMethod + }); + } + } + + private void PostBlockActivity(PxContext pxContext, PxModuleConfigurationSection config) + { + if (sendBlockActivities) + { + PostActivity(pxContext, "block", new ActivityDetails + { + BlockAction = pxContext.BlockAction, + BlockReason = pxContext.BlockReason, + BlockUuid = pxContext.UUID, + ModuleVersion = PxConstants.MODULE_VERSION, + RiskScore = pxContext.Score, RiskRoundtripTime = pxContext.RiskRoundtripTime, httpMethod = pxContext.HttpMethod, SimulatedBlock = config.MonitorMode == true - }); - } - } - - private void PostEnforcerTelemetryActivity() - { - var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - - string serializedConfig; - using (var json = new StringWriter()) - { - JSON.Serialize(config, json); - serializedConfig = json.ToString(); - } - - //remove cookieKey and ApiToken from telemetry - var jsSerializer = new JavaScriptSerializer(); - Dictionary dict = (Dictionary)jsSerializer.DeserializeObject(serializedConfig); - dict.Remove("CookieKey"); - dict.Remove("ApiToken"); - serializedConfig = new JavaScriptSerializer().Serialize(dict); - - var activity = new Activity - { - Type = "enforcer_telemetry", - Timestamp = PxConstants.GetTimestamp(), - AppId = appId, - Details = new EnforcerTelemetryActivityDetails - { - ModuleVersion = PxConstants.MODULE_VERSION, - UpdateReason = EnforcerTelemetryUpdateReasonEnum.COMMAND, - OsName = osVersion, - NodeName = nodeName, - EnforcerConfigs = serializedConfig - } - }; - - try - { - var stringBuilder = new StringBuilder(); - using (var stringOutput = new StringWriter(stringBuilder)) - { - JSON.SerializeDynamic(activity, stringOutput, Options.ExcludeNullsIncludeInherited); - } - - httpHandler.Post(stringBuilder.ToString(), PxConstants.ENFORCER_TELEMETRY_API_PATH); - } - catch (Exception ex) - { - PxLoggingUtils.LogDebug(string.Format("Encountered an error sending enforcer telemetry activity: {0}.", ex.Message)); - } - } - - private void PostActivity(PxContext pxContext, string eventType, ActivityDetails details = null) - { - var activity = new Activity - { - Type = eventType, - Timestamp = PxConstants.GetTimestamp(), - AppId = appId, - SocketIP = pxContext.Ip, - Url = pxContext.FullUrl, - Details = details, - Headers = pxContext.GetHeadersAsDictionary(), - }; - - - if (!string.IsNullOrEmpty(pxContext.Vid)) - { - activity.Vid = pxContext.Vid; - } - - if (!string.IsNullOrEmpty(pxContext.Pxhd) && (eventType == "page_requested" || eventType == "block")) - { - activity.Pxhd = pxContext.Pxhd; - } - - reporter.Post(activity); - } - - public void BlockRequest(PxContext pxContext, PxModuleConfigurationSection config) - { - pxContext.ApplicationContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - pxContext.ApplicationContext.Response.TrySkipIisCustomErrors = true; - if (config.SuppressContentBlock) - { - pxContext.ApplicationContext.Response.SuppressContent = true; - } - else - { - pxBlock.ResponseBlockPage(pxContext, config); - } - } - - public void Dispose() - { - if (httpHandler != null) - { - httpHandler.Dispose(); - } - } - - - /// - /// Checks the url if it should be a proxy request for the client/xhrs - /// - /// HTTP context for client - private bool IsFirstPartyProxyRequest(HttpContext context) - { - return ReverseProxy.ShouldReverseClient(context) || ReverseProxy.ShouldReverseCaptcha(context) || ReverseProxy.ShouldReverseXhr(context); - } - - private bool IsFilteredRequest(HttpContext context) - { - if (!enabled) - { - return true; - } - - - // whitelist file extension - var ext = Path.GetExtension(context.Request.Url.AbsolutePath).ToLowerInvariant(); - if (fileExtWhitelist != null && fileExtWhitelist.Contains(ext)) - { - return true; - } - - var url = context.Request.Url.AbsolutePath; - - // custom block url check - if (customBlockUrl != null && url == customBlockUrl) { - return true; - } - - // whitelist routes prefix - if (routesWhitelist != null) - { - foreach (var prefix in routesWhitelist) - { - if (url.StartsWith(prefix)) - { - return true; - } - } - } - - // whitelist user-agent - if (useragentsWhitelist != null && useragentsWhitelist.Contains(context.Request.UserAgent)) - { - return true; - } - - // enforce specific routes prefix - if (enforceSpecificRoutes != null) - { - // case list is not empty, module will skip the route if - // the routes prefix is not present in the list - foreach (var prefix in enforceSpecificRoutes) - { - if (url.StartsWith(prefix)) - { - return false; - } - } - // we go over all the list and prefix wasn't found - // meaning this route is not a specifc route - return true; - } - - return false; - } - - private void VerifyRequest(HttpApplication application) - { - try - { - var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - pxContext = new PxContext(application.Context, config); - - // validate using risk cookie - IPxCookie pxCookie = PxCookieUtils.BuildCookie(config, pxContext.PxCookies, cookieDecoder); - pxContext.OriginalToken = PxCookieUtils.BuildCookie(config, pxContext.OriginalTokens, cookieDecoder); - if (!PxCookieValidator.Verify(pxContext, pxCookie)) - { - // validate using server risk api - PxS2SValidator.VerifyS2S(pxContext); - } - - HandleVerification(application); - } - catch (Exception ex) // Fail-open approach - { - PxLoggingUtils.LogError(string.Format("Module failed to process request in fault: {0}, passing request", ex.Message)); - pxContext.PassReason = PassReasonEnum.ERROR; - PostPageRequestedActivity(pxContext); - } - } - - private static string ByteArrayToHexString(byte[] input) - { - StringBuilder sb = new StringBuilder(input.Length * 2); - foreach (byte b in input) - { - sb.Append(PxConstants.HEX_ALPHABET[b >> 4]); - sb.Append(PxConstants.HEX_ALPHABET[b & 0xF]); - } - return sb.ToString(); - } - - private void HandleVerification(HttpApplication application) - { - PxModuleConfigurationSection config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - bool verified = blockingScore > pxContext.Score; - - PxLoggingUtils.LogDebug(string.Format("Request score: {0}, blocking score: {1}, monitor mode status: {2}.", pxContext.Score, blockingScore, config.MonitorMode == true ? "On" : "Off")); - - if (verified) - { - if (config.MonitorMode) - { - PxLoggingUtils.LogDebug("Monitor Mode is activated. passing request"); - } - PxLoggingUtils.LogDebug(string.Format("Valid request to {0}", application.Context.Request.RawUrl)); - PostPageRequestedActivity(pxContext); - } - else - { - PxLoggingUtils.LogDebug(string.Format("Invalid request to {0}", application.Context.Request.RawUrl)); - PostBlockActivity(pxContext, config); - } - - SetPxhdAndVid(pxContext); - // If implemented, run the customVerificationHandler. - if (customVerificationHandlerInstance != null) - { - customVerificationHandlerInstance.Handle(application, pxContext, config); - } - // No custom verification handler -> continue regular flow - else if (!verified && !pxContext.MonitorRequest) - { - BlockRequest(pxContext, config); - application.CompleteRequest(); - } - } - - private static void SetPxhdAndVid(PxContext pxContext) - { - - if (!string.IsNullOrEmpty(pxContext.Pxhd)) - { - string pxhd = PxConstants.COOKIE_PXHD_PREFIX + "=" + pxContext.Pxhd + "; path=/"; - pxContext.ApplicationContext.Response.AddHeader("Set-Cookie", pxhd); - } - } - - /// - /// Uses reflection to check whether an IVerificationHandler was implemented by the customer. - /// - /// If found, returns the IVerificationHandler class instance. Otherwise, returns null. - private static IVerificationHandler GetCustomVerificationHandler(string customHandlerName) - { - if (string.IsNullOrEmpty(customHandlerName)) - { - return null; - } - - try - { - var customVerificationHandlerType = - AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => { - try - { - return a.GetTypes(); - } - catch - { - return new Type[0]; - } - }) - .FirstOrDefault(t => t.GetInterface(typeof(IVerificationHandler).Name) != null && - t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); - - if (customVerificationHandlerType != null) - { - var instance = (IVerificationHandler)Activator.CreateInstance(customVerificationHandlerType, null); - PxLoggingUtils.LogDebug(string.Format("Successfully loaded ICustomeVerificationHandler '{0}'.", customHandlerName)); - return instance; - } - else - { - PxLoggingUtils.LogDebug(string.Format( - "Missing implementation of the configured IVerificationHandler ('customVerificationHandler' attribute): {0}.", - customHandlerName)); - } - } - catch (ReflectionTypeLoadException ex) - { - PxLoggingUtils.LogError(string.Format("Failed to load the ICustomeVerificationHandler '{0}': {1}.", - customHandlerName, ex.Message)); - } - catch (Exception ex) - { - PxLoggingUtils.LogError(string.Format("Encountered an error while retrieving the ICustomeVerificationHandler '{0}': {1}.", - customHandlerName, ex.Message)); - } - - return null; - } - } -} + }); + } + } + + private void PostEnforcerTelemetryActivity() + { + var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); + + string serializedConfig; + using (var json = new StringWriter()) + { + JSON.Serialize(config, json); + serializedConfig = json.ToString(); + } + + //remove cookieKey and ApiToken from telemetry + var jsSerializer = new JavaScriptSerializer(); + Dictionary dict = (Dictionary)jsSerializer.DeserializeObject(serializedConfig); + dict.Remove("CookieKey"); + dict.Remove("ApiToken"); + serializedConfig = new JavaScriptSerializer().Serialize(dict); + + var activity = new Activity + { + Type = "enforcer_telemetry", + Timestamp = PxConstants.GetTimestamp(), + AppId = appId, + Details = new EnforcerTelemetryActivityDetails + { + ModuleVersion = PxConstants.MODULE_VERSION, + UpdateReason = EnforcerTelemetryUpdateReasonEnum.COMMAND, + OsName = osVersion, + NodeName = nodeName, + EnforcerConfigs = serializedConfig + } + }; + + try + { + var stringBuilder = new StringBuilder(); + using (var stringOutput = new StringWriter(stringBuilder)) + { + JSON.SerializeDynamic(activity, stringOutput, Options.ExcludeNullsIncludeInherited); + } + + httpHandler.Post(stringBuilder.ToString(), PxConstants.ENFORCER_TELEMETRY_API_PATH); + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug(string.Format("Encountered an error sending enforcer telemetry activity: {0}.", ex.Message)); + } + } + + private void PostActivity(PxContext pxContext, string eventType, ActivityDetails details = null) + { + var activity = new Activity + { + Type = eventType, + Timestamp = PxConstants.GetTimestamp(), + AppId = appId, + SocketIP = pxContext.Ip, + Url = pxContext.FullUrl, + Details = details, + Headers = pxContext.GetHeadersAsDictionary(), + }; + + + if (!string.IsNullOrEmpty(pxContext.Vid)) + { + activity.Vid = pxContext.Vid; + } + + if (!string.IsNullOrEmpty(pxContext.Pxhd) && (eventType == "page_requested" || eventType == "block")) + { + activity.Pxhd = pxContext.Pxhd; + } + + reporter.Post(activity); + } + + public void BlockRequest(PxContext pxContext, PxModuleConfigurationSection config) + { + pxContext.ApplicationContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + pxContext.ApplicationContext.Response.TrySkipIisCustomErrors = true; + if (config.SuppressContentBlock) + { + pxContext.ApplicationContext.Response.SuppressContent = true; + } + else + { + pxBlock.ResponseBlockPage(pxContext, config); + } + } + + public void Dispose() + { + if (httpHandler != null) + { + httpHandler.Dispose(); + } + } + + + /// + /// Checks the url if it should be a proxy request for the client/xhrs + /// + /// HTTP context for client + private bool IsFirstPartyProxyRequest(HttpContext context) + { + return ReverseProxy.ShouldReverseClient(context) || ReverseProxy.ShouldReverseCaptcha(context) || ReverseProxy.ShouldReverseXhr(context); + } + + private bool IsFilteredRequest(HttpContext context) + { + if (!enabled) + { + return true; + } + + + // whitelist file extension + var ext = Path.GetExtension(context.Request.Url.AbsolutePath).ToLowerInvariant(); + if (fileExtWhitelist != null && fileExtWhitelist.Contains(ext)) + { + return true; + } + + var url = context.Request.Url.AbsolutePath; + + // custom block url check + if (customBlockUrl != null && url == customBlockUrl) { + return true; + } + + // whitelist routes prefix + if (routesWhitelist != null) + { + foreach (var prefix in routesWhitelist) + { + if (url.StartsWith(prefix)) + { + return true; + } + } + } + + // whitelist user-agent + if (useragentsWhitelist != null && useragentsWhitelist.Contains(context.Request.UserAgent)) + { + return true; + } + + // enforce specific routes prefix + if (enforceSpecificRoutes != null) + { + // case list is not empty, module will skip the route if + // the routes prefix is not present in the list + foreach (var prefix in enforceSpecificRoutes) + { + if (url.StartsWith(prefix)) + { + return false; + } + } + // we go over all the list and prefix wasn't found + // meaning this route is not a specifc route + return true; + } + + return false; + } + + private void VerifyRequest(HttpApplication application) + { + try + { + var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); + pxContext = new PxContext(application.Context, config); + + //CI + if (loginData != null) + { + loginData.extractCredentials(pxContext, application.Context.Request); + } + + // validate using risk cookie + IPxCookie pxCookie = PxCookieUtils.BuildCookie(config, pxContext.PxCookies, cookieDecoder); + pxContext.OriginalToken = PxCookieUtils.BuildCookie(config, pxContext.OriginalTokens, cookieDecoder); + if (!PxCookieValidator.Verify(pxContext, pxCookie)) + { + // validate using server risk api + PxS2SValidator.VerifyS2S(pxContext); + } + + HandleVerification(application); + } + catch (Exception ex) // Fail-open approach + { + PxLoggingUtils.LogError(string.Format("Module failed to process request in fault: {0}, passing request", ex.Message)); + pxContext.PassReason = PassReasonEnum.ERROR; + PostPageRequestedActivity(pxContext); + } + } + + private static string ByteArrayToHexString(byte[] input) + { + StringBuilder sb = new StringBuilder(input.Length * 2); + foreach (byte b in input) + { + sb.Append(PxConstants.HEX_ALPHABET[b >> 4]); + sb.Append(PxConstants.HEX_ALPHABET[b & 0xF]); + } + return sb.ToString(); + } + + private void HandleVerification(HttpApplication application) + { + PxModuleConfigurationSection config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); + bool verified = blockingScore > pxContext.Score; + + PxLoggingUtils.LogDebug(string.Format("Request score: {0}, blocking score: {1}, monitor mode status: {2}.", pxContext.Score, blockingScore, config.MonitorMode == true ? "On" : "Off")); + + if (verified) + { + if (config.MonitorMode) + { + PxLoggingUtils.LogDebug("Monitor Mode is activated. passing request"); + } + PxLoggingUtils.LogDebug(string.Format("Valid request to {0}", application.Context.Request.RawUrl)); + PostPageRequestedActivity(pxContext); + } + else + { + PxLoggingUtils.LogDebug(string.Format("Invalid request to {0}", application.Context.Request.RawUrl)); + PostBlockActivity(pxContext, config); + } + + SetPxhdAndVid(pxContext); + // If implemented, run the customVerificationHandler. + if (customVerificationHandlerInstance != null) + { + customVerificationHandlerInstance.Handle(application, pxContext, config); + } + // No custom verification handler -> continue regular flow + else if (!verified && !pxContext.MonitorRequest) + { + BlockRequest(pxContext, config); + application.CompleteRequest(); + } + } + + private static void SetPxhdAndVid(PxContext pxContext) + { + + if (!string.IsNullOrEmpty(pxContext.Pxhd)) + { + string pxhd = PxConstants.COOKIE_PXHD_PREFIX + "=" + pxContext.Pxhd + "; path=/"; + pxContext.ApplicationContext.Response.AddHeader("Set-Cookie", pxhd); + } + } + + /// + /// Uses reflection to check whether an IVerificationHandler was implemented by the customer. + /// + /// If found, returns the IVerificationHandler class instance. Otherwise, returns null. + private static IVerificationHandler GetCustomVerificationHandler(string customHandlerName) + { + if (string.IsNullOrEmpty(customHandlerName)) + { + return null; + } + + try + { + var customVerificationHandlerType = + AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => { + try + { + return a.GetTypes(); + } + catch + { + return new Type[0]; + } + }) + .FirstOrDefault(t => t.GetInterface(typeof(IVerificationHandler).Name) != null && + t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); + + if (customVerificationHandlerType != null) + { + var instance = (IVerificationHandler)Activator.CreateInstance(customVerificationHandlerType, null); + PxLoggingUtils.LogDebug(string.Format("Successfully loaded ICustomeVerificationHandler '{0}'.", customHandlerName)); + return instance; + } + else + { + PxLoggingUtils.LogDebug(string.Format( + "Missing implementation of the configured IVerificationHandler ('customVerificationHandler' attribute): {0}.", + customHandlerName)); + } + } + catch (ReflectionTypeLoadException ex) + { + PxLoggingUtils.LogError(string.Format("Failed to load the ICustomeVerificationHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + catch (Exception ex) + { + PxLoggingUtils.LogError(string.Format("Encountered an error while retrieving the ICustomeVerificationHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + + return null; + } + } +} diff --git a/PerimeterXModule/PxModuleConfigurationSection.cs b/PerimeterXModule/PxModuleConfigurationSection.cs index 639a8d0..fee9a43 100644 --- a/PerimeterXModule/PxModuleConfigurationSection.cs +++ b/PerimeterXModule/PxModuleConfigurationSection.cs @@ -579,5 +579,52 @@ public string ByPassMonitorHeader this["bypassMonitorHeader"] = value; } } + + [ConfigurationProperty("loginCredentialsExtractionEnabled", DefaultValue = false)] + public bool LoginCredentialsExtractionEnabled + { + get + { + return (bool)this["loginCredentialsExtractionEnabled"]; + } + + set + { + this["loginCredentialsExtractionEnabled"] = value; + } + } + + [ConfigurationProperty("loginCredentialsExtraction", DefaultValue = "")] + public string LoginCredentialsExtraction + { + get + { + return (string)this["loginCredentialsExtraction"]; + } + + set + { + this["loginCredentialsExtraction"] = value; + } + + } + + + + [ConfigurationProperty("ciVersion", DefaultValue = "v2")] + public string CiVersion + { + get + { + return (string)this["ciVersion"]; + } + + set + { + this["ciVersion"] = value; + } + + } + } } From 0b5ca1be2812779311c53a64414e24d1d339f899 Mon Sep 17 00:00:00 2001 From: "WINDOWS-MOSHE\\chen_zimmer" Date: Tue, 7 Feb 2023 22:39:21 +0000 Subject: [PATCH 02/20] Added hashed credentials --- .../DataContracts/Requests/Additional.cs | 18 +++++++- .../ExtractedCredentials.cs | 4 +- .../LoginCredentialsFields.cs | 18 +++++++- ...istepSSOCredentialsIntelligenceProtocol.cs | 22 +++++++++- .../CredentialsIntelligence/PxLoginData.cs | 16 +------- .../V2CredentialsIntelligenceProtocol.cs | 41 ++++++++++++++++++- .../Internals/Helpers/PxCommonUtils.cs | 18 ++++++++ .../Internals/Helpers/PxConstants.cs | 5 ++- PerimeterXModule/Internals/PxContext.cs | 2 + .../Internals/Validators/PXS2SValidator.cs | 19 +++++++++ PerimeterXModule/PxModule.cs | 3 +- 11 files changed, 140 insertions(+), 26 deletions(-) diff --git a/PerimeterXModule/DataContracts/Requests/Additional.cs b/PerimeterXModule/DataContracts/Requests/Additional.cs index 8d6ee74..9cec180 100644 --- a/PerimeterXModule/DataContracts/Requests/Additional.cs +++ b/PerimeterXModule/DataContracts/Requests/Additional.cs @@ -46,5 +46,21 @@ public class Additional [DataMember(Name = "enforcer_vid_source", EmitDefaultValue = false)] public string VidSource; - } + + [DataMember(Name = "user")] + public string Username; + + [DataMember(Name = "pass")] + public string Password; + + [DataMember(Name = "raw_username", IsRequired = false)] + public string RawUsername; + + [DataMember(Name = "ci_version")] + public string Version; + + [DataMember(Name = "sso_step", IsRequired = false)] + public string SsoStep; + + } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs index b4e9b34..aa0fabc 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs @@ -5,10 +5,10 @@ namespace PerimeterX.Internals.CredentialsIntelligence public class ExtractedCredentials { [DataMember(Name = "username")] - private string Username; + public string Username { get; set; } [DataMember(Name = "password")] - private string Password; + public string Password { get; set; } public ExtractedCredentials(string username, string password) { diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs index e232d9d..0e978b4 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs @@ -21,8 +21,24 @@ public class LoginCredentialsFields [DataMember(Name = "version")] public string Version; - [DataMember(Name = "ssoDtep", IsRequired = false)] + [DataMember(Name = "ssoStep", IsRequired = false)] public string SsoStep; + public LoginCredentialsFields(string username, string password, string rawUsername, string version, string SsoStep) + { + this.Username = username; + this.Password = password; + this.RawUsername = rawUsername; + this.Version = version; + this.SsoStep = SsoStep; + } + + public LoginCredentialsFields(string username, string password, string rawUsername, string version) + { + this.Username = username; + this.Password = password; + this.RawUsername = rawUsername; + this.Version = version; + } } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs index 98e9b7c..a3dbcc0 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs @@ -12,8 +12,26 @@ public class MultistepSSoCredentialsIntelligenceProtocol : ICredentialsIntellige { public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) { - return null; + string rawUsername = null; + string password = null; + + if (extractedCredentials.Username != null) + { + rawUsername = extractedCredentials.Username; + } + + if (extractedCredentials.Password != null) + { + password = PxCommonUtils.Sha256(extractedCredentials.Password); + } + + return new LoginCredentialsFields( + rawUsername, + password, + rawUsername, + "multistep_sso" + ); } - + } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index 42d0f45..d2e54f7 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -26,7 +26,7 @@ public PxLoginData(string ciVersion, List loginCredentialsExrac this.loginCredentialsExtractor = loginCredentialsExraction; } - public LoginCredentialsFields extractCredentials(PxContext context, HttpRequest request) + public LoginCredentialsFields ExtractCredentials(PxContext context, HttpRequest request) { ExtractorObject extarctionDetails = FindMatchCredentialsDetails(request); if (extarctionDetails != null) @@ -135,17 +135,7 @@ public static async Task ReadRequestBodyAsync(HttpRequest request) { using (var reader = new StreamReader(request.InputStream)) { - try - { - var content = await reader.ReadToEndAsync(); - return content; - } - catch (Exception ex) - { - // Handle the exception - Console.WriteLine(ex.Message); - return null; - } + return await reader.ReadToEndAsync(); } } @@ -191,8 +181,6 @@ public ExtractedCredentials ExtarctValueFromMultipart(string body, string conten var value = new StringBuilder(); foreach (var line in lines) { - - if (line.StartsWith("Content-Disposition")) { key = line.Split(new[] { "name=" }, StringSplitOptions.RemoveEmptyEntries)[1].Trim('\"'); diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs index 5564761..b1df82b 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs @@ -12,8 +12,45 @@ public class V2CredentialsIntelligenceProtocol : ICredentialsIntelligenceProtoco { public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) { - return null; + string normalizedUsername = PxCommonUtils.IsEmailAddress(extractedCredentials.Username) ? NormalizeEmailAddress(extractedCredentials.Username) : extractedCredentials.Username; + string hashedUsername = PxCommonUtils.Sha256(normalizedUsername); + string hashedPassword = HashPassword(hashedUsername, extractedCredentials.Password); + + return new LoginCredentialsFields( + hashedUsername, + hashedPassword, + extractedCredentials.Username, + "v2" + ); + } + + public static string NormalizeEmailAddress(string emailAddress) + { + string lowercaseEmail = emailAddress.Trim().ToLower(); + int atIndex = lowercaseEmail.IndexOf("@"); + string normalizedUsername = lowercaseEmail.Substring(0, atIndex); + int plusIndex = normalizedUsername.IndexOf("+"); + + if (plusIndex > -1) + { + normalizedUsername = normalizedUsername.Substring(0, plusIndex); + } + + string domain = lowercaseEmail.Substring(atIndex); + + if (domain == "@gmail.com") + { + normalizedUsername = normalizedUsername.Replace(".", ""); + } + + return normalizedUsername; + } + + public static string HashPassword(string salt, string password) + { + string hashedPassword = PxCommonUtils.Sha256(password); + return PxCommonUtils.Sha256(salt + hashedPassword); } - + } } diff --git a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs index 4eb97e1..a21d539 100644 --- a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs +++ b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs @@ -2,7 +2,10 @@ using System.Collections; using System.Net; using System.Reflection; +using System.Text.RegularExpressions; using System.Web; +using System.Security.Cryptography; +using System.Text; namespace PerimeterX { @@ -68,5 +71,20 @@ public static void AddHeaderToRequest(HttpContext context, string key, string va ro.SetValue(headers, true, null); } } + + public static bool IsEmailAddress(string str) + { + return Regex.IsMatch(str, PxConstants.EMAIL_ADDRESS_REGEX, RegexOptions.IgnoreCase); + } + + public static string Sha256(string str) + { + using (SHA256 sha256 = SHA256.Create()) + { + byte[] inputBytes = Encoding.UTF8.GetBytes(str); + byte[] hash = sha256.ComputeHash(inputBytes); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } + } } } diff --git a/PerimeterXModule/Internals/Helpers/PxConstants.cs b/PerimeterXModule/Internals/Helpers/PxConstants.cs index 6e5200c..812c07a 100644 --- a/PerimeterXModule/Internals/Helpers/PxConstants.cs +++ b/PerimeterXModule/Internals/Helpers/PxConstants.cs @@ -35,9 +35,10 @@ public static class PxConstants public static readonly string FIRST_PARTY_VALUE = "1"; public static readonly string COOKIE_HEADER = "cookie"; public static readonly string ENFORCER_TELEMETRY_HEADER = "X-PX-ENFORCER-TELEMETRY"; + public static readonly string EMAIL_ADDRESS_REGEX = @"^([a - zA - Z0 - 9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$"; - // Endpoints - public const string RISK_API_PATH = "/api/v3/risk"; + // Endpoints + public const string RISK_API_PATH = "/api/v3/risk"; public const string ACTIVITIES_API_PATH = "/api/v1/collector/s2s"; public const string ENFORCER_TELEMETRY_API_PATH = "/api/v2/risk/telemetry"; diff --git a/PerimeterXModule/Internals/PxContext.cs b/PerimeterXModule/Internals/PxContext.cs index c57bac7..1a21698 100644 --- a/PerimeterXModule/Internals/PxContext.cs +++ b/PerimeterXModule/Internals/PxContext.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Linq; using PerimeterX.DataContracts.Cookies; +using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { @@ -51,6 +52,7 @@ public class PxContext public string VidSource { get; set; } public string Pxhd { get; set; } public bool MonitorRequest { get; set; } + public LoginCredentialsFields LoginCredentialsFields { get; set; } public PxContext(HttpContext context, PxModuleConfigurationSection pxConfiguration) { diff --git a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs index c9357ec..d461d5f 100644 --- a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs +++ b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Threading.Tasks; using PerimeterX.DataContracts.Cookies; +using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { @@ -149,9 +150,27 @@ public RiskResponse SendRiskResponse(PxContext PxContext) riskRequest.Additional.OriginalTokenError = PxContext.OriginalTokenError; } + SetCredentialsIntelligenceOnRisk(PxContext, riskRequest.Additional); + string requestJson = JSON.SerializeDynamic(riskRequest, PxConstants.JSON_OPTIONS); var responseJson = httpHandler.Post(requestJson, PxConstants.RISK_API_PATH); return JSON.Deserialize(responseJson, PxConstants.JSON_OPTIONS); } + + public void SetCredentialsIntelligenceOnRisk(PxContext pxContext, Additional riskRequest) + { + LoginCredentialsFields loginCredentialsFields = pxContext.LoginCredentialsFields; + + if (loginCredentialsFields != null) + { + riskRequest.Username = loginCredentialsFields.Username; + riskRequest.Version = loginCredentialsFields.Version; + riskRequest.Password = loginCredentialsFields.Password; + if (loginCredentialsFields.Version == "multistep_sso") + { + riskRequest.SsoStep = loginCredentialsFields.SsoStep; + } + } + } } } diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index 6578f08..cc1491d 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -514,10 +514,9 @@ private void VerifyRequest(HttpApplication application) var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); pxContext = new PxContext(application.Context, config); - //CI if (loginData != null) { - loginData.extractCredentials(pxContext, application.Context.Request); + pxContext.LoginCredentialsFields = loginData.ExtractCredentials(pxContext, application.Context.Request); } // validate using risk cookie From b7f9d52e4ef4a80d6cb1fe9769eb1f4ee35d0bf4 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Sun, 12 Feb 2023 12:46:45 +0000 Subject: [PATCH 03/20] Fix bugs and handle breached account header --- .../DataContracts/Requests/Additional.cs | 3 +- .../LoginCredentialsFields.cs | 8 ++--- ...istepSSOCredentialsIntelligenceProtocol.cs | 5 +++- .../CredentialsIntelligence/PxLoginData.cs | 25 +++++++++------- .../V2CredentialsIntelligenceProtocol.cs | 14 +++++---- .../Internals/Helpers/PxCommonUtils.cs | 24 +++++++++++++++ .../Internals/Helpers/PxConstants.cs | 3 +- PerimeterXModule/Internals/PxContext.cs | 6 ++++ .../Internals/Validators/PXS2SValidator.cs | 6 ++-- PerimeterXModule/PerimeterXModule.csproj | 9 ++++++ PerimeterXModule/PxModule.cs | 30 +++++++++++++++---- .../PxModuleConfigurationSection.cs | 17 ++++++++++- 12 files changed, 116 insertions(+), 34 deletions(-) diff --git a/PerimeterXModule/DataContracts/Requests/Additional.cs b/PerimeterXModule/DataContracts/Requests/Additional.cs index 9cec180..5ab9f87 100644 --- a/PerimeterXModule/DataContracts/Requests/Additional.cs +++ b/PerimeterXModule/DataContracts/Requests/Additional.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { @@ -57,7 +58,7 @@ public class Additional public string RawUsername; [DataMember(Name = "ci_version")] - public string Version; + public string CiVersion; [DataMember(Name = "sso_step", IsRequired = false)] public string SsoStep; diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs index 0e978b4..58b2556 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs @@ -19,17 +19,17 @@ public class LoginCredentialsFields public string RawUsername; [DataMember(Name = "version")] - public string Version; + public string CiVersion; [DataMember(Name = "ssoStep", IsRequired = false)] public string SsoStep; - public LoginCredentialsFields(string username, string password, string rawUsername, string version, string SsoStep) + public LoginCredentialsFields(string username, string password, string rawUsername, string ciVersion, string SsoStep) { this.Username = username; this.Password = password; this.RawUsername = rawUsername; - this.Version = version; + this.CiVersion = ciVersion; this.SsoStep = SsoStep; } @@ -38,7 +38,7 @@ public LoginCredentialsFields(string username, string password, string rawUserna this.Username = username; this.Password = password; this.RawUsername = rawUsername; - this.Version = version; + this.CiVersion = version; } } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs index a3dbcc0..f6071b4 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs @@ -14,10 +14,12 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC { string rawUsername = null; string password = null; + string ssoStep = "pass"; if (extractedCredentials.Username != null) { rawUsername = extractedCredentials.Username; + ssoStep = "user"; } if (extractedCredentials.Password != null) @@ -29,7 +31,8 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC rawUsername, password, rawUsername, - "multistep_sso" + "multistep_sso", + ssoStep ); } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index d2e54f7..77e2ac3 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -63,7 +63,7 @@ public static bool IsMatchedPath(ExtractorObject extractorObject, HttpRequest re return true; } - if (extractorObject.PathType == "regex" && Regex.IsMatch(request.RawUrl, extractorObject.Path)) + if (extractorObject.PathType == "regex" && Regex.IsMatch(request.Path, extractorObject.Path)) { return true; } @@ -89,7 +89,10 @@ public ExtractedCredentials ExtractCredentials(ExtractorObject extractionDetails return ExtractFromHeader(userFieldName, passwordFieldName, headers); } else if (extractionDetails.SentThrough == "query-param") { - return new ExtractedCredentials(request.QueryString[userFieldName], request.QueryString[passwordFieldName]); + return new ExtractedCredentials( + request.QueryString[userFieldName].Replace(" ", "+"), + request.QueryString[passwordFieldName].Replace(" ", "+") + ); } else if (extractionDetails.SentThrough == "body") { return ExtractFromBodyAsync(userFieldName, passwordFieldName, headers, request).Result; @@ -103,7 +106,7 @@ public static ExtractedCredentials ExtractFromHeader(string userFieldName, strin bool isUsernameHeaderExist = headers.TryGetValue(userFieldName, out string userName); bool isPasswordHeaderExist = headers.TryGetValue(passwordFieldName, out string password); - if (!isUsernameHeaderExist || !isPasswordHeaderExist) { return null; } + if (!isUsernameHeaderExist && !isPasswordHeaderExist) { return null; } return new ExtractedCredentials(userName, password); } @@ -119,7 +122,7 @@ public async Task ExtractFromBodyAsync(string userFieldNam return null; } else if (contentType.Contains("application/json")) { - return convertToJson(body, userFieldName, passwordFieldName); + return ConvertToJson(body, userFieldName, passwordFieldName); } else if (contentType.Contains("x-www-form-urlencoded")) { return ReadValueFromUrlEncoded(body, userFieldName, passwordFieldName); @@ -139,11 +142,14 @@ public static async Task ReadRequestBodyAsync(HttpRequest request) } } - public ExtractedCredentials convertToJson(string body, string userFieldName, string passwordFieldName) { + public ExtractedCredentials ConvertToJson(string body, string userFieldName, string passwordFieldName) { - dynamic json = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS); + dynamic jsonBody = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS); - return new ExtractedCredentials((string)json[userFieldName], (string)json[passwordFieldName]); + string userValue = PxCommonUtils.ExtractValueFromNestedJson(userFieldName, jsonBody); + string passValue = PxCommonUtils.ExtractValueFromNestedJson(passwordFieldName, jsonBody); + + return new ExtractedCredentials(userValue, passValue); } public ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFieldName, string passwordFieldName) @@ -202,15 +208,14 @@ public ExtractedCredentials ExtractCredentialsFromDictinary(Dictionary -1) { @@ -43,7 +45,7 @@ public static string NormalizeEmailAddress(string emailAddress) normalizedUsername = normalizedUsername.Replace(".", ""); } - return normalizedUsername; + return normalizedUsername + domain; } public static string HashPassword(string salt, string password) diff --git a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs index a21d539..9847b3d 100644 --- a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs +++ b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs @@ -6,6 +6,7 @@ using System.Web; using System.Security.Cryptography; using System.Text; +using System.Linq; namespace PerimeterX { @@ -86,5 +87,28 @@ public static string Sha256(string str) return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } } + + public static string ExtractValueFromNestedJson(string pathToValue, dynamic jsonObject) + { + const char CREDENTIAL_FIELD_NESTING_DELIMITER = '.'; + string[] stepsToCredentialInBody = pathToValue.Split(CREDENTIAL_FIELD_NESTING_DELIMITER); + + dynamic result = jsonObject; + + foreach (string step in stepsToCredentialInBody) + { + if (result == null || !result.ContainsKey(step)) + { + result = null; + break; + } + else + { + result = result[step]; + } + } + + return result; + } } } diff --git a/PerimeterXModule/Internals/Helpers/PxConstants.cs b/PerimeterXModule/Internals/Helpers/PxConstants.cs index 812c07a..94626c3 100644 --- a/PerimeterXModule/Internals/Helpers/PxConstants.cs +++ b/PerimeterXModule/Internals/Helpers/PxConstants.cs @@ -35,8 +35,7 @@ public static class PxConstants public static readonly string FIRST_PARTY_VALUE = "1"; public static readonly string COOKIE_HEADER = "cookie"; public static readonly string ENFORCER_TELEMETRY_HEADER = "X-PX-ENFORCER-TELEMETRY"; - public static readonly string EMAIL_ADDRESS_REGEX = @"^([a - zA - Z0 - 9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$"; - + public static readonly string EMAIL_ADDRESS_REGEX = @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z"; // Endpoints public const string RISK_API_PATH = "/api/v3/risk"; public const string ACTIVITIES_API_PATH = "/api/v1/collector/s2s"; diff --git a/PerimeterXModule/Internals/PxContext.cs b/PerimeterXModule/Internals/PxContext.cs index 1a21698..5e1bb71 100644 --- a/PerimeterXModule/Internals/PxContext.cs +++ b/PerimeterXModule/Internals/PxContext.cs @@ -304,5 +304,11 @@ public string MapBlockAction() return "captcha"; } } + + + public bool IsBreachedAccount() + { + return Pxde != null && Pxde.breached_account != null && IsPxdeVerified; + } } } diff --git a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs index d461d5f..2cfb78c 100644 --- a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs +++ b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs @@ -152,7 +152,7 @@ public RiskResponse SendRiskResponse(PxContext PxContext) SetCredentialsIntelligenceOnRisk(PxContext, riskRequest.Additional); - string requestJson = JSON.SerializeDynamic(riskRequest, PxConstants.JSON_OPTIONS); + string requestJson = JSON.SerializeDynamic(riskRequest, PxConstants.JSON_OPTIONS); var responseJson = httpHandler.Post(requestJson, PxConstants.RISK_API_PATH); return JSON.Deserialize(responseJson, PxConstants.JSON_OPTIONS); } @@ -164,9 +164,9 @@ public void SetCredentialsIntelligenceOnRisk(PxContext pxContext, Additional ris if (loginCredentialsFields != null) { riskRequest.Username = loginCredentialsFields.Username; - riskRequest.Version = loginCredentialsFields.Version; + riskRequest.CiVersion = loginCredentialsFields.CiVersion; riskRequest.Password = loginCredentialsFields.Password; - if (loginCredentialsFields.Version == "multistep_sso") + if (loginCredentialsFields.CiVersion == "multistep_sso") { riskRequest.SsoStep = loginCredentialsFields.SsoStep; } diff --git a/PerimeterXModule/PerimeterXModule.csproj b/PerimeterXModule/PerimeterXModule.csproj index 7818f2e..7a5098c 100644 --- a/PerimeterXModule/PerimeterXModule.csproj +++ b/PerimeterXModule/PerimeterXModule.csproj @@ -64,6 +64,15 @@ + + + + + + + + + diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index cc1491d..c95746b 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -40,6 +40,7 @@ using PerimeterX.Internals; using System.Web.Script.Serialization; using PerimeterX.Internals.CredentialsIntelligence; +using static System.Net.Mime.MediaTypeNames; namespace PerimeterX { @@ -516,7 +517,11 @@ private void VerifyRequest(HttpApplication application) if (loginData != null) { - pxContext.LoginCredentialsFields = loginData.ExtractCredentials(pxContext, application.Context.Request); + LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentials(pxContext, application.Context.Request); + if (loginCredentialsFields != null) + { + pxContext.LoginCredentialsFields = loginCredentialsFields; + } } // validate using risk cookie @@ -562,6 +567,9 @@ private void HandleVerification(HttpApplication application) { PxLoggingUtils.LogDebug("Monitor Mode is activated. passing request"); } + + HandleCredentialsIntelligence(application, config); + PxLoggingUtils.LogDebug(string.Format("Valid request to {0}", application.Context.Request.RawUrl)); PostPageRequestedActivity(pxContext); } @@ -595,11 +603,21 @@ private static void SetPxhdAndVid(PxContext pxContext) } } - /// - /// Uses reflection to check whether an IVerificationHandler was implemented by the customer. - /// - /// If found, returns the IVerificationHandler class instance. Otherwise, returns null. - private static IVerificationHandler GetCustomVerificationHandler(string customHandlerName) + private void HandleCredentialsIntelligence(HttpApplication application, PxModuleConfigurationSection config) + { + if (config.LoginCredentialsExtractionEnabled && + pxContext.LoginCredentialsFields != null && + pxContext.IsBreachedAccount()) + { + application.Context.Request.Headers.Add(config.CompromisedCredentialsHeader, JSON.Serialize(pxContext.Pxde.breached_account)); + } + } + + /// + /// Uses reflection to check whether an IVerificationHandler was implemented by the customer. + /// + /// If found, returns the IVerificationHandler class instance. Otherwise, returns null. + private static IVerificationHandler GetCustomVerificationHandler(string customHandlerName) { if (string.IsNullOrEmpty(customHandlerName)) { diff --git a/PerimeterXModule/PxModuleConfigurationSection.cs b/PerimeterXModule/PxModuleConfigurationSection.cs index fee9a43..c5647fc 100644 --- a/PerimeterXModule/PxModuleConfigurationSection.cs +++ b/PerimeterXModule/PxModuleConfigurationSection.cs @@ -608,7 +608,6 @@ public string LoginCredentialsExtraction } } - [ConfigurationProperty("ciVersion", DefaultValue = "v2")] @@ -626,5 +625,21 @@ public string CiVersion } + [ConfigurationProperty("compromisedCredentialsHeader", DefaultValue = "px-compromised-credentials")] + public string CompromisedCredentialsHeader + { + get + { + return (string)this["compromisedCredentialsHeader"]; + } + + set + { + this["compromisedCredentialsHeader"] = value; + } + + } + + } } From 134bb81ba83a466abfe7403e4f864efb70f60aa8 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Tue, 14 Feb 2023 13:53:22 +0000 Subject: [PATCH 04/20] Added login successful handling --- .../Activities/ActivityDetails.cs | 43 ++++++- .../DataContracts/Requests/Additional.cs | 1 - .../CredentialsIntelligenceProtocolFactory.cs | 7 +- .../ExtractedCredentials.cs | 2 +- .../ExtractorObject.cs | 11 +- .../ICredentialsIntelligenceProtocol.cs | 12 +- .../LoginCredentialsFields.cs | 9 +- .../LoginSuccessful/AdditionalS2SUtils.cs | 53 +++++++++ .../BodyLoginSuccessfulParser.cs | 26 +++++ .../HeaderLoginSuccessfulParser.cs | 26 +++++ .../LoginSuccessful/ILoginSuccessfulParser.cs | 9 ++ .../LoginSuccessfulParsetFactory.cs | 21 ++++ .../StatusLoginSuccessfulParser.cs | 21 ++++ ...istepSSOCredentialsIntelligenceProtocol.cs | 11 +- .../OutputFilterStream.cs | 107 ++++++++++++++++++ .../CredentialsIntelligence/PxLoginData.cs | 35 +++--- .../V2CredentialsIntelligenceProtocol.cs | 5 +- .../Internals/Helpers/PxCommonUtils.cs | 1 - PerimeterXModule/Internals/PxContext.cs | 9 +- .../Internals/Validators/PXS2SValidator.cs | 1 - PerimeterXModule/PxModule.cs | 94 +++++++++++---- .../PxModuleConfigurationSection.cs | 107 +++++++++++++++++- 22 files changed, 518 insertions(+), 93 deletions(-) create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/OutputFilterStream.cs diff --git a/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs b/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs index 3721266..5579f46 100644 --- a/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs +++ b/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs @@ -40,6 +40,15 @@ public class ActivityDetails : IActivityDetails [DataMember(Name = "simulated_block")] public bool? SimulatedBlock; + + [DataMember(Name = "ci_version")] + public string CiVersion { get; set; } + + [DataMember(Name = "credentials_compromised")] + public bool CredentialsCompromised { get; set; } + + [DataMember(Name = "sso_step")] + public string SsoStep { get; set; } } [DataContract] @@ -59,5 +68,37 @@ public class EnforcerTelemetryActivityDetails : IActivityDetails [DataMember(Name = "enforcer_configs")] public string EnforcerConfigs; - } + } + + [DataContract] + public class AdditionalS2SActivityDetails : IActivityDetails + { + + [DataMember(Name = "client_uuid")] + public string ClientUuid { get; set; } + + [DataMember(Name = "request_id")] + public string RequestId { get; set; } + + [DataMember(Name = "ci_version")] + public string CiVersion { get; set; } + + [DataMember(Name = "credentials_compromised", EmitDefaultValue = false)] + public bool CredentialsCompromised { get; set; } + + [DataMember(Name = "http_status_code")] + public int HttpStatusCode { get; set; } + + [DataMember(Name = "login_successful")] + public bool LoginSuccessful { get; set; } + + [DataMember(Name = "raw_username")] + public string RawUsername { get; set; } + + [DataMember(Name = "sso_step")] + public string SsoStep { get; set; } + + [DataMember(Name = "module_version")] + public string ModuleVersion { get; internal set; } + } } diff --git a/PerimeterXModule/DataContracts/Requests/Additional.cs b/PerimeterXModule/DataContracts/Requests/Additional.cs index 5ab9f87..197dd40 100644 --- a/PerimeterXModule/DataContracts/Requests/Additional.cs +++ b/PerimeterXModule/DataContracts/Requests/Additional.cs @@ -1,5 +1,4 @@ using System.Runtime.Serialization; -using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs index a5dd7c2..61f3dc9 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using PerimeterX; -namespace PerimeterX.Internals.CredentialsIntelligence +namespace PerimeterX { public class CredentialsIntelligenceProtocolFactory { diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs index aa0fabc..390bd01 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractedCredentials.cs @@ -1,6 +1,6 @@ using System.Runtime.Serialization; -namespace PerimeterX.Internals.CredentialsIntelligence +namespace PerimeterX { public class ExtractedCredentials { diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs index 9929113..ecf167a 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ExtractorObject.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Serialization; -namespace PerimeterX.Internals.CredentialsIntelligence +namespace PerimeterX { - [DataContract] + [DataContract] public class ExtractorObject { [DataMember(Name = "path")] diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs index d999ee0..4308652 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/ICredentialsIntelligenceProtocol.cs @@ -1,16 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web.UI; - -namespace PerimeterX.Internals.CredentialsIntelligence + +namespace PerimeterX { public interface ICredentialsIntelligenceProtocol { LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials); } - - } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs index 58b2556..eec0d9b 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginCredentialsFields.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Serialization; -namespace PerimeterX.Internals.CredentialsIntelligence +namespace PerimeterX { public class LoginCredentialsFields { diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs new file mode 100644 index 0000000..72f72a6 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs @@ -0,0 +1,53 @@ +namespace PerimeterX +{ + public class AdditionalS2SUtils + { + public static Activity CreateAdditionalS2SActivity(PxModuleConfigurationSection config, int? statusCode, bool? loginSuccessful, PxContext pxContext) + { + bool isBreachedAccount = pxContext.IsBreachedAccount(); + LoginCredentialsFields loginCredentialsFields = pxContext.LoginCredentialsFields; + + bool shouldAddRawUsername = isBreachedAccount && + config.SendRawUsernameOnAdditionalS2SActivity && + loginCredentialsFields.RawUsername != null; + + AdditionalS2SActivityDetails details = new AdditionalS2SActivityDetails + { + ModuleVersion = PxConstants.MODULE_VERSION, + ClientUuid = pxContext.UUID, + RequestId = pxContext.RequestId, + CiVersion = config.CiVersion, + CredentialsCompromised = isBreachedAccount, + RawUsername = shouldAddRawUsername ? loginCredentialsFields.RawUsername : null, + SsoStep = config.CiVersion == "multistep_sso" ? loginCredentialsFields.SsoStep : null + }; + + if (statusCode.HasValue) + { + details.HttpStatusCode = statusCode.Value; + } + + if (loginSuccessful.HasValue) + { + details.LoginSuccessful = loginSuccessful.Value; + } + + var activity = new Activity + { + Type = "additional_s2s", + Timestamp = PxConstants.GetTimestamp(), + AppId = config.AppId, + SocketIP = pxContext.Ip, + Url = pxContext.FullUrl, + Details = details, + }; + + if (!string.IsNullOrEmpty(pxContext.Vid)) + { + activity.Vid = pxContext.Vid; + } + + return activity; + } + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs new file mode 100644 index 0000000..28b4111 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs @@ -0,0 +1,26 @@ +using System.Web; +using System.Text.RegularExpressions; + +namespace PerimeterX +{ + public class BodyLoginSuccessfulParser : ILoginSuccessfulParser + { + private readonly string bodyRegex; + + public BodyLoginSuccessfulParser(PxModuleConfigurationSection config) { + bodyRegex = config.LoginSuccessfulBodyRegex; + } + + public bool IsLoginSuccessful(HttpResponse httpResponse) + { + HttpResponse tempHttpResponse = httpResponse; + string body = ((OutputFilterStream)tempHttpResponse.Filter).ReadStream(); + + if (body == null) { + return false; + } + + return Regex.IsMatch(body, bodyRegex); + } + } +} \ No newline at end of file diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs new file mode 100644 index 0000000..202c7fa --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs @@ -0,0 +1,26 @@ +using System.Web; + +namespace PerimeterX +{ + public class HeaderLoginSuccessfulParser : ILoginSuccessfulParser + { + private readonly string successfulHeaderName; + private readonly string successfulHeaderValue; + + public HeaderLoginSuccessfulParser(PxModuleConfigurationSection config) + { + successfulHeaderName = config.LoginSuccessfulHeaderName; + successfulHeaderValue = config.LoginSuccessfulHeaderValue; + } + + public bool IsLoginSuccessful(HttpResponse httpResponse) + { + if (httpResponse.Headers[successfulHeaderName] == successfulHeaderValue) + { + return true; + } + + return false; + } + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs new file mode 100644 index 0000000..f66f3ef --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs @@ -0,0 +1,9 @@ +using System.Web; + +namespace PerimeterX +{ + public interface ILoginSuccessfulParser + { + bool IsLoginSuccessful(HttpResponse httpResponse); + } +} \ No newline at end of file diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs new file mode 100644 index 0000000..90d36e6 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs @@ -0,0 +1,21 @@ + +namespace PerimeterX +{ + public class LoginSuccessfulParsetFactory + { + public static ILoginSuccessfulParser Create(PxModuleConfigurationSection config) + { + switch (config.LoginSuccessfulReportingMethod) + { + case ("body"): + return new BodyLoginSuccessfulParser(config); + case ("header"): + return new HeaderLoginSuccessfulParser(config); + case ("status"): + return new StatusLoginSuccessfulParser(config); + default: + return null; + } + } + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs new file mode 100644 index 0000000..f5e3365 --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Web; +using System.Collections.Generic; + +namespace PerimeterX +{ + public class StatusLoginSuccessfulParser : ILoginSuccessfulParser + { + private readonly List successfulStatuses; + + public StatusLoginSuccessfulParser(PxModuleConfigurationSection config) + { + successfulStatuses = config.LoginSuccessfulStatus.Cast().ToList(); + } + + public bool IsLoginSuccessful(HttpResponse httpResponse) + { + return successfulStatuses.Contains(httpResponse.StatusCode.ToString()); + } + } +} \ No newline at end of file diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs index f6071b4..132201e 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace PerimeterX.Internals.CredentialsIntelligence +namespace PerimeterX { - public class MultistepSSoCredentialsIntelligenceProtocol : ICredentialsIntelligenceProtocol { public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) @@ -35,6 +27,5 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC ssoStep ); } - } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/OutputFilterStream.cs b/PerimeterXModule/Internals/CredentialsIntelligence/OutputFilterStream.cs new file mode 100644 index 0000000..8079d9c --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/OutputFilterStream.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PerimeterX +{ + /// + /// A stream which keeps an in-memory copy as it passes the bytes through + /// + public class OutputFilterStream : Stream + { + private readonly Stream InnerStream; + private readonly MemoryStream CopyStream; + + public OutputFilterStream(Stream inner) + { + this.InnerStream = inner; + this.CopyStream = new MemoryStream(); + } + + public string ReadStream() + { + lock (this.InnerStream) + { + if (this.CopyStream.Length <= 0L || + !this.CopyStream.CanRead || + !this.CopyStream.CanSeek) + { + return String.Empty; + } + + long pos = this.CopyStream.Position; + this.CopyStream.Position = 0L; + try + { + return new StreamReader(this.CopyStream).ReadToEnd(); + } + finally + { + try + { + this.CopyStream.Position = pos; + } + catch { } + } + } + } + + + public override bool CanRead + { + get { return this.InnerStream.CanRead; } + } + + public override bool CanSeek + { + get { return this.InnerStream.CanSeek; } + } + + public override bool CanWrite + { + get { return this.InnerStream.CanWrite; } + } + + public override void Flush() + { + this.InnerStream.Flush(); + } + + public override long Length + { + get { return this.InnerStream.Length; } + } + + public override long Position + { + get { return this.InnerStream.Position; } + set { this.CopyStream.Position = this.InnerStream.Position = value; } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return this.InnerStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + this.CopyStream.Seek(offset, origin); + return this.InnerStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + this.CopyStream.SetLength(value); + this.InnerStream.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + this.CopyStream.Write(buffer, offset, count); + this.InnerStream.Write(buffer, offset, count); + } + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index 77e2ac3..86f9d5e 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -1,17 +1,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Eventing.Reader; using System.IO; using System.Linq; -using System.Security.Policy; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; -using System.Web.UI.WebControls; using Jil; -using Microsoft.SqlServer.Server; -using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { @@ -26,16 +21,22 @@ public PxLoginData(string ciVersion, List loginCredentialsExrac this.loginCredentialsExtractor = loginCredentialsExraction; } - public LoginCredentialsFields ExtractCredentials(PxContext context, HttpRequest request) + public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, HttpRequest request) { - ExtractorObject extarctionDetails = FindMatchCredentialsDetails(request); - if (extarctionDetails != null) + try { - ExtractedCredentials extractedCredentials = ExtractCredentials(extarctionDetails, context, request); - if (extractedCredentials != null) + ExtractorObject extarctionDetails = FindMatchCredentialsDetails(request); + if (extarctionDetails != null) { - return protocol.ProcessCredentials(extractedCredentials); + ExtractedCredentials extractedCredentials = ExtractCredentials(extarctionDetails, context, request); + if (extractedCredentials != null) + { + return protocol.ProcessCredentials(extractedCredentials); + } } + } catch (Exception ex) + { + PxLoggingUtils.LogError(string.Format("Failed to extract credentials.", ex.Message)); } return null; @@ -115,14 +116,16 @@ public async Task ExtractFromBodyAsync(string userFieldNam { bool isContentTypeHeaderExist = headers.TryGetValue("Content-Type", out string contentType); - string body = await ReadRequestBodyAsync(request); + HttpRequest httpRequest = request; + + string body = await ReadRequestBodyAsync(httpRequest); if (!isContentTypeHeaderExist) { return null; } else if (contentType.Contains("application/json")) { - return ConvertToJson(body, userFieldName, passwordFieldName); + return ExtractCredentialsFromJson(body, userFieldName, passwordFieldName); } else if (contentType.Contains("x-www-form-urlencoded")) { return ReadValueFromUrlEncoded(body, userFieldName, passwordFieldName); @@ -136,13 +139,15 @@ public async Task ExtractFromBodyAsync(string userFieldNam public static async Task ReadRequestBodyAsync(HttpRequest request) { - using (var reader = new StreamReader(request.InputStream)) + HttpRequest httpRequest = request; + + using (var reader = new StreamReader(httpRequest.InputStream)) { return await reader.ReadToEndAsync(); } } - public ExtractedCredentials ConvertToJson(string body, string userFieldName, string passwordFieldName) { + public ExtractedCredentials ExtractCredentialsFromJson(string body, string userFieldName, string passwordFieldName) { dynamic jsonBody = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS); diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs index aa9cea4..f2aa619 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs @@ -1,8 +1,5 @@ -using System; - -namespace PerimeterX.Internals.CredentialsIntelligence +namespace PerimeterX { - public class V2CredentialsIntelligenceProtocol : ICredentialsIntelligenceProtocol { public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) diff --git a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs index 9847b3d..8abbc01 100644 --- a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs +++ b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs @@ -6,7 +6,6 @@ using System.Web; using System.Security.Cryptography; using System.Text; -using System.Linq; namespace PerimeterX { diff --git a/PerimeterXModule/Internals/PxContext.cs b/PerimeterXModule/Internals/PxContext.cs index 5e1bb71..027862c 100644 --- a/PerimeterXModule/Internals/PxContext.cs +++ b/PerimeterXModule/Internals/PxContext.cs @@ -4,7 +4,6 @@ using System.Collections.Specialized; using System.Linq; using PerimeterX.DataContracts.Cookies; -using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { @@ -53,6 +52,7 @@ public class PxContext public string Pxhd { get; set; } public bool MonitorRequest { get; set; } public LoginCredentialsFields LoginCredentialsFields { get; set; } + public string RequestId { get; set; } public PxContext(HttpContext context, PxModuleConfigurationSection pxConfiguration) { @@ -63,11 +63,12 @@ public PxContext(HttpContext context, PxModuleConfigurationSection pxConfigurati OriginalTokens = new Dictionary(); S2SCallReason = "none"; IsMobileRequest = false; + RequestId = Guid.NewGuid().ToString(); - // Get Headers + // Get Headers - // if userAgentOverride is present override the default user-agent - CookieNames = extractCookieNames(context.Request.Headers[PxConstants.COOKIE_HEADER]); + // if userAgentOverride is present override the default user-agent + CookieNames = extractCookieNames(context.Request.Headers[PxConstants.COOKIE_HEADER]); string userAgentOverride = pxConfiguration.UserAgentOverride; if (!string.IsNullOrEmpty(userAgentOverride)) { diff --git a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs index 2cfb78c..218c8cf 100644 --- a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs +++ b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Threading.Tasks; using PerimeterX.DataContracts.Cookies; -using PerimeterX.Internals.CredentialsIntelligence; namespace PerimeterX { diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index c95746b..2524137 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -29,7 +29,6 @@ using System.Text; using System.IO; using System.Configuration; -using System.Diagnostics; using System.Collections.Specialized; using System.Net; using System.Linq; @@ -39,8 +38,6 @@ using System.Collections.Generic; using PerimeterX.Internals; using System.Web.Script.Serialization; -using PerimeterX.Internals.CredentialsIntelligence; -using static System.Net.Mime.MediaTypeNames; namespace PerimeterX { @@ -77,7 +74,6 @@ public class PxModule : IHttpModule private bool loginCredentialsExtractionEnabled; private PxLoginData loginData; - static PxModule() { try @@ -126,10 +122,10 @@ public PxModule() enforceSpecificRoutes = config.EnforceSpecificRoutes; - extractCredentialsIntelligence(config); + InitCredentialsIntelligence(config); - // Set Decoder - if (config.EncryptionEnabled) + // Set Decoder + if (config.EncryptionEnabled) { cookieDecoder = new EncryptedCookieDecoder(cookieKeyBytes); } @@ -168,28 +164,33 @@ public string ModuleName get { return "PxModule"; } } - private void extractCredentialsIntelligence(PxModuleConfigurationSection config) + private void InitCredentialsIntelligence(PxModuleConfigurationSection config) { List loginCredentialsExtraction; loginCredentialsExtractionEnabled = config.LoginCredentialsExtractionEnabled; - + if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") - { - loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); - loginData = new PxLoginData(config.CiVersion, loginCredentialsExtraction); - } - } + { + loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); + loginData = new PxLoginData(config.CiVersion, loginCredentialsExtraction); + } + } public void Init(HttpApplication application) { application.BeginRequest += this.Application_BeginRequest; nodeName = application.Context.Server.MachineName; + application.EndRequest += this.Application_EndRequest; } private void Application_BeginRequest(object source, EventArgs e) { - try - { + try + { + HttpResponse response = HttpContext.Current.Response; + OutputFilterStream filter = new OutputFilterStream(response.Filter); + response.Filter = filter; + var application = (HttpApplication)source; if (application == null) @@ -264,7 +265,33 @@ private void Application_BeginRequest(object source, EventArgs e) } } - private bool IsTelemetryCommand(HttpContext applicationContext) + private void Application_EndRequest(object source, EventArgs e) + { + var application = (HttpApplication)source; + + if (application == null) + { + return; + } + + var applicationContext = application.Context; + var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); + + if (!config.AdditionalS2SActivityHeaderEnabled && pxContext != null && pxContext.LoginCredentialsFields != null) + { + HandleLoginSuccessful(applicationContext.Response, config); + } + } + + public void HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config) + { + ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParsetFactory.Create(config); + + bool isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); + reporter.Post(AdditionalS2SUtils.CreateAdditionalS2SActivity(config, httpResponse.StatusCode, isLoginSuccessful, pxContext)); + } + + private bool IsTelemetryCommand(HttpContext applicationContext) { //extract header value and decode it from base64 string string headerValue = applicationContext.Request.Headers[PxConstants.ENFORCER_TELEMETRY_HEADER]; @@ -391,7 +418,19 @@ private void PostEnforcerTelemetryActivity() private void PostActivity(PxContext pxContext, string eventType, ActivityDetails details = null) { - var activity = new Activity + if (pxContext.LoginCredentialsFields != null) + { + LoginCredentialsFields loginCredentialsFields = pxContext.LoginCredentialsFields; + details.CiVersion = loginCredentialsFields.CiVersion; + details.CredentialsCompromised = pxContext.IsBreachedAccount(); + + if (loginCredentialsFields.CiVersion == "multistep_sso") + { + details.SsoStep = loginCredentialsFields.SsoStep; + } + } + + var activity = new Activity { Type = eventType, Timestamp = PxConstants.GetTimestamp(), @@ -402,7 +441,6 @@ private void PostActivity(PxContext pxContext, string eventType, ActivityDetails Headers = pxContext.GetHeadersAsDictionary(), }; - if (!string.IsNullOrEmpty(pxContext.Vid)) { activity.Vid = pxContext.Vid; @@ -517,7 +555,7 @@ private void VerifyRequest(HttpApplication application) if (loginData != null) { - LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentials(pxContext, application.Context.Request); + LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentialsFromRequest(pxContext, application.Context.Request); if (loginCredentialsFields != null) { pxContext.LoginCredentialsFields = loginCredentialsFields; @@ -605,11 +643,19 @@ private static void SetPxhdAndVid(PxContext pxContext) private void HandleCredentialsIntelligence(HttpApplication application, PxModuleConfigurationSection config) { - if (config.LoginCredentialsExtractionEnabled && - pxContext.LoginCredentialsFields != null && - pxContext.IsBreachedAccount()) + if (config.LoginCredentialsExtractionEnabled && pxContext.LoginCredentialsFields != null) { - application.Context.Request.Headers.Add(config.CompromisedCredentialsHeader, JSON.Serialize(pxContext.Pxde.breached_account)); + if (pxContext.IsBreachedAccount()) + { + application.Context.Request.Headers.Add(config.CompromisedCredentialsHeader, JSON.SerializeDynamic(pxContext.Pxde.breached_account, PxConstants.JSON_OPTIONS)); + } + + if (config.AdditionalS2SActivityHeaderEnabled) + { + Activity activityPayload = AdditionalS2SUtils.CreateAdditionalS2SActivity(config, null, null, pxContext); + application.Context.Request.Headers.Add("px-additional_activity", JSON.SerializeDynamic(activityPayload, PxConstants.JSON_OPTIONS)); + application.Context.Request.Headers.Add("px-backend-activity-url", PxConstants.FormatBaseUri(config) + PxConstants.ACTIVITIES_API_PATH); + } } } diff --git a/PerimeterXModule/PxModuleConfigurationSection.cs b/PerimeterXModule/PxModuleConfigurationSection.cs index c5647fc..0c9d4ae 100644 --- a/PerimeterXModule/PxModuleConfigurationSection.cs +++ b/PerimeterXModule/PxModuleConfigurationSection.cs @@ -638,8 +638,113 @@ public string CompromisedCredentialsHeader this["compromisedCredentialsHeader"] = value; } + } + + [ConfigurationProperty("sendRawUsernameOnAdditionalS2SActivity", DefaultValue = false)] + public bool SendRawUsernameOnAdditionalS2SActivity + { + get + { + return (bool)this["sendRawUsernameOnAdditionalS2SActivity"]; + } + + set + { + this["sendRawUsernameOnAdditionalS2SActivity"] = value; + } + + } + + [ConfigurationProperty("additionalS2SActivityHeaderEnabled", DefaultValue = false)] + public bool AdditionalS2SActivityHeaderEnabled + { + get + { + return (bool)this["additionalS2SActivityHeaderEnabled"]; + } + + set + { + this["additionalS2SActivityHeaderEnabled"] = value; + } + + } + + + [ConfigurationProperty("loginSuccessfulReportingMethod", DefaultValue = "")] + public string LoginSuccessfulReportingMethod + { + get + { + return (string)this["loginSuccessfulReportingMethod"]; + } + + set + { + this["loginSuccessfulReportingMethod"] = value; + } + + } + + [ConfigurationProperty("loginSuccessfulBodyRegex", DefaultValue = "")] + public string LoginSuccessfulBodyRegex + { + get + { + return (string)this["loginSuccessfulBodyRegex"]; + } + + set + { + this["loginSuccessfulBodyRegex"] = value; + } + + } + + [ConfigurationProperty("loginSuccessfulHeaderName", DefaultValue = "")] + public string LoginSuccessfulHeaderName + { + get + { + return (string)this["loginSuccessfulHeaderName"]; + } + + set + { + this["loginSuccessfulHeaderName"] = value; + } + + } + + [ConfigurationProperty("loginSuccessfulHeaderValue", DefaultValue = "")] + public string LoginSuccessfulHeaderValue + { + get + { + return (string)this["loginSuccessfulHeaderValue"]; + } + + set + { + this["loginSuccessfulHeaderValue"] = value; + } + } - + [ConfigurationProperty("loginSuccessfulStatus", DefaultValue = "")] + [TypeConverter(typeof(CommaDelimitedStringCollectionConverter))] + public StringCollection LoginSuccessfulStatus + { + get + { + return (StringCollection)this["loginSuccessfulStatus"]; + } + + set + { + this["loginSuccessfulStatus"] = value; + } + + } } } From 539df34ba66044d0c48f2671f5440bbf4840bb2f Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 20 Feb 2023 10:25:16 +0000 Subject: [PATCH 05/20] FIx comments and add consts --- .../CredentialsIntelligenceProtocolFactory.cs | 4 +-- .../LoginSuccessful/AdditionalS2SUtils.cs | 7 ++-- .../HeaderLoginSuccessfulParser.cs | 7 +--- ...istepSSOCredentialsIntelligenceProtocol.cs | 6 ++-- .../CredentialsIntelligence/PxLoginData.cs | 34 +++++++++---------- .../V2CredentialsIntelligenceProtocol.cs | 2 +- PerimeterXModule/Internals/Enums/CIVersion.cs | 18 +++------- .../Internals/Enums/MultistepSsoStep.cs | 8 +++++ .../Internals/Validators/PXS2SValidator.cs | 3 +- PerimeterXModule/PerimeterXModule.csproj | 8 +++++ PerimeterXModule/PxModule.cs | 2 +- 11 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 PerimeterXModule/Internals/Enums/MultistepSsoStep.cs diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs index 61f3dc9..f4e9a0e 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs @@ -9,9 +9,9 @@ public static ICredentialsIntelligenceProtocol Create(string protocolVersion) { switch(protocolVersion) { - case ("v2"): + case CIVersion.V2: return new V2CredentialsIntelligenceProtocol(); - case ("multistep_sso"): + case CIVersion.MULTISTEP_SSO: return new MultistepSSoCredentialsIntelligenceProtocol(); default: throw new Exception("Unknown CI protocol version" + protocolVersion); diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs index 72f72a6..861dce1 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/AdditionalS2SUtils.cs @@ -18,8 +18,7 @@ public static Activity CreateAdditionalS2SActivity(PxModuleConfigurationSection RequestId = pxContext.RequestId, CiVersion = config.CiVersion, CredentialsCompromised = isBreachedAccount, - RawUsername = shouldAddRawUsername ? loginCredentialsFields.RawUsername : null, - SsoStep = config.CiVersion == "multistep_sso" ? loginCredentialsFields.SsoStep : null + SsoStep = config.CiVersion == CIVersion.MULTISTEP_SSO ? loginCredentialsFields.SsoStep : null }; if (statusCode.HasValue) @@ -29,7 +28,9 @@ public static Activity CreateAdditionalS2SActivity(PxModuleConfigurationSection if (loginSuccessful.HasValue) { - details.LoginSuccessful = loginSuccessful.Value; + bool isLoginSuccessful = loginSuccessful.Value; + details.LoginSuccessful = isLoginSuccessful; + details.RawUsername = shouldAddRawUsername && isLoginSuccessful ? loginCredentialsFields.RawUsername : null; } var activity = new Activity diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs index 202c7fa..339fecf 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs @@ -15,12 +15,7 @@ public HeaderLoginSuccessfulParser(PxModuleConfigurationSection config) public bool IsLoginSuccessful(HttpResponse httpResponse) { - if (httpResponse.Headers[successfulHeaderName] == successfulHeaderValue) - { - return true; - } - - return false; + return httpResponse.Headers[successfulHeaderName] == successfulHeaderValue; } } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs index 132201e..bc69d5d 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs @@ -6,12 +6,12 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC { string rawUsername = null; string password = null; - string ssoStep = "pass"; + string ssoStep = MultistepSsoStep.PASSWORD; if (extractedCredentials.Username != null) { rawUsername = extractedCredentials.Username; - ssoStep = "user"; + ssoStep = MultistepSsoStep.USER; } if (extractedCredentials.Password != null) @@ -23,7 +23,7 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC rawUsername, password, rawUsername, - "multistep_sso", + CIVersion.MULTISTEP_SSO, ssoStep ); } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index 86f9d5e..d69a13d 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -25,10 +25,10 @@ public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, H { try { - ExtractorObject extarctionDetails = FindMatchCredentialsDetails(request); - if (extarctionDetails != null) + ExtractorObject extractionDetails = FindMatchCredentialsDetails(request); + if (extractionDetails != null) { - ExtractedCredentials extractedCredentials = ExtractCredentials(extarctionDetails, context, request); + ExtractedCredentials extractedCredentials = ExtractCredentials(extractionDetails, context, request); if (extractedCredentials != null) { return protocol.ProcessCredentials(extractedCredentials); @@ -46,7 +46,7 @@ private ExtractorObject FindMatchCredentialsDetails(HttpRequest request) { foreach (ExtractorObject loginObject in this.loginCredentialsExtractor) { - if (IsMatchedPath(loginObject, request)) + if (IsRequestMatchLoginRequestConfiguration(loginObject, request)) { return loginObject; } @@ -55,7 +55,7 @@ private ExtractorObject FindMatchCredentialsDetails(HttpRequest request) return null; } - public static bool IsMatchedPath(ExtractorObject extractorObject, HttpRequest request) + private static bool IsRequestMatchLoginRequestConfiguration(ExtractorObject extractorObject, HttpRequest request) { if (request.HttpMethod.ToLower() == extractorObject.Method) { @@ -73,18 +73,18 @@ public static bool IsMatchedPath(ExtractorObject extractorObject, HttpRequest re return false; } - public ExtractedCredentials ExtractCredentials(ExtractorObject extractionDetails, PxContext pxContext, HttpRequest request) + private ExtractedCredentials ExtractCredentials(ExtractorObject extractionDetails, PxContext pxContext, HttpRequest request) { string userFieldName = extractionDetails.UserFieldName; string passwordFieldName = extractionDetails.PassFieldName; - Dictionary headers = pxContext.GetHeadersAsDictionary(); - if (userFieldName == null || passwordFieldName == null) { return null; } + Dictionary headers = pxContext.GetHeadersAsDictionary(); + if (extractionDetails.SentThrough == "header") { return ExtractFromHeader(userFieldName, passwordFieldName, headers); @@ -112,7 +112,7 @@ public static ExtractedCredentials ExtractFromHeader(string userFieldName, strin return new ExtractedCredentials(userName, password); } - public async Task ExtractFromBodyAsync(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) + private async Task ExtractFromBodyAsync(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) { bool isContentTypeHeaderExist = headers.TryGetValue("Content-Type", out string contentType); @@ -131,13 +131,13 @@ public async Task ExtractFromBodyAsync(string userFieldNam return ReadValueFromUrlEncoded(body, userFieldName, passwordFieldName); } else if (contentType.Contains("form-data")) { - return ExtarctValueFromMultipart(body, contentType, userFieldName, passwordFieldName); + return ExtractValueFromMultipart(body, contentType, userFieldName, passwordFieldName); } return null; } - public static async Task ReadRequestBodyAsync(HttpRequest request) + private static async Task ReadRequestBodyAsync(HttpRequest request) { HttpRequest httpRequest = request; @@ -147,7 +147,7 @@ public static async Task ReadRequestBodyAsync(HttpRequest request) } } - public ExtractedCredentials ExtractCredentialsFromJson(string body, string userFieldName, string passwordFieldName) { + private ExtractedCredentials ExtractCredentialsFromJson(string body, string userFieldName, string passwordFieldName) { dynamic jsonBody = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS); @@ -157,7 +157,7 @@ public ExtractedCredentials ExtractCredentialsFromJson(string body, string userF return new ExtractedCredentials(userValue, passValue); } - public ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFieldName, string passwordFieldName) + private ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFieldName, string passwordFieldName) { var parametersQueryString = HttpUtility.ParseQueryString(body); var parametersDictionary = new Dictionary(); @@ -166,10 +166,10 @@ public ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFiel parametersDictionary.Add(key, parametersQueryString[key]); } - return ExtractCredentialsFromDictinary(parametersDictionary, userFieldName, passwordFieldName); + return ExtractCredentialsFromDictionary(parametersDictionary, userFieldName, passwordFieldName); } - public ExtractedCredentials ExtarctValueFromMultipart(string body, string contentType, string userFieldName, string passwordFieldName) + private ExtractedCredentials ExtractValueFromMultipart(string body, string contentType, string userFieldName, string passwordFieldName) { var formData = new Dictionary(); @@ -205,10 +205,10 @@ public ExtractedCredentials ExtarctValueFromMultipart(string body, string conten formData.Add(key, value.ToString()); } - return ExtractCredentialsFromDictinary(formData, userFieldName, passwordFieldName); + return ExtractCredentialsFromDictionary(formData, userFieldName, passwordFieldName); } - public ExtractedCredentials ExtractCredentialsFromDictinary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) + private ExtractedCredentials ExtractCredentialsFromDictionary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) { bool isUsernameExist = parametersDictionary.TryGetValue(userFieldName, out string userField); bool isPasswordExist = parametersDictionary.TryGetValue(passwordFieldName, out string passwordField); diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs index f2aa619..f7be3d3 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs @@ -18,7 +18,7 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC hashedUsername, hashedPassword, extractedCredentials.Username, - "v2" + CIVersion.V2 ); } diff --git a/PerimeterXModule/Internals/Enums/CIVersion.cs b/PerimeterXModule/Internals/Enums/CIVersion.cs index d9080dd..117fa70 100644 --- a/PerimeterXModule/Internals/Enums/CIVersion.cs +++ b/PerimeterXModule/Internals/Enums/CIVersion.cs @@ -1,18 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace PerimeterX +namespace PerimeterX { - [DataContract] - public enum CIVersion + public class CIVersion { - [EnumMember(Value = "v2")] - V2, - [EnumMember(Value = "multistep_sso")] - MULTISTEP_SSO + public const string MULTISTEP_SSO = "multistep_sso"; + public const string V2 = "v2"; } } diff --git a/PerimeterXModule/Internals/Enums/MultistepSsoStep.cs b/PerimeterXModule/Internals/Enums/MultistepSsoStep.cs new file mode 100644 index 0000000..d6d7af8 --- /dev/null +++ b/PerimeterXModule/Internals/Enums/MultistepSsoStep.cs @@ -0,0 +1,8 @@ +namespace PerimeterX +{ + public class MultistepSsoStep + { + public const string USER = "user"; + public const string PASSWORD = "pass"; + } +} diff --git a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs index 218c8cf..5f0641c 100644 --- a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs +++ b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs @@ -165,7 +165,8 @@ public void SetCredentialsIntelligenceOnRisk(PxContext pxContext, Additional ris riskRequest.Username = loginCredentialsFields.Username; riskRequest.CiVersion = loginCredentialsFields.CiVersion; riskRequest.Password = loginCredentialsFields.Password; - if (loginCredentialsFields.CiVersion == "multistep_sso") + + if (loginCredentialsFields.CiVersion == CIVersion.MULTISTEP_SSO) { riskRequest.SsoStep = loginCredentialsFields.SsoStep; } diff --git a/PerimeterXModule/PerimeterXModule.csproj b/PerimeterXModule/PerimeterXModule.csproj index 7a5098c..09ef475 100644 --- a/PerimeterXModule/PerimeterXModule.csproj +++ b/PerimeterXModule/PerimeterXModule.csproj @@ -64,13 +64,21 @@ + + + + + + + + diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index 2524137..b63ceaa 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -424,7 +424,7 @@ private void PostActivity(PxContext pxContext, string eventType, ActivityDetails details.CiVersion = loginCredentialsFields.CiVersion; details.CredentialsCompromised = pxContext.IsBreachedAccount(); - if (loginCredentialsFields.CiVersion == "multistep_sso") + if (loginCredentialsFields.CiVersion == CIVersion.MULTISTEP_SSO) { details.SsoStep = loginCredentialsFields.SsoStep; } From b0e330dcece7bcbd5be34736aa7bb633e2bd7aa0 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 20 Feb 2023 10:26:49 +0000 Subject: [PATCH 06/20] add loginSuccessful interface --- .../CustomBehavior/ILoginSuccessful.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 PerimeterXModule/CustomBehavior/ILoginSuccessful.cs diff --git a/PerimeterXModule/CustomBehavior/ILoginSuccessful.cs b/PerimeterXModule/CustomBehavior/ILoginSuccessful.cs new file mode 100644 index 0000000..9b29d27 --- /dev/null +++ b/PerimeterXModule/CustomBehavior/ILoginSuccessful.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace PerimeterX.CustomBehavior +{ + public interface ILoginSuccessful + { + void Handle(HttpApplication HttpApplication, PxContext pxContext, PxModuleConfigurationSection pxConfig); + } +} From b8f33df4f69505c3901bcdb7663db9524baf5faf Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 20 Feb 2023 11:15:46 +0000 Subject: [PATCH 07/20] Add BodyReader class --- .../CredentialsIntelligence/PxLoginData.cs | 18 ++++------------- .../Internals/Helpers/BodyReader.cs | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 PerimeterXModule/Internals/Helpers/BodyReader.cs diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index d69a13d..f171e20 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -118,7 +118,7 @@ private async Task ExtractFromBodyAsync(string userFieldNa HttpRequest httpRequest = request; - string body = await ReadRequestBodyAsync(httpRequest); + string body = await BodyReader.ReadRequestBodyAsync(httpRequest); if (!isContentTypeHeaderExist) { @@ -137,16 +137,6 @@ private async Task ExtractFromBodyAsync(string userFieldNa return null; } - private static async Task ReadRequestBodyAsync(HttpRequest request) - { - HttpRequest httpRequest = request; - - using (var reader = new StreamReader(httpRequest.InputStream)) - { - return await reader.ReadToEndAsync(); - } - } - private ExtractedCredentials ExtractCredentialsFromJson(string body, string userFieldName, string passwordFieldName) { dynamic jsonBody = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS); @@ -166,7 +156,7 @@ private ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFie parametersDictionary.Add(key, parametersQueryString[key]); } - return ExtractCredentialsFromDictionary(parametersDictionary, userFieldName, passwordFieldName); + return ExtractCredentialsFromDictinary(parametersDictionary, userFieldName, passwordFieldName); } private ExtractedCredentials ExtractValueFromMultipart(string body, string contentType, string userFieldName, string passwordFieldName) @@ -205,10 +195,10 @@ private ExtractedCredentials ExtractValueFromMultipart(string body, string conte formData.Add(key, value.ToString()); } - return ExtractCredentialsFromDictionary(formData, userFieldName, passwordFieldName); + return ExtractCredentialsFromDictinary(formData, userFieldName, passwordFieldName); } - private ExtractedCredentials ExtractCredentialsFromDictionary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) + private ExtractedCredentials ExtractCredentialsFromDictinary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) { bool isUsernameExist = parametersDictionary.TryGetValue(userFieldName, out string userField); bool isPasswordExist = parametersDictionary.TryGetValue(passwordFieldName, out string passwordField); diff --git a/PerimeterXModule/Internals/Helpers/BodyReader.cs b/PerimeterXModule/Internals/Helpers/BodyReader.cs new file mode 100644 index 0000000..5bb2e67 --- /dev/null +++ b/PerimeterXModule/Internals/Helpers/BodyReader.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using System.Web; + +namespace PerimeterX +{ + public class BodyReader + { + + public static async Task ReadRequestBodyAsync(HttpRequest request) + { + HttpRequest httpRequest = request; + + using (var reader = new StreamReader(httpRequest.InputStream)) + { + return await reader.ReadToEndAsync(); + } + } + } +} \ No newline at end of file From cfa95de65a04ddc01ca70772f45bf7cdb6391fb2 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 20 Feb 2023 17:25:43 +0000 Subject: [PATCH 08/20] Added custom functions for CI --- .../ICredentialsExtractionHandler.cs | 9 ++ .../CustomBehavior/ILoginSuccessful.cs | 14 -- .../CustomBehavior/ILoginSuccessfulHandler.cs | 9 ++ .../CredentialsIntelligence/PxLoginData.cs | 26 ++- PerimeterXModule/PerimeterXModule.csproj | 6 +- PerimeterXModule/PxCustomFunctions.cs | 151 ++++++++++++++++++ PerimeterXModule/PxModule.cs | 86 +++------- .../PxModuleConfigurationSection.cs | 26 +++ 8 files changed, 242 insertions(+), 85 deletions(-) create mode 100644 PerimeterXModule/CustomBehavior/ICredentialsExtractionHandler.cs delete mode 100644 PerimeterXModule/CustomBehavior/ILoginSuccessful.cs create mode 100644 PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs create mode 100644 PerimeterXModule/PxCustomFunctions.cs diff --git a/PerimeterXModule/CustomBehavior/ICredentialsExtractionHandler.cs b/PerimeterXModule/CustomBehavior/ICredentialsExtractionHandler.cs new file mode 100644 index 0000000..8f91808 --- /dev/null +++ b/PerimeterXModule/CustomBehavior/ICredentialsExtractionHandler.cs @@ -0,0 +1,9 @@ +using System.Web; + +namespace PerimeterX.CustomBehavior +{ + public interface ICredentialsExtractionHandler + { + ExtractedCredentials Handle(HttpRequest httpRequest); + } +} diff --git a/PerimeterXModule/CustomBehavior/ILoginSuccessful.cs b/PerimeterXModule/CustomBehavior/ILoginSuccessful.cs deleted file mode 100644 index 9b29d27..0000000 --- a/PerimeterXModule/CustomBehavior/ILoginSuccessful.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web; - -namespace PerimeterX.CustomBehavior -{ - public interface ILoginSuccessful - { - void Handle(HttpApplication HttpApplication, PxContext pxContext, PxModuleConfigurationSection pxConfig); - } -} diff --git a/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs b/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs new file mode 100644 index 0000000..8244593 --- /dev/null +++ b/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs @@ -0,0 +1,9 @@ +using System.Web; + +namespace PerimeterX.CustomBehavior +{ + public interface ILoginSuccessfulHandler + { + bool Handle(HttpApplication HttpApplication); + } +} diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index f171e20..dab246a 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using System.Web; using Jil; +using PerimeterX.CustomBehavior; namespace PerimeterX { @@ -21,19 +22,32 @@ public PxLoginData(string ciVersion, List loginCredentialsExrac this.loginCredentialsExtractor = loginCredentialsExraction; } - public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, HttpRequest request) + public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, HttpRequest request, ICredentialsExtractionHandler credentialsExtractionHandler) { try { - ExtractorObject extractionDetails = FindMatchCredentialsDetails(request); - if (extractionDetails != null) + ExtractedCredentials extractedCredentials = null; + + if (credentialsExtractionHandler != null) + { + extractedCredentials = credentialsExtractionHandler.Handle(request); + } + else { - ExtractedCredentials extractedCredentials = ExtractCredentials(extractionDetails, context, request); - if (extractedCredentials != null) + ExtractorObject extractionDetails = FindMatchCredentialsDetails(request); + + if (extractionDetails != null) { - return protocol.ProcessCredentials(extractedCredentials); + extractedCredentials = ExtractCredentials(extractionDetails, context, request); } } + + if (extractedCredentials != null) + { + return protocol.ProcessCredentials(extractedCredentials); + } + + return null; } catch (Exception ex) { PxLoggingUtils.LogError(string.Format("Failed to extract credentials.", ex.Message)); diff --git a/PerimeterXModule/PerimeterXModule.csproj b/PerimeterXModule/PerimeterXModule.csproj index 09ef475..bbb5f5e 100644 --- a/PerimeterXModule/PerimeterXModule.csproj +++ b/PerimeterXModule/PerimeterXModule.csproj @@ -64,7 +64,9 @@ - + + + @@ -80,6 +82,7 @@ + @@ -121,6 +124,7 @@ + diff --git a/PerimeterXModule/PxCustomFunctions.cs b/PerimeterXModule/PxCustomFunctions.cs new file mode 100644 index 0000000..6568d45 --- /dev/null +++ b/PerimeterXModule/PxCustomFunctions.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using PerimeterX.CustomBehavior; + +namespace PerimeterX +{ + public static class PxCustomFunctions + { + public static ILoginSuccessfulHandler GetCustomLoginSuccessfulHandler(string customHandlerName) + { + if (string.IsNullOrEmpty(customHandlerName)) + { + return null; + } + + try + { + var customLoginSuccessfulHandler = + getAssebliesTypes().FirstOrDefault(t => t.GetInterface(typeof(ILoginSuccessfulHandler).Name) != null && + t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); + + if (customLoginSuccessfulHandler != null) + { + var instance = (ILoginSuccessfulHandler)Activator.CreateInstance(customLoginSuccessfulHandler, null); + PxLoggingUtils.LogDebug(string.Format("Successfully loaded ICustomLoginSuccessfulHandler '{0}'.", customHandlerName)); + return instance; + } + else + { + PxLoggingUtils.LogDebug(string.Format( + "Missing implementation of the configured ICustomLoginSuccessfulHandler ('customLoginSuccessfulHandler' attribute): {0}.", + customHandlerName)); + } + } + catch (ReflectionTypeLoadException ex) + { + PxLoggingUtils.LogError(string.Format("Failed to load the ICustomLoginSuccessfulHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + catch (Exception ex) + { + PxLoggingUtils.LogError(string.Format("Encountered an error while retrieving the ICustomLoginSuccessfulHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + + return null; + } + + private static IEnumerable getAssebliesTypes() + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => + { + try + { + return a.GetTypes(); + } + catch + { + return new Type[0]; + } + }); + } + + /// + /// Uses reflection to check whether an IVerificationHandler was implemented by the customer. + /// + /// If found, returns the IVerificationHandler class instance. Otherwise, returns null. + public static IVerificationHandler GetCustomVerificationHandler(string customHandlerName) + { + if (string.IsNullOrEmpty(customHandlerName)) + { + return null; + } + + try + { + var customVerificationHandlerType = getAssebliesTypes() + .FirstOrDefault(t => t.GetInterface(typeof(IVerificationHandler).Name) != null && + t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); + + if (customVerificationHandlerType != null) + { + var instance = (IVerificationHandler)Activator.CreateInstance(customVerificationHandlerType, null); + PxLoggingUtils.LogDebug(string.Format("Successfully loaded ICustomeVerificationHandler '{0}'.", customHandlerName)); + return instance; + } + else + { + PxLoggingUtils.LogDebug(string.Format( + "Missing implementation of the configured IVerificationHandler ('customVerificationHandler' attribute): {0}.", + customHandlerName)); + } + } + catch (ReflectionTypeLoadException ex) + { + PxLoggingUtils.LogError(string.Format("Failed to load the ICustomeVerificationHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + catch (Exception ex) + { + PxLoggingUtils.LogError(string.Format("Encountered an error while retrieving the ICustomeVerificationHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + + return null; + } + + public static ICredentialsExtractionHandler GetCustomLoginCredentialsExtractionHandler(string customHandlerName) + { + if (string.IsNullOrEmpty(customHandlerName)) + { + return null; + } + + try + { + var customCredentialsExtraction = getAssebliesTypes() + .FirstOrDefault(t => t.GetInterface(typeof(ICredentialsExtractionHandler).Name) != null && + t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); + + if (customCredentialsExtraction != null) + { + var instance = (ICredentialsExtractionHandler)Activator.CreateInstance(customCredentialsExtraction, null); + PxLoggingUtils.LogDebug(string.Format("Successfully loaded ICredentialsExtractionHandler '{0}'.", customHandlerName)); + return instance; + } + else + { + PxLoggingUtils.LogDebug(string.Format( + "Missing implementation of the configured IVerificationHandler ('ICredentialsExtractionHandler' attribute): {0}.", + customHandlerName)); + } + } + catch (ReflectionTypeLoadException ex) + { + PxLoggingUtils.LogError(string.Format("Failed to load the ICredentialsExtractionHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + catch (Exception ex) + { + PxLoggingUtils.LogError(string.Format("Encountered an error while retrieving the ICredentialsExtractionHandler '{0}': {1}.", + customHandlerName, ex.Message)); + } + + return null; + } + } +} \ No newline at end of file diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index b63ceaa..b6067dc 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -38,6 +38,8 @@ using System.Collections.Generic; using PerimeterX.Internals; using System.Web.Script.Serialization; +using PerimeterX.CustomBehavior; +using static System.Net.Mime.MediaTypeNames; namespace PerimeterX { @@ -73,6 +75,8 @@ public class PxModule : IHttpModule private string nodeName; private bool loginCredentialsExtractionEnabled; private PxLoginData loginData; + private readonly ILoginSuccessfulHandler customLoginSuccessful; + private readonly ICredentialsExtractionHandler customCredentialsExtraction; static PxModule() { @@ -112,7 +116,7 @@ public PxModule() cookieKeyBytes = Encoding.UTF8.GetBytes(cookieKey); blockingScore = config.BlockingScore; appId = config.AppId; - customVerificationHandlerInstance = GetCustomVerificationHandler(config.CustomVerificationHandler); + customVerificationHandlerInstance = PxCustomFunctions.GetCustomVerificationHandler(config.CustomVerificationHandler); suppressContentBlock = config.SuppressContentBlock; challengeEnabled = config.ChallengeEnabled; sensetiveHeaders = config.SensitiveHeaders.Cast().ToArray(); @@ -120,6 +124,8 @@ public PxModule() routesWhitelist = config.RoutesWhitelist; useragentsWhitelist = config.UseragentsWhitelist; enforceSpecificRoutes = config.EnforceSpecificRoutes; + customLoginSuccessful = PxCustomFunctions.GetCustomLoginSuccessfulHandler(config.CustomLoginSuccessfulHandler); + customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); InitCredentialsIntelligence(config); @@ -279,15 +285,22 @@ private void Application_EndRequest(object source, EventArgs e) if (!config.AdditionalS2SActivityHeaderEnabled && pxContext != null && pxContext.LoginCredentialsFields != null) { - HandleLoginSuccessful(applicationContext.Response, config); + HandleLoginSuccessful(applicationContext.Response, config, application); } } - public void HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config) + public void HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config, HttpApplication application) + { + bool isLoginSuccessful; + + if (customLoginSuccessful != null) { - ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParsetFactory.Create(config); - - bool isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); + isLoginSuccessful = customLoginSuccessful.Handle(application); + } else { + ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParsetFactory.Create(config); + isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); + } + reporter.Post(AdditionalS2SUtils.CreateAdditionalS2SActivity(config, httpResponse.StatusCode, isLoginSuccessful, pxContext)); } @@ -552,10 +565,10 @@ private void VerifyRequest(HttpApplication application) { var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); pxContext = new PxContext(application.Context, config); - + if (loginData != null) { - LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentialsFromRequest(pxContext, application.Context.Request); + LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentialsFromRequest(pxContext, application.Context.Request, customCredentialsExtraction); if (loginCredentialsFields != null) { pxContext.LoginCredentialsFields = loginCredentialsFields; @@ -658,60 +671,5 @@ private void HandleCredentialsIntelligence(HttpApplication application, PxModule } } } - - /// - /// Uses reflection to check whether an IVerificationHandler was implemented by the customer. - /// - /// If found, returns the IVerificationHandler class instance. Otherwise, returns null. - private static IVerificationHandler GetCustomVerificationHandler(string customHandlerName) - { - if (string.IsNullOrEmpty(customHandlerName)) - { - return null; - } - - try - { - var customVerificationHandlerType = - AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => { - try - { - return a.GetTypes(); - } - catch - { - return new Type[0]; - } - }) - .FirstOrDefault(t => t.GetInterface(typeof(IVerificationHandler).Name) != null && - t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); - - if (customVerificationHandlerType != null) - { - var instance = (IVerificationHandler)Activator.CreateInstance(customVerificationHandlerType, null); - PxLoggingUtils.LogDebug(string.Format("Successfully loaded ICustomeVerificationHandler '{0}'.", customHandlerName)); - return instance; - } - else - { - PxLoggingUtils.LogDebug(string.Format( - "Missing implementation of the configured IVerificationHandler ('customVerificationHandler' attribute): {0}.", - customHandlerName)); - } - } - catch (ReflectionTypeLoadException ex) - { - PxLoggingUtils.LogError(string.Format("Failed to load the ICustomeVerificationHandler '{0}': {1}.", - customHandlerName, ex.Message)); - } - catch (Exception ex) - { - PxLoggingUtils.LogError(string.Format("Encountered an error while retrieving the ICustomeVerificationHandler '{0}': {1}.", - customHandlerName, ex.Message)); - } - - return null; - } - } + } } diff --git a/PerimeterXModule/PxModuleConfigurationSection.cs b/PerimeterXModule/PxModuleConfigurationSection.cs index 0c9d4ae..5fdf5a3 100644 --- a/PerimeterXModule/PxModuleConfigurationSection.cs +++ b/PerimeterXModule/PxModuleConfigurationSection.cs @@ -746,5 +746,31 @@ public StringCollection LoginSuccessfulStatus } } + + [ConfigurationProperty("customLoginSuccessfulHandler")] + public string CustomLoginSuccessfulHandler + { + get + { + return (string)this["customLoginSuccessfulHandler"]; + } + set + { + this["customLoginSuccessfulHandler"] = value; + } + } + + [ConfigurationProperty("customCredentialsExtractionHandler")] + public string CustomCredentialsExtractionHandler + { + get + { + return (string)this["customCredentialsExtractionHandler"]; + } + set + { + this["customCredentialsExtractionHandler"] = value; + } + } } } From ed329287883c040184e850643867d16021bd48d2 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Tue, 21 Feb 2023 10:26:43 +0000 Subject: [PATCH 09/20] Add custom login successful option --- .../CustomBehavior/ILoginSuccessfulHandler.cs | 2 +- .../CustomLoginSuccessfulParser.cs | 34 +++++++++++++++++++ .../LoginSuccessfulParsetFactory.cs | 2 ++ .../CredentialsIntelligence/PxLoginData.cs | 1 - PerimeterXModule/PxModule.cs | 13 ++----- 5 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs diff --git a/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs b/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs index 8244593..46a4dcf 100644 --- a/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs +++ b/PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs @@ -4,6 +4,6 @@ namespace PerimeterX.CustomBehavior { public interface ILoginSuccessfulHandler { - bool Handle(HttpApplication HttpApplication); + bool Handle(HttpResponse httpResponse); } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs new file mode 100644 index 0000000..3f5ee6d --- /dev/null +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs @@ -0,0 +1,34 @@ +using System; +using System.Web; +using PerimeterX.CustomBehavior; + +namespace PerimeterX +{ + public class CustomLoginSuccessfulParser : ILoginSuccessfulParser + { + ILoginSuccessfulHandler loginSuccessfulParserhandler; + + public CustomLoginSuccessfulParser(PxModuleConfigurationSection config) + { + loginSuccessfulParserhandler = PxCustomFunctions.GetCustomLoginSuccessfulHandler(config.CustomLoginSuccessfulHandler); + } + + public bool IsLoginSuccessful(HttpResponse httpResponse) + { + try + { + if (loginSuccessfulParserhandler != null) + { + return loginSuccessfulParserhandler.Handle(httpResponse); + } + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug("An error occurred while executing login successful handler " + ex.Message); + + } + + return false; + } + } +} \ No newline at end of file diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs index 90d36e6..3846273 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs @@ -13,6 +13,8 @@ public static ILoginSuccessfulParser Create(PxModuleConfigurationSection config) return new HeaderLoginSuccessfulParser(config); case ("status"): return new StatusLoginSuccessfulParser(config); + case ("custom"): + return new CustomLoginSuccessfulParser(config); default: return null; } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index dab246a..8b51e79 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -47,7 +47,6 @@ public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, H return protocol.ProcessCredentials(extractedCredentials); } - return null; } catch (Exception ex) { PxLoggingUtils.LogError(string.Format("Failed to extract credentials.", ex.Message)); diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index b6067dc..bf39a0d 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -75,7 +75,6 @@ public class PxModule : IHttpModule private string nodeName; private bool loginCredentialsExtractionEnabled; private PxLoginData loginData; - private readonly ILoginSuccessfulHandler customLoginSuccessful; private readonly ICredentialsExtractionHandler customCredentialsExtraction; static PxModule() @@ -124,7 +123,6 @@ public PxModule() routesWhitelist = config.RoutesWhitelist; useragentsWhitelist = config.UseragentsWhitelist; enforceSpecificRoutes = config.EnforceSpecificRoutes; - customLoginSuccessful = PxCustomFunctions.GetCustomLoginSuccessfulHandler(config.CustomLoginSuccessfulHandler); customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); @@ -291,15 +289,8 @@ private void Application_EndRequest(object source, EventArgs e) public void HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config, HttpApplication application) { - bool isLoginSuccessful; - - if (customLoginSuccessful != null) - { - isLoginSuccessful = customLoginSuccessful.Handle(application); - } else { - ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParsetFactory.Create(config); - isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); - } + ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParsetFactory.Create(config); + bool isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); reporter.Post(AdditionalS2SUtils.CreateAdditionalS2SActivity(config, httpResponse.StatusCode, isLoginSuccessful, pxContext)); } From 8b26e14d76b739e060afcf8d1b186a7aba355671 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Tue, 21 Feb 2023 10:56:29 +0000 Subject: [PATCH 10/20] Changed the custom login extraction handling --- .../CredentialsIntelligence/PxLoginData.cs | 36 ++++++++++--------- PerimeterXModule/PerimeterXModule.csproj | 1 + 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index 8b51e79..b281a03 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -25,28 +25,20 @@ public PxLoginData(string ciVersion, List loginCredentialsExrac public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, HttpRequest request, ICredentialsExtractionHandler credentialsExtractionHandler) { try - { - ExtractedCredentials extractedCredentials = null; + { + ExtractorObject extractionDetails = FindMatchCredentialsDetails(request); - if (credentialsExtractionHandler != null) - { - extractedCredentials = credentialsExtractionHandler.Handle(request); - } - else + if (extractionDetails != null) { - ExtractorObject extractionDetails = FindMatchCredentialsDetails(request); + ExtractedCredentials extractedCredentials = ExtractLoginCredentials(context, request, credentialsExtractionHandler, extractionDetails); + - if (extractionDetails != null) + if (extractedCredentials != null) { - extractedCredentials = ExtractCredentials(extractionDetails, context, request); + return protocol.ProcessCredentials(extractedCredentials); } } - if (extractedCredentials != null) - { - return protocol.ProcessCredentials(extractedCredentials); - } - } catch (Exception ex) { PxLoggingUtils.LogError(string.Format("Failed to extract credentials.", ex.Message)); @@ -55,6 +47,18 @@ public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, H return null; } + private ExtractedCredentials ExtractLoginCredentials(PxContext context, HttpRequest request, ICredentialsExtractionHandler credentialsExtractionHandler, ExtractorObject extractionDetails) + { + if (credentialsExtractionHandler != null) + { + return credentialsExtractionHandler.Handle(request); + } + else + { + return HandleExtractCredentials(extractionDetails, context, request); + } + } + private ExtractorObject FindMatchCredentialsDetails(HttpRequest request) { foreach (ExtractorObject loginObject in this.loginCredentialsExtractor) @@ -86,7 +90,7 @@ private static bool IsRequestMatchLoginRequestConfiguration(ExtractorObject extr return false; } - private ExtractedCredentials ExtractCredentials(ExtractorObject extractionDetails, PxContext pxContext, HttpRequest request) + private ExtractedCredentials HandleExtractCredentials(ExtractorObject extractionDetails, PxContext pxContext, HttpRequest request) { string userFieldName = extractionDetails.UserFieldName; string passwordFieldName = extractionDetails.PassFieldName; diff --git a/PerimeterXModule/PerimeterXModule.csproj b/PerimeterXModule/PerimeterXModule.csproj index bbb5f5e..6182a21 100644 --- a/PerimeterXModule/PerimeterXModule.csproj +++ b/PerimeterXModule/PerimeterXModule.csproj @@ -66,6 +66,7 @@ + From 22138c9a535d97ca48c532026752bfd7278ac615 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Thu, 23 Feb 2023 14:36:48 +0000 Subject: [PATCH 11/20] Fixed comments --- .../LoginSuccessful/BodyLoginSuccessfulParser.cs | 3 +-- ...ultistepSSOCredentialsIntelligenceProtocol.cs | 9 +++++++-- .../CredentialsIntelligence/PxLoginData.cs | 16 +++++++--------- .../V2CredentialsIntelligenceProtocol.cs | 10 ++++++---- PerimeterXModule/Internals/Helpers/BodyReader.cs | 16 ++++++++++------ PerimeterXModule/Internals/PxContext.cs | 14 +++++++++++++- .../Internals/Validators/PXCookieValidator.cs | 2 +- 7 files changed, 45 insertions(+), 25 deletions(-) diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs index 28b4111..52133b6 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs @@ -13,8 +13,7 @@ public BodyLoginSuccessfulParser(PxModuleConfigurationSection config) { public bool IsLoginSuccessful(HttpResponse httpResponse) { - HttpResponse tempHttpResponse = httpResponse; - string body = ((OutputFilterStream)tempHttpResponse.Filter).ReadStream(); + string body = ((OutputFilterStream)httpResponse.Filter).ReadStream(); if (body == null) { return false; diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs index bc69d5d..ce7acdf 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/MultistepSSOCredentialsIntelligenceProtocol.cs @@ -8,17 +8,22 @@ public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedC string password = null; string ssoStep = MultistepSsoStep.PASSWORD; - if (extractedCredentials.Username != null) + if (extractedCredentials.Username != null && extractedCredentials.Username.Length > 0) { rawUsername = extractedCredentials.Username; ssoStep = MultistepSsoStep.USER; } - if (extractedCredentials.Password != null) + if (extractedCredentials.Password != null && extractedCredentials.Password.Length > 0) { password = PxCommonUtils.Sha256(extractedCredentials.Password); } + if (rawUsername == null && password == null) + { + return null; + } + return new LoginCredentialsFields( rawUsername, password, diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs index b281a03..d18f66b 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs @@ -100,7 +100,7 @@ private ExtractedCredentials HandleExtractCredentials(ExtractorObject extraction return null; } - Dictionary headers = pxContext.GetHeadersAsDictionary(); + Dictionary headers = pxContext.GetLowercaseHeadersAsDictionary(); if (extractionDetails.SentThrough == "header") { @@ -113,7 +113,7 @@ private ExtractedCredentials HandleExtractCredentials(ExtractorObject extraction ); } else if (extractionDetails.SentThrough == "body") { - return ExtractFromBodyAsync(userFieldName, passwordFieldName, headers, request).Result; + return ExtractFromBodyAsync(userFieldName, passwordFieldName, headers, request); } return null; @@ -121,21 +121,19 @@ private ExtractedCredentials HandleExtractCredentials(ExtractorObject extraction public static ExtractedCredentials ExtractFromHeader(string userFieldName, string passwordFieldName, Dictionary headers) { - bool isUsernameHeaderExist = headers.TryGetValue(userFieldName, out string userName); - bool isPasswordHeaderExist = headers.TryGetValue(passwordFieldName, out string password); + bool isUsernameHeaderExist = headers.TryGetValue(userFieldName.ToLower(), out string userName); + bool isPasswordHeaderExist = headers.TryGetValue(passwordFieldName.ToLower(), out string password); if (!isUsernameHeaderExist && !isPasswordHeaderExist) { return null; } return new ExtractedCredentials(userName, password); } - private async Task ExtractFromBodyAsync(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) + private ExtractedCredentials ExtractFromBodyAsync(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) { - bool isContentTypeHeaderExist = headers.TryGetValue("Content-Type", out string contentType); + bool isContentTypeHeaderExist = headers.TryGetValue("content-type", out string contentType); - HttpRequest httpRequest = request; - - string body = await BodyReader.ReadRequestBodyAsync(httpRequest); + string body = BodyReader.ReadRequestBodyAsync(request); if (!isContentTypeHeaderExist) { diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs index f7be3d3..755be61 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/V2CredentialsIntelligenceProtocol.cs @@ -5,19 +5,21 @@ public class V2CredentialsIntelligenceProtocol : ICredentialsIntelligenceProtoco public LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials) { - if (extractedCredentials.Username == null || extractedCredentials.Password == null) + string username = extractedCredentials.Username; + string password = extractedCredentials.Password; + if (username == null || password == null || username.Length == 0 || password.Length == 0) { return null; } - string normalizedUsername = PxCommonUtils.IsEmailAddress(extractedCredentials.Username) ? NormalizeEmailAddress(extractedCredentials.Username) : extractedCredentials.Username; + string normalizedUsername = PxCommonUtils.IsEmailAddress(username) ? NormalizeEmailAddress(username) : username; string hashedUsername = PxCommonUtils.Sha256(normalizedUsername); - string hashedPassword = HashPassword(hashedUsername, extractedCredentials.Password); + string hashedPassword = HashPassword(hashedUsername, password); return new LoginCredentialsFields( hashedUsername, hashedPassword, - extractedCredentials.Username, + username, CIVersion.V2 ); } diff --git a/PerimeterXModule/Internals/Helpers/BodyReader.cs b/PerimeterXModule/Internals/Helpers/BodyReader.cs index 5bb2e67..e3c5c49 100644 --- a/PerimeterXModule/Internals/Helpers/BodyReader.cs +++ b/PerimeterXModule/Internals/Helpers/BodyReader.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Runtime.Remoting.Contexts; +using System.Threading; using System.Threading.Tasks; using System.Web; @@ -6,14 +8,16 @@ namespace PerimeterX { public class BodyReader { - - public static async Task ReadRequestBodyAsync(HttpRequest request) + public static string ReadRequestBodyAsync(HttpRequest request) { - HttpRequest httpRequest = request; - - using (var reader = new StreamReader(httpRequest.InputStream)) + MemoryStream memstream = new MemoryStream(); + request.InputStream.CopyTo(memstream); + memstream.Position = 0; + using (StreamReader reader = new StreamReader(memstream)) { - return await reader.ReadToEndAsync(); + string text = reader.ReadToEnd(); + request.InputStream.Position = 0; + return text; } } } diff --git a/PerimeterXModule/Internals/PxContext.cs b/PerimeterXModule/Internals/PxContext.cs index 027862c..7d6b83d 100644 --- a/PerimeterXModule/Internals/PxContext.cs +++ b/PerimeterXModule/Internals/PxContext.cs @@ -287,7 +287,19 @@ public Dictionary GetHeadersAsDictionary() return headersDictionary; } - public string MapBlockAction() + public Dictionary GetLowercaseHeadersAsDictionary() + { + Dictionary headersDictionary = new Dictionary(); + + if (Headers != null && Headers.Count() > 0) + { + headersDictionary = Headers.ToDictionary(header => header.Name.ToLower(), header => header.Value); + } + + return headersDictionary; + } + + public string MapBlockAction() { if (string.IsNullOrEmpty(BlockAction)) { diff --git a/PerimeterXModule/Internals/Validators/PXCookieValidator.cs b/PerimeterXModule/Internals/Validators/PXCookieValidator.cs index b0a095e..3600a6a 100644 --- a/PerimeterXModule/Internals/Validators/PXCookieValidator.cs +++ b/PerimeterXModule/Internals/Validators/PXCookieValidator.cs @@ -103,7 +103,7 @@ public virtual bool Verify(PxContext context, IPxCookie pxCookie) return false; } - if (context.SensitiveRoute) + if (context.SensitiveRoute || context.LoginCredentialsFields != null) { PxLoggingUtils.LogDebug(string.Format("Cookie is valid but is a sensitive route {0}", context.Uri)); context.S2SCallReason = CALL_REASON_SENSITIVE_ROUTE; From e8d032159d637579f207cbeac9c08638fb71a050 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Thu, 23 Feb 2023 15:10:11 +0000 Subject: [PATCH 12/20] Added try and catch for the ci comfiguration --- PerimeterXModule/PxModule.cs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index bf39a0d..19c1b8b 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -60,7 +60,6 @@ public class PxModule : IHttpModule private readonly bool sendBlockActivities; private readonly int blockingScore; private readonly string appId; - private readonly IVerificationHandler customVerificationHandlerInstance; private readonly bool suppressContentBlock; private readonly bool challengeEnabled; private readonly string[] sensetiveHeaders; @@ -75,7 +74,8 @@ public class PxModule : IHttpModule private string nodeName; private bool loginCredentialsExtractionEnabled; private PxLoginData loginData; - private readonly ICredentialsExtractionHandler customCredentialsExtraction; + private IVerificationHandler customVerificationHandlerInstance; + private ICredentialsExtractionHandler customCredentialsExtraction; static PxModule() { @@ -115,7 +115,6 @@ public PxModule() cookieKeyBytes = Encoding.UTF8.GetBytes(cookieKey); blockingScore = config.BlockingScore; appId = config.AppId; - customVerificationHandlerInstance = PxCustomFunctions.GetCustomVerificationHandler(config.CustomVerificationHandler); suppressContentBlock = config.SuppressContentBlock; challengeEnabled = config.ChallengeEnabled; sensetiveHeaders = config.SensitiveHeaders.Cast().ToArray(); @@ -123,8 +122,6 @@ public PxModule() routesWhitelist = config.RoutesWhitelist; useragentsWhitelist = config.UseragentsWhitelist; enforceSpecificRoutes = config.EnforceSpecificRoutes; - customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); - InitCredentialsIntelligence(config); @@ -170,15 +167,23 @@ public string ModuleName private void InitCredentialsIntelligence(PxModuleConfigurationSection config) { - List loginCredentialsExtraction; - loginCredentialsExtractionEnabled = config.LoginCredentialsExtractionEnabled; + try + { + List loginCredentialsExtraction; + loginCredentialsExtractionEnabled = config.LoginCredentialsExtractionEnabled; - if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") + if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") + { + loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); + loginData = new PxLoginData(config.CiVersion, loginCredentialsExtraction); + customVerificationHandlerInstance = PxCustomFunctions.GetCustomVerificationHandler(config.CustomVerificationHandler); + customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); + } + } catch(Exception ex) { - loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); - loginData = new PxLoginData(config.CiVersion, loginCredentialsExtraction); - } - } + PxLoggingUtils.LogDebug("An error occurred while initiating the CI configuration " + ex.Message); + } + } public void Init(HttpApplication application) { From 116aee252f117d87b1f6e0ded438a296cab14c0f Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 27 Feb 2023 06:33:11 +0000 Subject: [PATCH 13/20] Fixed comments --- ...ta.cs => CredentialIntelligenceManager.cs} | 52 +++------------ .../CredentialsIntelligenceProtocolFactory.cs | 2 +- .../CustomLoginSuccessfulParser.cs | 8 +-- ...ory.cs => LoginSuccessfulParserFactory.cs} | 2 +- .../Internals/Helpers/BodyReader.cs | 47 +++++++++++++- .../Internals/Helpers/PxCommonUtils.cs | 10 +-- PerimeterXModule/PerimeterXModule.csproj | 4 +- PerimeterXModule/PxCustomFunctions.cs | 8 +-- PerimeterXModule/PxModule.cs | 63 ++++++++++++------- 9 files changed, 114 insertions(+), 82 deletions(-) rename PerimeterXModule/Internals/CredentialsIntelligence/{PxLoginData.cs => CredentialIntelligenceManager.cs} (79%) rename PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/{LoginSuccessfulParsetFactory.cs => LoginSuccessfulParserFactory.cs} (93%) diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs similarity index 79% rename from PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs rename to PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs index d18f66b..c7daf9f 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/PxLoginData.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs @@ -11,15 +11,15 @@ namespace PerimeterX { - public class PxLoginData + public class CredentialIntelligenceManager { private ICredentialsIntelligenceProtocol protocol; - private List loginCredentialsExtractor; + private List loginCredentialsExtractors; - public PxLoginData(string ciVersion, List loginCredentialsExraction) + public CredentialIntelligenceManager(string ciVersion, List loginCredentialsExraction) { this.protocol = CredentialsIntelligenceProtocolFactory.Create(ciVersion); - this.loginCredentialsExtractor = loginCredentialsExraction; + this.loginCredentialsExtractors = loginCredentialsExraction; } public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, HttpRequest request, ICredentialsExtractionHandler credentialsExtractionHandler) @@ -61,7 +61,7 @@ private ExtractedCredentials ExtractLoginCredentials(PxContext context, HttpRequ private ExtractorObject FindMatchCredentialsDetails(HttpRequest request) { - foreach (ExtractorObject loginObject in this.loginCredentialsExtractor) + foreach (ExtractorObject loginObject in this.loginCredentialsExtractors) { if (IsRequestMatchLoginRequestConfiguration(loginObject, request)) { @@ -133,7 +133,7 @@ private ExtractedCredentials ExtractFromBodyAsync(string userFieldName, string { bool isContentTypeHeaderExist = headers.TryGetValue("content-type", out string contentType); - string body = BodyReader.ReadRequestBodyAsync(request); + string body = BodyReader.ReadRequestBody(request); if (!isContentTypeHeaderExist) { @@ -171,49 +171,17 @@ private ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFie parametersDictionary.Add(key, parametersQueryString[key]); } - return ExtractCredentialsFromDictinary(parametersDictionary, userFieldName, passwordFieldName); + return ExtractCredentialsFromDictionary(parametersDictionary, userFieldName, passwordFieldName); } private ExtractedCredentials ExtractValueFromMultipart(string body, string contentType, string userFieldName, string passwordFieldName) { - var formData = new Dictionary(); + Dictionary formData = BodyReader.GetFormDataContentAsDictionary(body, contentType); - var boundary = contentType.Split(';') - .SingleOrDefault(x => x.Trim().StartsWith("boundary="))? - .Split('=')[1]; - - var parts = body.Split(new[] { "--" + boundary }, StringSplitOptions.RemoveEmptyEntries); - - foreach (var part in parts) - { - var lines = part.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); - - if (part.StartsWith("--")) - { - continue; - } - - var key = string.Empty; - var value = new StringBuilder(); - foreach (var line in lines) - { - if (line.StartsWith("Content-Disposition")) - { - key = line.Split(new[] { "name=" }, StringSplitOptions.RemoveEmptyEntries)[1].Trim('\"'); - } - else - { - value.Append(line); - } - } - - formData.Add(key, value.ToString()); - } - - return ExtractCredentialsFromDictinary(formData, userFieldName, passwordFieldName); + return ExtractCredentialsFromDictionary(formData, userFieldName, passwordFieldName); } - private ExtractedCredentials ExtractCredentialsFromDictinary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) + private ExtractedCredentials ExtractCredentialsFromDictionary(Dictionary parametersDictionary, string userFieldName, string passwordFieldName) { bool isUsernameExist = parametersDictionary.TryGetValue(userFieldName, out string userField); bool isPasswordExist = parametersDictionary.TryGetValue(passwordFieldName, out string passwordField); diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs index f4e9a0e..67c9f9c 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialsIntelligenceProtocolFactory.cs @@ -14,7 +14,7 @@ public static ICredentialsIntelligenceProtocol Create(string protocolVersion) case CIVersion.MULTISTEP_SSO: return new MultistepSSoCredentialsIntelligenceProtocol(); default: - throw new Exception("Unknown CI protocol version" + protocolVersion); + throw new Exception("Unknown CI protocol version: " + protocolVersion); } } } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs index 3f5ee6d..54c4a0e 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs @@ -6,20 +6,20 @@ namespace PerimeterX { public class CustomLoginSuccessfulParser : ILoginSuccessfulParser { - ILoginSuccessfulHandler loginSuccessfulParserhandler; + ILoginSuccessfulHandler loginSuccessfulParserHandler; public CustomLoginSuccessfulParser(PxModuleConfigurationSection config) { - loginSuccessfulParserhandler = PxCustomFunctions.GetCustomLoginSuccessfulHandler(config.CustomLoginSuccessfulHandler); + loginSuccessfulParserHandler = PxCustomFunctions.GetCustomLoginSuccessfulHandler(config.CustomLoginSuccessfulHandler); } public bool IsLoginSuccessful(HttpResponse httpResponse) { try { - if (loginSuccessfulParserhandler != null) + if (loginSuccessfulParserHandler != null) { - return loginSuccessfulParserhandler.Handle(httpResponse); + return loginSuccessfulParserHandler.Handle(httpResponse); } } catch (Exception ex) diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParserFactory.cs similarity index 93% rename from PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs rename to PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParserFactory.cs index 3846273..bd3fcf3 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParsetFactory.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/LoginSuccessfulParserFactory.cs @@ -1,7 +1,7 @@  namespace PerimeterX { - public class LoginSuccessfulParsetFactory + public class LoginSuccessfulParserFactory { public static ILoginSuccessfulParser Create(PxModuleConfigurationSection config) { diff --git a/PerimeterXModule/Internals/Helpers/BodyReader.cs b/PerimeterXModule/Internals/Helpers/BodyReader.cs index e3c5c49..1f4c420 100644 --- a/PerimeterXModule/Internals/Helpers/BodyReader.cs +++ b/PerimeterXModule/Internals/Helpers/BodyReader.cs @@ -1,5 +1,9 @@ -using System.IO; +using System.Collections.Generic; +using System; +using System.IO; +using System.Linq; using System.Runtime.Remoting.Contexts; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -8,7 +12,7 @@ namespace PerimeterX { public class BodyReader { - public static string ReadRequestBodyAsync(HttpRequest request) + public static string ReadRequestBody(HttpRequest request) { MemoryStream memstream = new MemoryStream(); request.InputStream.CopyTo(memstream); @@ -20,5 +24,44 @@ public static string ReadRequestBodyAsync(HttpRequest request) return text; } } + + public static Dictionary GetFormDataContentAsDictionary(string body, string contentType) + { + var formData = new Dictionary(); + + var boundary = contentType.Split(';') + .SingleOrDefault(x => x.Trim().StartsWith("boundary="))? + .Split('=')[1]; + + var parts = body.Split(new[] { "--" + boundary }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var part in parts) + { + var lines = part.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); + + if (part.StartsWith("--")) + { + continue; + } + + var key = string.Empty; + var value = new StringBuilder(); + foreach (var line in lines) + { + if (line.StartsWith("Content-Disposition")) + { + key = line.Split(new[] { "name=" }, StringSplitOptions.RemoveEmptyEntries)[1].Trim('\"'); + } + else + { + value.Append(line); + } + } + + formData.Add(key, value.ToString()); + } + + return formData; + } } } \ No newline at end of file diff --git a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs index 8abbc01..b1d9e2c 100644 --- a/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs +++ b/PerimeterXModule/Internals/Helpers/PxCommonUtils.cs @@ -11,7 +11,7 @@ namespace PerimeterX { class PxCommonUtils { - /** + /** * * Request helper, extracting the ip from the request according to socketIpHeader or from * the request socket when socketIpHeader is absent @@ -20,7 +20,7 @@ class PxCommonUtils * PxConfiguration * Ip from the request */ - public static string GetRequestIP(HttpContext context, PxModuleConfigurationSection pxConfig) + public static string GetRequestIP(HttpContext context, PxModuleConfigurationSection pxConfig) { // Get IP from custom header string socketIpHeader = pxConfig.SocketIpHeader; @@ -87,10 +87,10 @@ public static string Sha256(string str) } } - public static string ExtractValueFromNestedJson(string pathToValue, dynamic jsonObject) + public static string ExtractValueFromNestedJson(string pathToValue, dynamic jsonObject, char seperatorChar = '.') { - const char CREDENTIAL_FIELD_NESTING_DELIMITER = '.'; - string[] stepsToCredentialInBody = pathToValue.Split(CREDENTIAL_FIELD_NESTING_DELIMITER); + + string[] stepsToCredentialInBody = pathToValue.Split(seperatorChar); dynamic result = jsonObject; diff --git a/PerimeterXModule/PerimeterXModule.csproj b/PerimeterXModule/PerimeterXModule.csproj index 6182a21..1329ad0 100644 --- a/PerimeterXModule/PerimeterXModule.csproj +++ b/PerimeterXModule/PerimeterXModule.csproj @@ -77,9 +77,9 @@ - + - + diff --git a/PerimeterXModule/PxCustomFunctions.cs b/PerimeterXModule/PxCustomFunctions.cs index 6568d45..f12e721 100644 --- a/PerimeterXModule/PxCustomFunctions.cs +++ b/PerimeterXModule/PxCustomFunctions.cs @@ -18,7 +18,7 @@ public static ILoginSuccessfulHandler GetCustomLoginSuccessfulHandler(string cus try { var customLoginSuccessfulHandler = - getAssebliesTypes().FirstOrDefault(t => t.GetInterface(typeof(ILoginSuccessfulHandler).Name) != null && + getAssembliesTypes().FirstOrDefault(t => t.GetInterface(typeof(ILoginSuccessfulHandler).Name) != null && t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); if (customLoginSuccessfulHandler != null) @@ -48,7 +48,7 @@ public static ILoginSuccessfulHandler GetCustomLoginSuccessfulHandler(string cus return null; } - private static IEnumerable getAssebliesTypes() + private static IEnumerable getAssembliesTypes() { return AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => @@ -77,7 +77,7 @@ public static IVerificationHandler GetCustomVerificationHandler(string customHan try { - var customVerificationHandlerType = getAssebliesTypes() + var customVerificationHandlerType = getAssembliesTypes() .FirstOrDefault(t => t.GetInterface(typeof(IVerificationHandler).Name) != null && t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); @@ -117,7 +117,7 @@ public static ICredentialsExtractionHandler GetCustomLoginCredentialsExtractionH try { - var customCredentialsExtraction = getAssebliesTypes() + var customCredentialsExtraction = getAssembliesTypes() .FirstOrDefault(t => t.GetInterface(typeof(ICredentialsExtractionHandler).Name) != null && t.Name.Equals(customHandlerName) && t.IsClass && !t.IsAbstract); diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index 19c1b8b..49ab33f 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -73,7 +73,7 @@ public class PxModule : IHttpModule private readonly string osVersion; private string nodeName; private bool loginCredentialsExtractionEnabled; - private PxLoginData loginData; + private CredentialIntelligenceManager loginData; private IVerificationHandler customVerificationHandlerInstance; private ICredentialsExtractionHandler customCredentialsExtraction; @@ -115,7 +115,8 @@ public PxModule() cookieKeyBytes = Encoding.UTF8.GetBytes(cookieKey); blockingScore = config.BlockingScore; appId = config.AppId; - suppressContentBlock = config.SuppressContentBlock; + customVerificationHandlerInstance = PxCustomFunctions.GetCustomVerificationHandler(config.CustomVerificationHandler); + suppressContentBlock = config.SuppressContentBlock; challengeEnabled = config.ChallengeEnabled; sensetiveHeaders = config.SensitiveHeaders.Cast().ToArray(); fileExtWhitelist = config.FileExtWhitelist; @@ -175,8 +176,7 @@ private void InitCredentialsIntelligence(PxModuleConfigurationSection config) if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") { loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); - loginData = new PxLoginData(config.CiVersion, loginCredentialsExtraction); - customVerificationHandlerInstance = PxCustomFunctions.GetCustomVerificationHandler(config.CustomVerificationHandler); + loginData = new CredentialIntelligenceManager(config.CiVersion, loginCredentialsExtraction); customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); } } catch(Exception ex) @@ -197,8 +197,7 @@ private void Application_BeginRequest(object source, EventArgs e) try { HttpResponse response = HttpContext.Current.Response; - OutputFilterStream filter = new OutputFilterStream(response.Filter); - response.Filter = filter; + response.Filter = new OutputFilterStream(response.Filter); var application = (HttpApplication)source; @@ -283,20 +282,42 @@ private void Application_EndRequest(object source, EventArgs e) return; } - var applicationContext = application.Context; - var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - - if (!config.AdditionalS2SActivityHeaderEnabled && pxContext != null && pxContext.LoginCredentialsFields != null) - { - HandleLoginSuccessful(applicationContext.Response, config, application); - } - } + try + { + var applicationContext = application.Context; + var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); - public void HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config, HttpApplication application) - { - ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParsetFactory.Create(config); - bool isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); + if (!config.AdditionalS2SActivityHeaderEnabled && pxContext != null && pxContext.LoginCredentialsFields != null) + { + bool isLoginSuccessful = HandleLoginSuccessful(applicationContext.Response, config, application); + HandleAutomaticAdditionalS2SActivity(applicationContext.Response, config, isLoginSuccessful); + } + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug("Failed to handle end reqeust event: " + ex.Message); + } + } + + public bool HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config, HttpApplication application) + { + try + { + ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParserFactory.Create(config); + bool isLoginSuccessful; + + isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); + return isLoginSuccessful; + } + catch (Exception ex) + { + PxLoggingUtils.LogDebug("Error determining login status: " + ex.Message); + return false; + } + } + private void HandleAutomaticAdditionalS2SActivity(HttpResponse httpResponse, PxModuleConfigurationSection config, bool isLoginSuccessful) + { reporter.Post(AdditionalS2SUtils.CreateAdditionalS2SActivity(config, httpResponse.StatusCode, isLoginSuccessful, pxContext)); } @@ -615,7 +636,7 @@ private void HandleVerification(HttpApplication application) PxLoggingUtils.LogDebug("Monitor Mode is activated. passing request"); } - HandleCredentialsIntelligence(application, config); + AddCredentialIntelligenceHeadersToRequest(application, config); PxLoggingUtils.LogDebug(string.Format("Valid request to {0}", application.Context.Request.RawUrl)); PostPageRequestedActivity(pxContext); @@ -650,7 +671,7 @@ private static void SetPxhdAndVid(PxContext pxContext) } } - private void HandleCredentialsIntelligence(HttpApplication application, PxModuleConfigurationSection config) + private void AddCredentialIntelligenceHeadersToRequest(HttpApplication application, PxModuleConfigurationSection config) { if (config.LoginCredentialsExtractionEnabled && pxContext.LoginCredentialsFields != null) { @@ -663,7 +684,7 @@ private void HandleCredentialsIntelligence(HttpApplication application, PxModule { Activity activityPayload = AdditionalS2SUtils.CreateAdditionalS2SActivity(config, null, null, pxContext); application.Context.Request.Headers.Add("px-additional_activity", JSON.SerializeDynamic(activityPayload, PxConstants.JSON_OPTIONS)); - application.Context.Request.Headers.Add("px-backend-activity-url", PxConstants.FormatBaseUri(config) + PxConstants.ACTIVITIES_API_PATH); + application.Context.Request.Headers.Add("px-additional-activity-url", PxConstants.FormatBaseUri(config) + PxConstants.ACTIVITIES_API_PATH); } } } From 5e35be4c1dc34b8f36570ebdf5df9fd908b810e3 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 27 Feb 2023 09:57:13 +0000 Subject: [PATCH 14/20] added fields with null value to the additional s2s activity payload --- PerimeterXModule/PxModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index 49ab33f..fd58961 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -683,7 +683,7 @@ private void AddCredentialIntelligenceHeadersToRequest(HttpApplication applicati if (config.AdditionalS2SActivityHeaderEnabled) { Activity activityPayload = AdditionalS2SUtils.CreateAdditionalS2SActivity(config, null, null, pxContext); - application.Context.Request.Headers.Add("px-additional_activity", JSON.SerializeDynamic(activityPayload, PxConstants.JSON_OPTIONS)); + application.Context.Request.Headers.Add("px-additional_activity", JSON.SerializeDynamic(activityPayload, new Options(prettyPrint: false, excludeNulls: false, includeInherited: true))); application.Context.Request.Headers.Add("px-additional-activity-url", PxConstants.FormatBaseUri(config) + PxConstants.ACTIVITIES_API_PATH); } } From da126418d9a4cd7bb9d187867e71af3d2c7466dc Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Mon, 27 Feb 2023 13:59:40 +0000 Subject: [PATCH 15/20] Fixed comments --- .../CredentialsIntelligence/CredentialIntelligenceManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs index c7daf9f..3cd0acc 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/CredentialIntelligenceManager.cs @@ -113,7 +113,7 @@ private ExtractedCredentials HandleExtractCredentials(ExtractorObject extraction ); } else if (extractionDetails.SentThrough == "body") { - return ExtractFromBodyAsync(userFieldName, passwordFieldName, headers, request); + return ExtractFromBody(userFieldName, passwordFieldName, headers, request); } return null; @@ -129,7 +129,7 @@ public static ExtractedCredentials ExtractFromHeader(string userFieldName, strin return new ExtractedCredentials(userName, password); } - private ExtractedCredentials ExtractFromBodyAsync(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) + private ExtractedCredentials ExtractFromBody(string userFieldName, string passwordFieldName, Dictionary headers, HttpRequest request) { bool isContentTypeHeaderExist = headers.TryGetValue("content-type", out string contentType); From 375522d7a5b7dc61d2c8eaed1a1c85c4384d8117 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Tue, 28 Feb 2023 08:29:17 +0000 Subject: [PATCH 16/20] Is login succesful fix --- .../BodyLoginSuccessfulParser.cs | 4 ++-- .../CustomLoginSuccessfulParser.cs | 8 ++++---- .../HeaderLoginSuccessfulParser.cs | 2 +- .../LoginSuccessful/ILoginSuccessfulParser.cs | 2 +- .../StatusLoginSuccessfulParser.cs | 2 +- PerimeterXModule/PxModule.cs | 17 ++++++++++------- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs index 52133b6..db8a1d8 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/BodyLoginSuccessfulParser.cs @@ -11,12 +11,12 @@ public BodyLoginSuccessfulParser(PxModuleConfigurationSection config) { bodyRegex = config.LoginSuccessfulBodyRegex; } - public bool IsLoginSuccessful(HttpResponse httpResponse) + public bool? IsLoginSuccessful(HttpResponse httpResponse) { string body = ((OutputFilterStream)httpResponse.Filter).ReadStream(); if (body == null) { - return false; + return null; } return Regex.IsMatch(body, bodyRegex); diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs index 54c4a0e..9f5868d 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/CustomLoginSuccessfulParser.cs @@ -13,22 +13,22 @@ public CustomLoginSuccessfulParser(PxModuleConfigurationSection config) loginSuccessfulParserHandler = PxCustomFunctions.GetCustomLoginSuccessfulHandler(config.CustomLoginSuccessfulHandler); } - public bool IsLoginSuccessful(HttpResponse httpResponse) + public bool? IsLoginSuccessful(HttpResponse httpResponse) { try { if (loginSuccessfulParserHandler != null) { return loginSuccessfulParserHandler.Handle(httpResponse); + } } catch (Exception ex) { - PxLoggingUtils.LogDebug("An error occurred while executing login successful handler " + ex.Message); - + PxLoggingUtils.LogDebug("An error occurred while executing login successful handler " + ex.Message); } - return false; + return null; } } } \ No newline at end of file diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs index 339fecf..9f779a4 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/HeaderLoginSuccessfulParser.cs @@ -13,7 +13,7 @@ public HeaderLoginSuccessfulParser(PxModuleConfigurationSection config) successfulHeaderValue = config.LoginSuccessfulHeaderValue; } - public bool IsLoginSuccessful(HttpResponse httpResponse) + public bool? IsLoginSuccessful(HttpResponse httpResponse) { return httpResponse.Headers[successfulHeaderName] == successfulHeaderValue; } diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs index f66f3ef..990f6a3 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/ILoginSuccessfulParser.cs @@ -4,6 +4,6 @@ namespace PerimeterX { public interface ILoginSuccessfulParser { - bool IsLoginSuccessful(HttpResponse httpResponse); + bool? IsLoginSuccessful(HttpResponse httpResponse); } } \ No newline at end of file diff --git a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs index f5e3365..7de55a0 100644 --- a/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs +++ b/PerimeterXModule/Internals/CredentialsIntelligence/LoginSuccessful/StatusLoginSuccessfulParser.cs @@ -13,7 +13,7 @@ public StatusLoginSuccessfulParser(PxModuleConfigurationSection config) successfulStatuses = config.LoginSuccessfulStatus.Cast().ToList(); } - public bool IsLoginSuccessful(HttpResponse httpResponse) + public bool? IsLoginSuccessful(HttpResponse httpResponse) { return successfulStatuses.Contains(httpResponse.StatusCode.ToString()); } diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index fd58961..e4d17b9 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -289,7 +289,7 @@ private void Application_EndRequest(object source, EventArgs e) if (!config.AdditionalS2SActivityHeaderEnabled && pxContext != null && pxContext.LoginCredentialsFields != null) { - bool isLoginSuccessful = HandleLoginSuccessful(applicationContext.Response, config, application); + bool? isLoginSuccessful = HandleLoginSuccessful(applicationContext.Response, config, application); HandleAutomaticAdditionalS2SActivity(applicationContext.Response, config, isLoginSuccessful); } } @@ -299,24 +299,27 @@ private void Application_EndRequest(object source, EventArgs e) } } - public bool HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config, HttpApplication application) + public bool? HandleLoginSuccessful(HttpResponse httpResponse, PxModuleConfigurationSection config, HttpApplication application) { try { ILoginSuccessfulParser loginSuccessfulParser = LoginSuccessfulParserFactory.Create(config); - bool isLoginSuccessful; + bool? isLoginSuccessful = loginSuccessfulParser.IsLoginSuccessful(httpResponse); - isLoginSuccessful = loginSuccessfulParser != null && loginSuccessfulParser.IsLoginSuccessful(httpResponse); - return isLoginSuccessful; + if (loginSuccessfulParser != null && isLoginSuccessful.HasValue) + { + return isLoginSuccessful; + } } catch (Exception ex) { PxLoggingUtils.LogDebug("Error determining login status: " + ex.Message); - return false; } + + return null; } - private void HandleAutomaticAdditionalS2SActivity(HttpResponse httpResponse, PxModuleConfigurationSection config, bool isLoginSuccessful) + private void HandleAutomaticAdditionalS2SActivity(HttpResponse httpResponse, PxModuleConfigurationSection config, bool? isLoginSuccessful) { reporter.Post(AdditionalS2SUtils.CreateAdditionalS2SActivity(config, httpResponse.StatusCode, isLoginSuccessful, pxContext)); } From 9e532c98014e8d66c18113ec1e177821f3526d1e Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Wed, 1 Mar 2023 07:31:43 +0000 Subject: [PATCH 17/20] Fixed comments --- PerimeterXModule/PxModule.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index e4d17b9..dff675f 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -73,7 +73,7 @@ public class PxModule : IHttpModule private readonly string osVersion; private string nodeName; private bool loginCredentialsExtractionEnabled; - private CredentialIntelligenceManager loginData; + private CredentialIntelligenceManager ciManager; private IVerificationHandler customVerificationHandlerInstance; private ICredentialsExtractionHandler customCredentialsExtraction; @@ -176,7 +176,7 @@ private void InitCredentialsIntelligence(PxModuleConfigurationSection config) if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") { loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); - loginData = new CredentialIntelligenceManager(config.CiVersion, loginCredentialsExtraction); + ciManager = new CredentialIntelligenceManager(config.CiVersion, loginCredentialsExtraction); customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); } } catch(Exception ex) @@ -586,9 +586,9 @@ private void VerifyRequest(HttpApplication application) var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); pxContext = new PxContext(application.Context, config); - if (loginData != null) + if (ciManager != null) { - LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentialsFromRequest(pxContext, application.Context.Request, customCredentialsExtraction); + LoginCredentialsFields loginCredentialsFields = ciManager.ExtractCredentialsFromRequest(pxContext, application.Context.Request, customCredentialsExtraction); if (loginCredentialsFields != null) { pxContext.LoginCredentialsFields = loginCredentialsFields; @@ -686,7 +686,7 @@ private void AddCredentialIntelligenceHeadersToRequest(HttpApplication applicati if (config.AdditionalS2SActivityHeaderEnabled) { Activity activityPayload = AdditionalS2SUtils.CreateAdditionalS2SActivity(config, null, null, pxContext); - application.Context.Request.Headers.Add("px-additional_activity", JSON.SerializeDynamic(activityPayload, new Options(prettyPrint: false, excludeNulls: false, includeInherited: true))); + application.Context.Request.Headers.Add("px-additional-activity", JSON.SerializeDynamic(activityPayload, new Options(prettyPrint: false, excludeNulls: false, includeInherited: true))); application.Context.Request.Headers.Add("px-additional-activity-url", PxConstants.FormatBaseUri(config) + PxConstants.ACTIVITIES_API_PATH); } } From 5167dcface1cf10cb723f3d27644f6280542f39f Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Wed, 1 Mar 2023 08:45:31 +0000 Subject: [PATCH 18/20] Added requestId to all activities --- .../DataContracts/Activities/ActivityDetails.cs | 3 +++ PerimeterXModule/DataContracts/Requests/Additional.cs | 3 +++ .../Internals/Validators/PXS2SValidator.cs | 5 +++-- PerimeterXModule/PxModule.cs | 10 ++++++---- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs b/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs index 5579f46..b0dbb77 100644 --- a/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs +++ b/PerimeterXModule/DataContracts/Activities/ActivityDetails.cs @@ -49,6 +49,9 @@ public class ActivityDetails : IActivityDetails [DataMember(Name = "sso_step")] public string SsoStep { get; set; } + + [DataMember(Name = "request_id")] + public string RequestId { get; set; } } [DataContract] diff --git a/PerimeterXModule/DataContracts/Requests/Additional.cs b/PerimeterXModule/DataContracts/Requests/Additional.cs index 197dd40..6f8767c 100644 --- a/PerimeterXModule/DataContracts/Requests/Additional.cs +++ b/PerimeterXModule/DataContracts/Requests/Additional.cs @@ -62,5 +62,8 @@ public class Additional [DataMember(Name = "sso_step", IsRequired = false)] public string SsoStep; + [DataMember(Name = "request_id")] + public string RequestId { get; set; } + } } diff --git a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs index 5f0641c..bcd1d4f 100644 --- a/PerimeterXModule/Internals/Validators/PXS2SValidator.cs +++ b/PerimeterXModule/Internals/Validators/PXS2SValidator.cs @@ -110,8 +110,9 @@ public RiskResponse SendRiskResponse(PxContext PxContext) PxCookieHMAC = PxContext.PxCookieHmac, CookieOrigin = PxContext.CookieOrigin, RequestCookieNames = PxContext.CookieNames, - VidSource = PxContext.VidSource - }, + VidSource = PxContext.VidSource, + RequestId = PxContext.RequestId + }, FirstParty = PxConfig.FirstPartyEnabled }; diff --git a/PerimeterXModule/PxModule.cs b/PerimeterXModule/PxModule.cs index dff675f..9dab783 100644 --- a/PerimeterXModule/PxModule.cs +++ b/PerimeterXModule/PxModule.cs @@ -73,7 +73,7 @@ public class PxModule : IHttpModule private readonly string osVersion; private string nodeName; private bool loginCredentialsExtractionEnabled; - private CredentialIntelligenceManager ciManager; + private CredentialIntelligenceManager loginData; private IVerificationHandler customVerificationHandlerInstance; private ICredentialsExtractionHandler customCredentialsExtraction; @@ -176,7 +176,7 @@ private void InitCredentialsIntelligence(PxModuleConfigurationSection config) if (loginCredentialsExtractionEnabled && config.LoginCredentialsExtraction != "") { loginCredentialsExtraction = JSON.Deserialize>(config.LoginCredentialsExtraction, PxConstants.JSON_OPTIONS); - ciManager = new CredentialIntelligenceManager(config.CiVersion, loginCredentialsExtraction); + loginData = new CredentialIntelligenceManager(config.CiVersion, loginCredentialsExtraction); customCredentialsExtraction = PxCustomFunctions.GetCustomLoginCredentialsExtractionHandler(config.CustomCredentialsExtractionHandler); } } catch(Exception ex) @@ -463,6 +463,8 @@ private void PostActivity(PxContext pxContext, string eventType, ActivityDetails } } + details.RequestId = pxContext.RequestId; + var activity = new Activity { Type = eventType, @@ -586,9 +588,9 @@ private void VerifyRequest(HttpApplication application) var config = (PxModuleConfigurationSection)ConfigurationManager.GetSection(PxConstants.CONFIG_SECTION); pxContext = new PxContext(application.Context, config); - if (ciManager != null) + if (loginData != null) { - LoginCredentialsFields loginCredentialsFields = ciManager.ExtractCredentialsFromRequest(pxContext, application.Context.Request, customCredentialsExtraction); + LoginCredentialsFields loginCredentialsFields = loginData.ExtractCredentialsFromRequest(pxContext, application.Context.Request, customCredentialsExtraction); if (loginCredentialsFields != null) { pxContext.LoginCredentialsFields = loginCredentialsFields; From cac87dbde8fcce7e380080bc87aa5fef6e91d965 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Wed, 1 Mar 2023 10:38:47 +0000 Subject: [PATCH 19/20] Released version 3.2.0 --- CHANGELOG.md | 10 ++++++++++ PerimeterXModule/Properties/AssemblyInfo.cs | 6 +++--- README.md | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd612f..1811e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## [3.2.0] - 2023-01-03 +### Added +- Support for credentials intelligence protocols `v2` and `multistep_sso` +- Support for login successful reporting methods `header`, `status`, `body`, and `custom` +- Support for automatic sending of `additional_s2s` activity +- Support for manual sending of `additional_s2s` activity via header +- Support for sending raw username on `additional_s2s` activity +- Support for login credentials extraction via custom function +- New `request_id` field to all enforcer activities + ## [3.1.4] - 2022-12-05 ### Added - Compatibility with .NET Framework 4.7 and higher diff --git a/PerimeterXModule/Properties/AssemblyInfo.cs b/PerimeterXModule/Properties/AssemblyInfo.cs index 81e00a4..66c8491 100644 --- a/PerimeterXModule/Properties/AssemblyInfo.cs +++ b/PerimeterXModule/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("PerimeterX")] [assembly: AssemblyProduct("PxModule")] -[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] @@ -23,5 +23,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.1.4")] -[assembly: AssemblyFileVersion("3.1.4")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] diff --git a/README.md b/README.md index 837d5ec..dabedfa 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [PerimeterX](http://www.perimeterx.com) ASP.NET SDK =================================================== -> Latest stable version: [v3.1.4](https://www.nuget.org/packages/PerimeterXModule/3.1.4) +> Latest stable version: [v3.2.0](https://www.nuget.org/packages/PerimeterXModule/3.2.0) Table of Contents ----------------- From 0e0f6da08a5d0f878983b9dfcb22ccede7911ff6 Mon Sep 17 00:00:00 2001 From: chen-zimmer-px Date: Wed, 1 Mar 2023 12:35:00 +0000 Subject: [PATCH 20/20] Added px_metadata --- px_metadata.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 px_metadata.json diff --git a/px_metadata.json b/px_metadata.json new file mode 100644 index 0000000..10bc77b --- /dev/null +++ b/px_metadata.json @@ -0,0 +1,32 @@ +{ + "version": "3.2.0", + "supported_features": [ + "additional_activity_handler", + "advanced_blocking_response", + "batched_activities", + "block_activity", + "block_page_captcha", + "bypass_monitor_header", + "client_ip_extraction", + "css_ref", + "custom_cookie_header", + "custom_logo", + "enforce_specific_routes", + "filter_by_extension", + "filter_by_route", + "first_party", + "js_ref", + "credentials_intelligence", + "mobile_support", + "module_enable", + "module_mode", + "page_requested_activity", + "pxde", + "pxhd", + "risk_api", + "sensitive_routes", + "sensitive_headers", + "telemetry_command", + "vid_extraction" + ] +} \ No newline at end of file