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.