Simple Brute force API shield
If you have split your site into two elements; Simple UI and an API that does all the heavy lifting/communicated with the DB. You need to be able to protect the API from simple brute force attacks. A simple attack could be a fresh of the site 1000 times per mileseconds or an unknow constant loop.. After some times the UI server or the API serve may start creaking. By adding a simple Shield between the UI and the API, could stop your site going down and report back to you if an issue has been seen.

Recording the invoked
Each invoke needs to be recorded and I have created. So it know how many times it been invoked and how long ago it was invoked. This logic will use "System.Runtime.Caching.MemoryCache" to hold the memory. The logic store a unique value for the invoke, how many times it has been invoked and the date time of the invoke. Each unique stored record will only be kept in memory 3 seconds. If the three second elapsed the count of the unique call will start at 1.

public bool SetInMemory(string memoryLocation)
{

try
{
System.Runtime.Caching.ObjectCache cache = System.Runtime.Caching.MemoryCache.Default;
memoryDataset.CallMade++;
cache.Set(memoryLocation.ToString(), memoryDataset, DateTime.Now.AddSeconds(6));
return true;
}
catch
{

}
return false;


}


Unique record name
With this logic I use the location the check was called from and the main URL element called.

private static string getUniqueKey(string keyCheck)
{
var s = keyCheck.Split("/");
var l = keyCheck.IndexOf("/api/");
if (l > 0)
return String.Join("-", keyCheck.Remove(0, l));
else
return keyCheck;
}



Getting the unique record
As the set uses "System.Runtime.Caching.MemoryCache" to hold the records, it needs to query this Cache to see if there is a record. If there is one, it simply returns the record. If not it will create a new record and set the count to 1.
   
private void getInMemory()
{
if (MemoryDataset == null)
{
MemoryDataset = new APIBruteForceDataSetDataClass();
MemoryDataset.BruteForceDetected = false;
MemoryDataset.Time = DateTime.Now;
MemoryDataset.Elements = new List();
setExpiry();
}
try
{
var co = cache.AddOrGetExisting(memoryKey, MemoryDataset, policy);
if (co !=null)
MemoryDataset = (APIBruteForceDataSetDataClass)co;

}
catch
{

}


}



Detecting a loop or attack
This logic is simple it will see if the unique record is within 2 seconds or the last recorded record and if it has counted 10 or more records within this 2 seconds. If it does then it will break the return false.

public bool AllGood(BruteForceAPIShieldMemoryENUM memoryLocation, string keyCheck)
{


if (!shieldOn)
return true;//If shield is no on, then let all calls go through. If it dies. It dies!

if (dataSetStorage.IsBruteForceFound())
return false;

var uniqueKey = getUniqueKey(keyCheck);
if (dataSetStorage.MemoryDataset.CallMade.Equals(0))
{
dataSetStorage.SetInMemory(dataSetStorage.MemoryDataset);
return true;//The last call was no the same
}
//Add the unique key to the memory
getLargestGroup();
var allowProcess = true;
if (largetsGroupingCount >= shieldLimit)
{
sendLoopDetected(memoryLocation, keyCheck);
allowProcess = false;//Hell there is an issue, Flag and stop!
dataSetStorage.SetBruteForceFound();
}else
{
dataSetStorage.MemoryDataset.Elements.Add(new APIBruteForceDataSetElementDataClass() { UniqueKey = uniqueKey, Time = dataSetStorage.MemoryDataset.Time });
allowProcess = dataSetStorage.SetInMemory(dataSetStorage.MemoryDataset);
}



return allowProcess;
}




Remove cache
Once the UI has gone to a safe place. I've made logic to make sure none of the cache memory is still in memory. Just encase it invoked a false attack response.

public void RemoveBruteForceFound(string memoryLocation)
{
dataSetStorage.RemoveBruteForce();
}



Get the largest group
Need the stored group, which has the largest number of hits

private void getLargestGroup()
{
largetsGroupingKey = String.Empty;
largetsGroupingCount = 0;
var grouppedElement = dataSetStorage.MemoryDataset.Elements.GroupBy(g => g.UniqueKey).OrderByDescending(x=>x.Count()).FirstOrDefault();
if (grouppedElement == null)
return;

largetsGroupingKey = grouppedElement.Key;
largetsGroupingCount = grouppedElement.Count()+1;

}



Inform detection
You can do this as you want, but I get this to email me to tell me that an issue has been detected. Here I am using three different types of Emails. One is that there seems to be quick hits of a type of call. Another one is that a number of the unique call has been hit, but it will wait and try again. The last one is to say intervention has been taken


