Kicking Ass with Weaklayer

Welcome to the Weaklayer "Kicking Ass" tutorial. This page provides an example of what to do with Weaklayer data: detect credential phishing!

Please Note: We are not going to detect when a user visits a known credential phishing site or a site that "looks" like a credential phishing site. We are not going to detect when a user clicks a link in a "suspicious looking" email. We are going to detect the actual moment of compromise when a user enters their credentials into a website that they shouldn't have.

This page skips setting up Weaklayer and jumps right to having some Weaklayer data that you can analyze. An example data set is provided here for you to follow along with.

Example Data

Here is an example data set generated from a local Weaklayer installation. You can generate your own dataset if you wish by following the Getting Started Tutorial. The data is a JSON array of Weaklayer events. The Weaklayer Gateway Reference Implementation gives you data in this format when you configure the filesystem output.

[
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","isNewInstall":true,"label":"Mitch-Desktop","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079353864057,"type":"Install","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0"},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","isTopLevelWindow":true,"sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079358335677,"topLevelWindowReference":1602079358335677,"type":"Window"},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","hash":"","hostname":"accounts.google.com","path":"/ServiceLogin","protocol":"https","search":"?passive=1209600\u0026continue=https://docs.google.com/\u0026followup=https://docs.google.com/\u0026emr=1","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079358339816,"type":"WindowLocation","windowReference":1602079358335677},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","isTopLevelWindow":false,"sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079358911728,"topLevelWindowReference":1602079358335677,"type":"Window"},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","hash":"","hostname":"accounts.youtube.com","path":"/accounts/CheckConnection","protocol":"https","search":"?pmpo=https%3A%2F%2Faccounts.google.com\u0026v=210519688\u0026timestamp=1602079358796","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079358912248,"type":"WindowLocation","windowReference":1602079358911728},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","hash":"","hostname":"accounts.google.com","path":"/signin/v2/identifier","protocol":"https","search":"?passive=1209600\u0026continue=https%3A%2F%2Fdocs.google.com%2F\u0026followup=https%3A%2F%2Fdocs.google.com%2F\u0026emr=1\u0026flowName=GlifWebSignIn\u0026flowEntry=ServiceLogin","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079358921616,"type":"WindowLocation","windowReference":1602079358335677},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","hash":"","hostname":"accounts.google.com","path":"/signin/v2/challenge/pwd","protocol":"https","search":"?passive=1209600\u0026continue=https%3A%2F%2Fdocs.google.com%2F\u0026followup=https%3A%2F%2Fdocs.google.com%2F\u0026emr=1\u0026flowName=GlifWebSignIn\u0026flowEntry=ServiceLogin\u0026cid=1\u0026navigationDirection=forward\u0026TL=AM3QAYas1DLpsICIFgkTgr-_hykqpfQXsgthVYtuoREsej42X9l7JX3DnhJU98R8","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079361681063,"type":"WindowLocation","windowReference":1602079358335677},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"email","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"uSZGd7gaqv8ZsIp+eTJR1gvzNrpj7OSoITBDt8ml4ag=","textLength":1,"time":1602079360013633,"type":"TextInput","windowLocationReference":1602079358921616},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"email","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"LvA7Ar/YL5lcgg5lHf+u+VHakuvl7Bl5QppeoTCa5CQ=","textLength":5,"time":1602079360747183,"type":"TextInput","windowLocationReference":1602079358921616},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","isTopLevelWindow":true,"sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079367377816,"topLevelWindowReference":1602079367377816,"type":"Window"},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","hash":"","hostname":"kjshfdoausydgabjkfas.example","path":"/","protocol":"https","search":"","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","time":1602079367382672,"type":"WindowLocation","windowReference":1602079367377816},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"password","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"3M8E30Wow58qQfbTkfqd8FIRumWCTrRELkiCZHsAjVg=","textLength":1,"time":1602079362834438,"type":"TextInput","windowLocationReference":1602079361681063},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"password","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=","textLength":7,"time":1602079364802568,"type":"TextInput","windowLocationReference":1602079361681063},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"composite","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"q7jJ3syPrfj6GvvNxUNJIUrvNOStbdlyLn44cJjKa84=","textLength":12,"time":1602079364801467,"type":"TextInput","windowLocationReference":1602079361681063},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"composite","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"3M8E30Wow58qQfbTkfqd8FIRumWCTrRELkiCZHsAjVg=","textLength":1,"time":1602079369698579,"type":"TextInput","windowLocationReference":1602079367382672},
{"group":"68886d61-572b-41a5-8edd-93a564fb5ba3","inputType":"composite","sensor":"3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b","textHash":"rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=","textLength":7,"time":1602079372705473,"type":"TextInput","windowLocationReference":1602079367382672}
]

