Here's how it all rolls together:
Someone fails a kerberos login, an Event Id 4771 gets logged on the DC. I've setup a task to run when this Event ID is triggered. Here's a solid page on how to do that if you're reading this and saying "wtf is this guy talking about" Tasks based on Event Log Activity
At first I just had it setup to send me an email to let me know that a failed login had occurred. I quickly found out that there's a LOT of failed logins per day on my domain, ~100. We have a lot of users it makes sense now that I think about it and see how often it happens. Since in their infinite wisdom Microsoft didn't include an option to attach the event information to the email these emails were pretty useless.
So now we need to find a way to get the information from the event log into an email.
Wevtutil is a nifty little windows utility (included natively) that can query the event log and output the event info to a file. That's handy. I'll be honest the syntax is a but confusing to me but I got it to do what I needed.
wevtutil qe security /rd:true /f:text /c:1 /q:"*[System/EventID=4771]" > file.txt
"qe security" is to query the security log
/rd:true is reverse direction, aka read from the newest event to the oldest
/f:text is the output format, there are other options
/c:1 find the first 1 event that matches
/q: xpath query string. I'm not going to go into explaining xpath queries, google is your friend. This one says find all (*) from System EventID is equal to 4771 (failed login event ID). You can get very specific if you'd like with the query.
Now, you can create two tasks to trigger in order when there's an event, so you could set things up like so:
1. Failed login occurs, event is created in the Event Log.
2. Task 1 for that event is triggered.
3. Use Wevtutil to output the event information to a text file
4. Task 2 for the event is triggered
5. Email is sent and attached is the output from Wevutil
I don't like that setup, seems overly complex in my mind no need for 2 tasks when we can have just 1 task and 1 script. I put a copy of my favorite command line emailer BLAT in the folder with my script and used that at the end of the script to send out the email.
-=Script=-
@echo off
wevtutil qe security /rd:true /f:text /c:1 /q:"*[System/EventID=4771]" > X:\path\file.txt
X:\Path\blat.exe X:\path\wevtutiloutputfile.txt -server mail.domain.whatever -subject "Failed Login" -to you@domain.whatever -mailfrom FailedLogin@domain.whatever
del /f /q X:\path\file.txt
*you need to make sure you use the full path for everything OR set your task to start in the specified directory on the task options when you create the task. Task. Had to get one last one in there.
Okay so now I get an email that a login has failed and I get the full info of the event log for that particular event. We're getting closer, however it's not as "human friendly" as I'd like it to be. Specifically it doesn't include the users full name, just the account login name, and it only gives the code for the failed login (0x18 for example) and doesn't tell you that 0x18 means bad username or password. There are many reasons that a login can fail and I for one don't want to try and remember what each error code means.
So we need to massage the text into an output that has more useful information and is easily readable:
-=Script=-
@echo off
wevtutil qe security /rd:true /f:text /c:1 /q:"*[System/EventID=4771]" > X:\Path\failed_login.txt
type X:\Path\failed_login.txt | find /i "Account Name" > X:\Path\fail_alert.txt
for /f "tokens=3" %%a in ('type X:\Path\failed_login.txt ^| find /i "Account Name"') do set accname=%%a
for /f "tokens=2,* delims= " %%a in ('net user %accname% /domain ^| find /i "Full Name"') do echo Full Name: %%b >> X:\Path\fail_alert.txt
type X:\Path\failed_login.txt | find /i "Client Address" >> X:\Path\fail_alert.txt
type X:\Path\failed_login.txt | find "Failure Code:" >> X:\Path\fail_alert.txt
for /f "tokens=2 Delims=x" %%a in ('type X:\Path\failed_login.txt ^| find "Failure Code:"') do set fail=%%a
if %fail% == 17 echo Reason Failed: Users Password has Expired >> X:\Path\fail_alert.txt
if %fail% == 12 echo Reason Failed: Account Disabled / Account Expired >> X:\Path\fail_alert.txt
if %fail% == 18 echo Reason Failed: Bad Username or Password >> X:\Path\fail_alert.txt
if %fail% == 25 echo Reason Failed: Workstation Clock too far out of Sync with DC >> X:\Path\fail_alert.txt
for /f "tokens=2 delims=:" %%a in ('type X:\Path\failed_login.txt ^| find "Computer:"') do set frm=%%a
echo Logged From: %frm% >> X:\Path\fail_alert.txt
for /f "tokens=1,* delims=:" %%a in ('type X:\Path\failed_login.txt ^| find "Date:"') do set tme=%%b
echo Logged At: %tme% >> X:\Path\fail_alert.txt
X:\Path\blat.exe X:\Path\fail_alert.txt -server mailserver -subject "Failed Login" -to you@yourdomain.ext -mailfrom FailedLogin@yourdomain.ext
del /f /q X:\Path\failed_login.txt
del /f /q X:\Path\fail_alert.txt
So what's going on here is this:
We use wevtutil to query the security log and find the last 1 event with the ID of 4771 (failed login) and output the text of the event to a file (failed_login.txt)
Then we search the file for the line with "Account Name" this gives us the user account name, output that to what will end up being the email body.
Then we do the same search again, except this time we're going to set just the actual account name as a varaible not the whole line.
Then we're going to query the domain for that account name and grab the Full Name (their actual name) value. Output that to the email body file.
Then we search the event output again for the client address (computer the login attempt came from) and the reason it failed (failure code). Output those to the email body file.
Then we do the search for the failure code again and set it as a variable.
Then we run through some if statements to check if the failure code is one of the common failure codes. Output that to the email body file.
*I didn't do a full list of all possible failure codes because there are only a few common ones that happen, also if the failure code isn't one of the common ones something unusual is probably happening and I'm going to have to look it up anyways. Kind of a way to alert me that I need to pay attention through lack of information. You can find a list of failure codes here: Failure Codes
Then we do a search through the wevtutil output again and find the computer that's reporting the failure. Set that as a variable and output it to the email body file, with some formatting.
Then we do the same thing with the time / date. Output it to the email body file.
Then we tell BLAT to do it's thing and send us an email.
Then we clean up the files and wait to be triggered again.
We end up getting an email with a body similar to this:
Account
Name: smahoney
Full Name: Mahoney, Sausage
Client
Address: ::ffff:192.168.0.56
Failure
Code: 0x18
Reason Failed: Bad Username or Password
Logged From: dc1.fakedomain.local
Logged At: 2013-04-26T08:52:35.445
That's about as good as I'm looking for.
Now this setup isn't perfect, if you have multiple failed logins rapidly things are going to get messy as you're using static output names for the files. It takes about 2 seconds for the script to run so as long as you don't have multiple failed attempts from different users within 2 seconds you're golden. You could tack on a %random% variable to the file name to eliminate that problem. I haven't tested it yet but I'm sure it'd work and the odds that you're having enough failed logins to hit the same %random% variable output while the file exists is slim, and if that is happening, you've got much larger problems.
There's lots and lots of interesting Event Id's that you can use this for, like when an account is created, deleted, or reaches the locked out state. Anything that registers an event in any of the event logs can be used to trigger this setup.
Now, you can also use this as post exploit foothold. For example, find a service user account, one that no one has any reason to ever actually log in with, setup a task that when that particular account fails a login create a user, add them to remote desktop users group, vpn users, and domain admins group, then intentionally fail a login via the OWA page, their ancient PPTP VPN, a Sharepoint page, that RDP port they didn't block, anywhere that you can try an AD login onto the domain to trigger it. Thanks to Mark Baggett for pointing that one out here: Wipe the Drive Part 4
Or maybe you're just a vengeful IT type and want all hell to break lose when you're fired and tie an account disable / deleted task to your user account.
I'm looking currently for a way to get user generated data into the event log somehow, if we can do that from some external source, and we had previous access to the server and created a task and some scripts to parse the event log info, we have the makings of a really slick backdoor.