Automated Threat Detection mit CEP - Token Cracking

Dies ist der zweite Teil der Artikelserie Automated Threat Detection mit CEP, die aufzeigt, wie mittels komplexer Ereignisverarbeitung Angriffe auf eine Website entdeckt werden können. Die Serie besteht aus den folgenden Artikel:

Dieser Artikel behandelt Token Cracking und Credential Cracking. Schauen wir uns zuerst die Spezifikation von OWASP an.

OAT-002 - Token Cracking
Mass enumeration of coupon numbers, voucher codes, discount tokens, etc.
Quelle: OWASP Automated Threat Handbook (PDF)

OAT-007 - Credential Cracking
Identify valid login credentials by trying different values for usernames and/or passwords.
Quelle: OWASP Automated Threat Handbook (PDF)

Beim Token Cracking versucht ein Angreifer, in einem Onlineshop einen Rabattcode, oder in einem Gewinnspiel eine gültige Losnummer herauszufinden. Dazu gibt es viele Gründe. Bleiben solche Angriffe unentdeckt, können daraus massive finanzielle Schäden entstehen.

Beim Credential Cracking versucht ein Angreifer herauszufinden, ob ein bestimmter Benutzername auf dem System schon existiert und welches das dazugehörige korrekte Passwort ist. Ist ein solcher Angriff erfolgreich, kann der Angreifer das betreffende Konto übernehmen und missbrauchen.

Wahrscheinlich gibt es einen Eintrag in einem Logfile, wenn ein Rabattcode, eine Losnummer oder ein Benutzername nicht existiert, oder das Passwort nicht übereinstimmt. Um automatisierte Angriffe zu entdecken und angemessen darauf zu reagieren, müssen wir diese Ereignisse jedoch sammeln und auswerten.

Zuerst schreiben wir einen Generator, der Events simuliert:

public class InvalidTokenEvent
{
    public string IpAddress { get; set; }
}

public class RandomEventGenerator : IObservable<InvalidTokenEvent>
{
    private static readonly string[] _ipAddresses =
    {
        "192.168.1.100",
        "192.168.1.100",
        "192.168.1.101",
        "192.168.1.102",
        "192.168.1.103",
        "192.168.1.104"
    };

    private readonly Random _random = new Random();

    public IDisposable Subscribe(IObserver<InvalidTokenEvent> observer)
    {
        var disposer = new Disposer();

        try
        {
            while (!disposer.IsDisposed)
            {
                Thread.Sleep(_random.Next(100, 1000));
                observer.OnNext(CreateEvent());
            }

            observer.OnCompleted();
        }
        catch (Exception ex)
        {
            observer.OnError(ex);
        }

        return disposer;
    }

    private InvalidTokenEvent CreateEvent()
    {
        var ipAddress = _ipAddresses[_random.Next(_ipAddresses.Length)];

        return new InvalidTokenEvent
        {
            IpAddress = ipAddress,
        };
    }

    private class Disposer : IDisposable
    {
        public bool IsDisposed { get; private set; }

        public void Dispose()
        {
            IsDisposed = true;
        }
    }
}

Der RandomEventGenerator implementiert das Interface IObservable<T>. Dadurch kann der Generator als Quelle abonniert werden.

Der Generator erstellt nun zwischen einem und zehn Events pro Sekunde. Jeweils mit einer zufälligen IP Adresse aus dem statischen Array. Die erste IP Adresse kommt dabei doppelt vor, sodass davon mehr Events generiert werden.

Nun definieren wir, wie ein Angriff erkannt wird. Ein Angriff ist es dann, wenn innerhalb von zehn Sekunden mehr als zehn Events mit der gleichen IP Adresse erstellt werden. Wenn ein ehrlicher Benutzer sich zwei oder dreimal vertippt, soll das noch nicht als Angriff gelten. Für diese Analyse brauchen wir ein Sliding Time Window. Wir prüfen also jede Sekunde, wie viele Events innerhalb der letzten zehn Sekunden mit welcher IP Adresse erstellt wurden. In Rx nutzen wir dafür die Buffer(TimeSpan timeSpan, TimeSpan timeShift) Methode.

var source = new RandomEventGenerator();

source
    .SubscribeOn(TaskPoolScheduler.Default)
    .Buffer(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(1))
    .Subscribe(new InvalidTokensByIpAddressAggregator());

Der InvalidTokensByIpAddressAggregator implementiert IObserver<IList<InvalidTokenEvent>> und wird nun jede Sekunde einmal aufgerufen. Wir gruppieren darin die Events nach IP Adresse und markieren Eventgruppen mit mehr als zehn Einträgen als Angreifer. In einem richtigen Projekt müsste man hier natürlich irgend einem Service mitteilen, dass diese IP Adresse, zumindest temporär, gesperrt werden soll. In einem späteren Blogpost zeige ich, wie man das am saubersten löst.

public class InvalidTokensByIpAddressAggregator : IObserver<IList<InvalidTokenEvent>>
{
    public void OnNext(IList<InvalidTokenEvent> value)
    {
        var groups = value.GroupBy(e => e.IpAddress);

        foreach (var group in groups)
        {
            var count = group.Count();
            var isAttacker = count >= 10;
            var attacker = isAttacker ? " - ATTACKER!" : "";
            Console.WriteLine("{0}: {1}{2}", group.Key, count, attacker);
        }
        Console.WriteLine("--------------");
    }

    public void OnError(Exception error) { }

    public void OnCompleted() { }
}

Resultat der Konsolenanwendung