Copy this data into a local file called weaklayer-example-data.json if you want to follow along with the analysis.

Finding Passwords

This brings us to the Weaklayer TextInput event. Text input events are generated by the Weaklayer Sensor when the user inputs text into a web page. Here is an example from the above data.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "inputType": "email",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "textHash": "LvA7Ar/YL5lcgg5lHf+u+VHakuvl7Bl5QppeoTCa5CQ=",
  "textLength": 5,
  "time": 1602079360747183,
  "type": "TextInput",
  "windowLocationReference": 1602079358921616
}

This event tells us that a piece of text with 5 characters was entered into an email input field. The event also gives us a keyed hash for this piece of text. The text hashes are base64-encoded HMAC-SHA256 hashes, where each sensor generates its own hash key. This technique adds security while still allowing for hashes from the same sensor to be comparable. You can think of this like salted hashes for password storage. However, the security model is different since the salt (HMAC key) never leaves the sensor.

These events are also generated for password fields. Password text input events will give us keyed hashes that correspond to user credentials. We'll use these hashes to find credential phishing evidence.

Let's start by getting these password text input events with the jq program.

jq '.[]  | select(.type == "TextInput" and .inputType == "password")' weaklayer-example-data.json

Here is the output of this command.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "inputType": "password",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "textHash": "3M8E30Wow58qQfbTkfqd8FIRumWCTrRELkiCZHsAjVg=",
  "textLength": 1,
  "time": 1602079362834438,
  "type": "TextInput",
  "windowLocationReference": 1602079361681063
}
{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "inputType": "password",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "textHash": "rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=",
  "textLength": 7,
  "time": 1602079364802568,
  "type": "TextInput",
  "windowLocationReference": 1602079361681063
}

This gives us a list of password hashes to work with.

Finding Relevant Passwords

We are most interested in credential phishing of accounts belonging to our hypothetical organization. Therefore we can filter the previous list a little.

The first technique is to apply our organization's password policy. Let's say our policy says passwords must be at least 6 characters. This lets us discard any text input events with length less than 6. We can accomplish this by editing the above jq command.

jq '.[] | select(.type == "TextInput" and .inputType == "password" and .textLength >= 6)' weaklayer-example-data.json

Now we are left with this single event. However, performing analysis on a real dataset would yield many events from many different sensors.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "inputType": "password",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "textHash": "rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=",
  "textLength": 7,
  "time": 1602079364802568,
  "type": "TextInput",
  "windowLocationReference": 1602079361681063
}

We still need to know if this password input is relevant to our organization though. We can do this by following the windowLocationReference link. It points to the time value of a WindowLocation event that will give us a URL. The jq query to find that event uses the windowLocationReference value and the sensor value.

jq '.[] | select(.type == "WindowLocation" and .time == 1602079361681063 and .sensor == "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b")' weaklayer-example-data.json

We expect only a single event from this query since Weaklayer events are uniquely identified by their sensor and time field values. This is the output.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "hash": "",
  "hostname": "accounts.google.com",
  "path": "/signin/v2/challenge/pwd",
  "protocol": "https",
  "search": "?passive=1209600&continue=https%3A%2F%2Fdocs.google.com%2F&followup=https%3A%2F%2Fdocs.google.com%2F&emr=1&flowName=GlifWebSignIn&flowEntry=ServiceLogin&cid=1&navigationDirection=forward&TL=AM3QAYas1DLpsICIFgkTgr-_hykqpfQXsgthVYtuoREsej42X9l7JX3DnhJU98R8",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "time": 1602079361681063,
  "type": "WindowLocation",
  "windowReference": 1602079358335677
}