private void sendLoopDetected(BruteForceAPIShieldMemoryENUM memoryLocation, string result)
{
SendMessage(String.Format(@"Monitoring {0} Loop Will try and Break out {1} History
    {2}
", memoryLocation.ToString(), result, String.Join(String.Empty, dataSetStorage.MemoryDataset.Elements.Select(s => String.Format("
  • {0} - {1}
  • ", s.Time.ToString("dd/MM/yyyy HH:mm:ss.ms"), s.UniqueKey)))));
    }


    Full code
    below, if you want a break down scroll down and I will go through each part.

    public class BruteForceAPIShield
    {
    private static bool doNotSendAudit = false;
    public bool tryAgain;
    public bool shieldOn;
    public bool SendAudit;
    public Interfaces.IBruteForceAPIShieldDataSet dataSetStorage;
    private int shieldLimit;
    private int shieldFlagLimit;
    private int shieldLatency;
    private int shieldSleepLevel;
    private int largetsGroupingCount;
    private string largetsGroupingKey;

    public static bool Found(string id)
    {

    var classObject = new BruteForceAPIShield(id);
    return classObject.HasBruteForceFound();

    }


    public BruteForceAPIShield(string uid)
    {
    shieldOn = AppSettingsHelper.GetBool(ENUMS.APPSettingENUM.BruteForceShieldOn);
    shieldLimit = AppSettingsHelper.GetInt(ENUMS.APPSettingENUM.BruteForceShieldLimit);
    shieldLatency = AppSettingsHelper.GetInt(ENUMS.APPSettingENUM.BruteForcehieldLatency);
    if (shieldLimit.Equals(0))
    shieldLimit = 100;//this is the default value
    if (shieldLatency.Equals(0))
    shieldLatency = 5;//this is the default value
    shieldFlagLimit = shieldLimit / 2;
    shieldSleepLevel = shieldFlagLimit/ 2;
    SendAudit = true;
    tryAgain = false;
    dataSetStorage = new BruteForceAPIShieldDataSetInMemoryCache(shieldLatency, uid);
    }

    public BruteForceAPIShield(bool sOn, int sLimit, int sLatency, string uid)
    {
    shieldOn = sOn;
    shieldLimit = sLimit;
    shieldLatency = sLatency;
    if (shieldLimit.Equals(0))
    shieldLimit = 100;//this is the default value
    if (shieldLatency.Equals(0))
    shieldLatency = 5;//this is the default value
    shieldFlagLimit = shieldLimit / 2;
    shieldSleepLevel = 5;
    tryAgain = false;
    SendAudit = false;
    dataSetStorage = new BruteForceAPIShieldDataSetInMemoryCache(shieldLatency, uid);
    }

    public static bool AllGoodStatic(BruteForceAPIShieldMemoryENUM memoryLocation, string uId, string keyCheck)
    {

    var classObject = new BruteForceAPIShield(uId);
    return classObject.AllGood(memoryLocation,keyCheck);


    }

    public static bool AllGoodNoAudit(BruteForceAPIShieldMemoryENUM memoryLocation, string keyCheck)
    {
    var classObject = new BruteForceAPIShield(Guid.Empty.ToString());
    doNotSendAudit = true;
    var outcome = classObject.AllGood(memoryLocation,keyCheck);
    doNotSendAudit = false;
    classObject = null;
    return outcome;

    }


    private string getUniqueKey(string keyCheck)
    {
    var s = keyCheck.Split("/");
    var l = keyCheck.IndexOf("/api/");
    if (l > 0)
    return String.Join("-", keyCheck.Remove(0, l));
    else
    return keyCheck;




    }

    public bool AllGood(BruteForceAPIShieldMemoryENUM memoryLocation, string keyCheck)
    {


    if (!shieldOn)
    return true;//If shield is no on, then let all calls go through. If it dies. It dies!

    if (dataSetStorage.IsBruteForceFound())
    return false;

    var uniqueKey = getUniqueKey(keyCheck);
    if (dataSetStorage.MemoryDataset.CallMade.Equals(0))
    {
    dataSetStorage.SetInMemory(dataSetStorage.MemoryDataset);
    return true;//The last call was no the same
    }
    //Add the unique key to the memory
    getLargestGroup();
    var allowProcess = true;
    if (largetsGroupingCount >= shieldLimit)
    {
    sendLoopDetected(memoryLocation, keyCheck);
    allowProcess = false;//Hell there is an issue, Flag and stop!
    dataSetStorage.SetBruteForceFound();
    }else
    {
    dataSetStorage.MemoryDataset.Elements.Add(new APIBruteForceDataSetElementDataClass() { UniqueKey = uniqueKey, Time = dataSetStorage.MemoryDataset.Time });
    allowProcess = dataSetStorage.SetInMemory(dataSetStorage.MemoryDataset);
    }



    return allowProcess;
    }

    private void getLargestGroup()
    {
    largetsGroupingKey = String.Empty;
    largetsGroupingCount = 0;
    var grouppedElement = dataSetStorage.MemoryDataset.Elements.GroupBy(g => g.UniqueKey).OrderByDescending(x=>x.Count()).FirstOrDefault();
    if (grouppedElement == null)
    return;

    largetsGroupingKey = grouppedElement.Key;
    largetsGroupingCount = grouppedElement.Count()+1;

    }

    private void sendLoopDetected(BruteForceAPIShieldMemoryENUM memoryLocation, string result)
    {
    if (!SendAudit || doNotSendAudit)
    return;

    SendMessage( String.Format(@"Monitoring {0} Loop Will try and Break out {1} History
      {2}
    ", memoryLocation.ToString(), result, String.Join(String.Empty, dataSetStorage.MemoryDataset.Elements.Select(s => String.Format("
  • {0} - {1}
  • ", s.Time.ToString("dd/MM/yyyy HH:mm:ss.ms"), s.UniqueKey)))));

    }

    public bool HasBruteForceFound()
    {
    return dataSetStorage.IsBruteForceFound();
    }
    public void RemoveBruteForceFound(string memoryLocation)
    {
    dataSetStorage.RemoveBruteForce();
    }
    }


    Data elements
    The data is stored in the Cache as object of the below data classes.

    public class APIBruteForceDataSetDataClass
    {
    public List Elements { get; set; }
    public DateTime Time { get; set; }
    public int CallMade { get; set; }
    }
    public class APIBruteForceDataSetElementDataClass
    {

    public string UniqueKey { get; set; }
    public DateTime Time { get; set; }
    }





    Latest blogs

    Buy me a coffeeBuy me a coffee
    Created: 30/06/2022 Total Comment: 0

    Comments

    (Not Displayed)
    Human validation
    Enter 8619 number, before submitting to confirm your human
    [Home] [All Blogs] [Contact] [Me]
    Buy me a coffeeBuy me a coffee