We can see now that the password was entered into https://accounts.google.com/signin/v2/challenge/pwd ... and the URL search field even indicates the login is for docs.google.com. Our hypothetical organization uses GSuite, so this password entry very likely corresponds to credentials for our organization.

The important piece of data we have so far is that the text hash rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw= corresponds to a password for our organization.

Finding Credential Phishing

Now that we have a hash, we can look for its usage. Here is a jq command to do that. Note that we don't filter on the inputType field. There is no guarantee that malicious websites will use a specific type of text field (or use a text field at all). Also note that using the sensor value in the query isn't nessecary because hashes are uniquely keyed per sensor. It probably won't hurt if you want to query with the sensor GUID as well though.

jq '.[] | select(.type == "TextInput" and .textHash == "rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=")' weaklayer-example-data.json

Here is the output.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "inputType": "password",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "textHash": "rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=",
  "textLength": 7,
  "time": 1602079364802568,
  "type": "TextInput",
  "windowLocationReference": 1602079361681063
}
{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "inputType": "composite",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "textHash": "rloAwoeu+VQ+Gahn81GKnvZPp+Q4FQeQQCJt+mJ6SBw=",
  "textLength": 7,
  "time": 1602079372705473,
  "type": "TextInput",
  "windowLocationReference": 1602079367382672
}

We can tell by the time value that the first event is the password input event that we found in the previous section. However, the second event is a composite text input that we haven't seen before. This is our possible evidence of credential phishing. Note that an inputType value of composite indicates the sensor created the hashed text from keystrokes and other user inputs.

We can follow the windowLocationReference link again to find the URL for the page this text was entered into.

jq '.[] | select(.type == "WindowLocation" and .time == 1602079367382672 and .sensor == "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b")' weaklayer-example-data.json

Here is what we find.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "hash": "",
  "hostname": "kjshfdoausydgabjkfas.example",
  "path": "/",
  "protocol": "https",
  "search": "",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "time": 1602079367382672,
  "type": "WindowLocation",
  "windowReference": 1602079367377816
}

We see from this event that the text was entered into https://kjshfdoausydgabjkfas.example/. This site is definitely not affiliated with Google. Note: this particular domain is an example domain made up for this tutorial.

In summary, we have a piece of text that is very likely a user's GSuite password that was entered into a non-Google site. You may wish to perform some further investigation on this site to see if it is actually a credential phishing site. However, this will be enough evidence for many organizations to perform some remediation - locking the user's account for example.

Remediation

Now we want to lock the user's account. This means we have to tie the above data back to a specific user. Text input events won't help us here since the text data is hashed. Instead we'll look at the info that we have on the sensor.

We'll look for the Install event for this sensor.

jq '.[] | select(.type == "Install" and .sensor == "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b")' weaklayer-example-data.json

Here is the output.

{
  "group": "68886d61-572b-41a5-8edd-93a564fb5ba3",
  "isNewInstall": true,
  "label": "Mitch-Desktop",
  "sensor": "3098f175-38d6-4bb8-9ab0-fc8e1aa27c0b",
  "time": 1602079353864057,
  "type": "Install",
  "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0"
}

We are interested in the label field. It is a value that you set when the Weaklayer Sensor is installed. See the Sensor Configuration page for more information. Normally, you want to set the label value to something that will be useful here (e.g. some combination of hostname and user account). In this example, label is set to Mitch-Desktop. Luckily, our hypothetical organization has one employee named Mitch.

Therefore, based on this evidence, we should lock Mitch's account and contact him with instructions on how to reset his compromised password.

What's Next

Hopefully this page has shown you a way to get value from Weaklayer today. The Getting Started Tutorial will show you how to set up a local Weaklayer Sensor and Weaklayer Gateway. Then you can start exploring your own data set.

Please submit an issue on Github or contact us if you have any questions. Starring the Weaklayer Sensor repository is a big help if you like what you see.