
If an attacker managed to avoid detection at the network level and reached Active Directory with a domain user account, then you’ve lost the first round. Now the intruder can deliver dozens of deadly attacks, and most of them are quite ‘silent’.
At this point, the second (and final!) round of your confrontation begins. The internal infrastructure and, potentially, the entire company’s business are at stake. However, if the domain withstands the simplest exploits that the hacker tries in the first minutes, and the intruder has no choice but to implement ‘noisier’ attacks, then the final battle can still be won.
You will learn to hear barely perceptible noises made by the attacker who has gained a foothold in Active Directory, identified your misconfigs, and started advancing to domain controllers.
Authentication
An attacker whose goal is to capture the heart of your internal infrastructure – Active Directory -inevitably has to deal with domain accounts. In the course of advancement, the hacker can make unsuccessful authentication attempts, enter incorrect or old passwords, or try to brute-force a password.
On the domain controller, you can monitor events 4776 (
, 4768
, or 4769
in a centralized way, but this requires domain admin privileges. By contrast, authentication attempts made by all domain users can be monitored with ordinary user rights (as you remember, all the proposed defense techniques don’t require exclusive rights and can be performed by any employee).
Using LDAP, you can track changes in the lastLogon/
attribute and see the dynamics of successful authentications; changes in the badPasswordTime
and badPwdCount
attributes reflect the dynamics of unsuccessful authentications; while lockoutTime
shows the dynamics of blockings. All you have to do is request in a loop objects whose above-listed attributes have changed within a certain period of time.

The automation below performs all the required operations: the script writes in real time which user has successfully authenticated, which user failed and how many times, and who was locked:
defence/ad/auth.py
from ldap3 import Server, Connection, SUBTREE, ALLfrom time import sleepfrom datetime import datetimefrom getpass import getpassfrom os import systemfrom sys import argvfrom colorama import Foredc = argv[1]userdom = argv[2] # "user@company.org"USERS = { }MAX_LOCKS = 50MAX_FAILS = 100server = Server(dc, get_info=ALL)Connection(server, auto_bind=True)root = server.info.naming_contexts[0]server_time = server.info.other.get('currentTime')[0]print("{root} {server_time}".format(root=root, server_time=server_time))conn = Connection(server, user=userdom, password=argv[3] if len(argv) > 3 else getpass("password: "))conn.bind()alerts = []def alert(user, action): if user in alerts: return print("[!] Auth event detected: %s %s" % (user,action)) #system("telegram '{message}' &".format(message="Auth event detected: "+user+" "+action)) #system("email admin@company.org '{message}' &".format(message="Auth event detected: "+user+" "+action)) #system("sms PHONENUMBER '{message}' &".format(message="Auth event detected: "+user+" "+action)) system("zenity --warning --title='Auth event detected' --text='%s %s' &" % (user,action)) #system("echo 'Auth event detected' | festival --tts --language english") alerts.append(user)failures_time = {}success_time = {}fails = set()locks = set()timestamp = (int(datetime.strptime(server_time, "%Y%m%d%H%M%S.0Z").timestamp() if server_time else datetime.utcnow().timestamp()) + 11644473600) * 10000000while True: conn.search(root, '(&(objectCategory=person)(objectClass=user)(|(badPasswordTime>={timestamp})(lastLogon>={timestamp})))'.format(timestamp=timestamp), SUBTREE, attributes=["sAMAccountName", "badPasswordTime", "lastLogon", "badPwdCount", "lockoutTime"]) lasts = [timestamp] for result in conn.entries: dn = result.entry_dn if result['sAMAccountName']: user = result['sAMAccountName'].value if user.lower() in ('incident','sp_farm'): continue auth_failure_count = "" if result['badPwdCount']: auth_failure_count = int(result['badPwdCount'].value) if result['badPasswordTime']: if user in failures_time and failures_time[user] < result['badPasswordTime'].value.timestamp(): print('[{now}]{red} "{user}" auth failure ({auth_failure_count}){reset}'.format(now=datetime.now().strftime("%d.%m.%Y %H:%M:%S"), badPasswordTime=result["badPasswordTime"].value.strftime("%d.%m.%Y %H:%M:%S"), red=Fore.RED, user=user, auth_failure_count=auth_failure_count, reset=Fore.RESET)) if user.lower() in USERS['fail']: alert(user, 'failure') lasts.append((result['badPasswordTime'].value.timestamp() + 11644473600) * 10000000) if result['lockoutTime'].value and result['lockoutTime'].value.timestamp() == result['badPasswordTime'].value.timestamp(): print('[{now}]{red} "{user}" locked{reset}'.format(now=datetime.now().strftime("%d.%m.%Y %H:%M:%S"), red=Fore.LIGHTRED_EX, user=user, reset=Fore.RESET)) if user.lower() in USERS['lock']: alert(user, 'locked') locks.add(user) fails.add(user) failures_time[user] = result['badPasswordTime'].value.timestamp() if result['lastLogon']: if user in success_time and success_time[user] < result['lastLogon'].value.timestamp(): print('[{now}]{green} "{user}" auth success{reset}'.format(now=datetime.now().strftime("%d.%m.%Y %H:%M:%S"), lastLogon=result["lastLogon"].value.strftime("%d.%m.%Y %H:%M:%S"), green=Fore.GREEN, user=user, reset=Fore.RESET)) lasts.append((result['lastLogon'].value.timestamp() + 11644473600) * 10000000) if user.lower() in USERS['auth']: alert(user, 'auth') if user in locks: locks.remove(user) if user in fails: fails.remove(user) success_time[user] = result['lastLogon'].value.timestamp() if len(locks) > MAX_LOCKS: alert("mass locks users", str(len(locks))) if len(fails) > MAX_FAILS: alert("mass fails users", str(len(fails))) timestamp = int(max(lasts) + 1) sleep(1)
Under normal circumstances, it’s no surprise that users sometimes enter their passwords with typos.

In the above screenshot, the first failed authentication is immediately followed by a successful one: the user simply made a typo when entering their password for the first time, but then entered it correctly. However, the second failed authentication looks suspicious because the correct password was never entered.
The badPwdCount
attribute shows the number of failed attempts so that you can conclude that somebody is guessing the password for this account.

In the above screenshot, you can see that somebody is trying to brute-force standard account names using a wordlist. Apparently, this is an echo of an attack on the external network perimeter. Services that use domain accounts for authentication can often be located there.
But in the next example, the situation is different: the usernames are definitely not taken from a wordlist.

If the attacker could find out these usernames only after penetrating into the network and connecting to Active Directory, then you can conclude with confidence: you are dealing with an internal intruder. Direct brute-forcing of domain accounts is a very rare phenomenon and, most likely, it’s a consequence of careless attacks.
However, an attacker can only try weakest passwords against a wide range of usernames without being blocked. If your company has a large number of accounts, then the attacker has a chance of success.

Such an attack can be easily detected: from the outside, it looks like a spontaneous burst of failed authentication attempts made by multiple users.

Similar activity can be detected if an attacker finds a valid password and delivers a password spraying attack in the hope that the password would work somewhere else.
In large companies, the flow of authentication events is huge, and it’s quite difficult to monitor it manually. Therefore, you can set up automatic notifications for certain users and certain events.
For instance, you can create a fictitious user so that no one is aware of its existence. Any authentication attempt performed by such a user can be considered an anomaly. Usernames obviously taken from a wordlist (e.g. guest
, security
, audit
, testuser
, test1
, etc.) that aren’t used by anyone should also be considered an anomaly if they make failed authentication attempts. Finally, under normal circumstances, such standard usernames as administrator
and the above-listed ones should never be blocked; otherwise, this also indicates an attack. In auth.
, this logic is described as follows:
defence/ad/auth.py...USERS = { # notifications 'auth': ['honeypot_user'], 'fail': ['guest', 'security', 'audit', 'testuser', 'test1'], 'lock': ['administrator', 'guest', 'security', 'audit', 'testuser', 'test1']}...
Since the script collects the full dynamics of authentication events, it would be great to analyze it somehow. Using standard Python math capabilities, you can easily construct a chart showing trends in successful and failed authentications and blockings:
defence/ad/auth-anal.py
from datetime import datetimeimport matplotlib.pyplot as pltHOURS = 24auth_success = {}auth_fail = {}auth_lock = {}while True: try: line = input() except: break try: date,time,user,*result = line.strip().split() except: continue date = date.split("[")[1] time = time.split("]")[0] hour = time.split(":")[0] result = " ".join(result) try: datetime.strptime(date, '%d.%m.%Y') except: continue if result.find("success") != -1: try: auth_success[date+"-"+hour] += 1 except: auth_success[date+"-"+hour] = 1 elif result.find("fail") != -1: try: auth_fail[date+"-"+hour] += 1 except: auth_fail[date+"-"+hour] = 1 elif result.find("lock") != -1: try: auth_lock[date+"-"+hour] += 1 except: auth_lock[date+"-"+hour] = 1plt.plot(sorted(auth_success, key=lambda k:datetime.strptime(k, '%d.%m.%Y-%H').timestamp()), list(map(lambda d: auth_success[d], sorted(auth_success, key=lambda k:datetime.strptime(k, '%d.%m.%Y-%H').timestamp()))), label="success")plt.plot(sorted(auth_fail, key=lambda k:datetime.strptime(k, '%d.%m.%Y-%H').timestamp()), list(map(lambda d: auth_fail[d], sorted(auth_fail, key=lambda k:datetime.strptime(k, '%d.%m.%Y-%H').timestamp()))), label="fail")plt.plot(sorted(auth_lock, key=lambda k:datetime.strptime(k, '%d.%m.%Y-%H').timestamp()), list(map(lambda d: auth_lock[d], sorted(auth_lock, key=lambda k:datetime.strptime(k, '%d.%m.%Y-%H').timestamp()))), label="lock")ax = plt.gca(); ax.set_xticks(ax.get_xticks()[::HOURS])plt.legend()plt.show()
This analysis is as simple as ABC, but the overall picture immediately becomes clear: the company actively operates during business hours; while brute-forcing attacks occur at night time.

However, authentication events are only a small part of what’s going on in Active Directory. Some events and processes are often not monitored even by Security Operations Centers.
Changes in Active Directory objects
Many attacks on the Active Directory infrastructure leave specific traces: respective attributes of objects are created or changed. Almost all such modifications indirectly affect the whenChanged
attribute that reflects changes in a given object.

Importantly, the whenChanged
attribute is updated even if object’s rights have changed, which makes it possible to detect nearly untraceable ACL attacks. To track the entire dynamics in Active Directory, all you have to do is make a preliminary snapshot of all attributes for all objects, analyze their ACLs, and compare differences in changed objects:
defence/ad/changes.py
from ldap3.protocol.microsoft import security_descriptor_controlfrom ldap3 import Server, Connection, SUBTREE, BASE, ALL, ALL_ATTRIBUTESimport picklefrom time import sleepfrom datetime import datetimefrom getpass import getpassfrom os import systemfrom sys import argvfrom re import matchfrom colorama import Forefrom winacl.dtyp.security_descriptor import SECURITY_DESCRIPTORfrom winacl.dtyp.sid import SIDfrom winacl.dtyp.ace import ADS_ACCESS_MASKdc = argv[1]ATTACKS = { # notifications "SPN attack": {"attr": "^serviceprincipalname$", "dn": ".*"}, "RBCD attack" : {"attr": "^msds-allowedtoactonbehalfofotheridentity$", "dn": ".*"}, "ShadowCredentials attack" : {"attr": "^msds-keycredentiallink$", "dn": ".*"}, "membership changed": {"attr": "^member$", "dn": ".*admin.*"}, "GPO attack": {"attr": "^gpcfilesyspath$", "dn": ".*"}, "user object abuse": {"attr": "^scriptpath$", "dn": ".*"}, "ACL attack": {"attr": ".*generic_all.*", "dn": ".*"}, "sAMAccountName spoofing": {"attr": "^samaccountname$", "dn": ".*"}, "dNSHostName spoofing": {"attr": "^dnshostname$", "dn": ".*"}, "ADCS attack templates ESC4": {"attr": "^(msPKI-Certificate-Name-Flag|msPKI-Enrollment-Flag|msPKI-RA-Signature)$", "dn": ".*CN=Certificate Templates,.*"}}server = Server(dc, get_info=ALL)Connection(server, auto_bind=True)server_time = server.info.other.get('currentTime')[0]if len(argv) < 4: print(server_time) print("\n".join(server.info.naming_contexts)) exit()else: root = argv[3]userdom = argv[2] # "user@company.org"conn = Connection(server, user=userdom, password=getpass("password: "))conn.bind()alerts = []def alert(dn, attr, value, message): if (dn,attr) in alerts: return print("[!] Danger changes detected: %s: %s=%s (%s)" % (dn, attr, value, message)) #system("telegram '{message}'".format(message="Danger changes detected %s: %s=%s (%s)" % (dn, attr, value, message))) system("zenity --warning --title='Danger changes detected' --text='%s: %s=%s (%s)' &" % (dn, attr, value, message)) #system("echo 'Danger changes detected' | festival --tts --language english") alerts.append((dn,attr))cache_sid = {}def resolve_sid(sid): global cache_sid if not sid in cache_sid: cache_sid[sid] = None for dn in objects: if objects[dn].get("objectSid") == [sid]: name = objects[dn]["sAMAccountName"] cache_sid[sid] = name break return cache_sid.get(sid)def parse_acl(nTSecurityDescriptor): acl = SECURITY_DESCRIPTOR.from_bytes(nTSecurityDescriptor) acl_canonical = {"owner": [acl.Owner.to_sddl() if acl.Owner else ""], "dacl":[]} for ace in acl.Dacl.aces if acl.Dacl else []: ace_canonical = {} ace_canonical["who"] = SID.wellknown_sid_lookup(ace.Sid.to_sddl()) or resolve_sid(ace.Sid.to_sddl()) or ace.Sid.to_sddl() ace_canonical["type"] = str(ace).split("\n")[0].strip() for line in str(ace).split("\n")[1:]: if line.strip(): field = line.split(":")[0].lower() value = line.split(":")[1].strip() ace_canonical[field] = value acl_canonical["dacl"].append(ace_canonical) return acl_canonicaldef snapshot_create(): global objects #results = conn.extend.standard.paged_search(search_base=root, search_filter='(objectClass=*)', search_scope=SUBTREE, attributes=ALL_ATTRIBUTES, paged_size=1000) # only attributes results = conn.extend.standard.paged_search(search_base=root, search_filter='(objectClass=*)', search_scope=SUBTREE, attributes=ALL_ATTRIBUTES, controls=security_descriptor_control(sdflags=0x05), paged_size=1000) # with ACL #conn.search(root, '(objectClass=*)', SUBTREE, attributes=ALL_ATTRIBUTES) # only attributes #conn.search(root, '(objectClass=*)', SUBTREE, attributes=ALL_ATTRIBUTES, controls=security_descriptor_control(sdflags=0x05)) # with ACL #conn.search(root, '(|(objectClass=pKICertificateTemplate)(objectClass=certificationAuthority))', SUBTREE, attributes=ALL_ATTRIBUTES, controls=security_descriptor_control(sdflags=0x05)) # with ACL #for result in conn.entries: for result in results: if result.get('type') == 'searchResRef': continue #dn = result.entry_dn #objects[dn] = result.entry_attributes_as_dict dn = result["dn"] objects[dn] = result["raw_attributes"] for dn in objects: # because of resolve_sid() if 'nTSecurityDescriptor' in objects[dn]: objects[dn]['nTSecurityDescriptor'] = parse_acl(objects[dn]['nTSecurityDescriptor'][0]) open("objects.dat", "wb").write(pickle.dumps([objects,cache_sid]))def snapshot_restore(): global objects, cache_sid try: objects, cache_sid = pickle.loads(open("objects.dat", "rb").read()) return True except: return Falsedef get_attrs(dn): #conn.search(dn, '(objectClass=*)', BASE, attributes=ALL_ATTRIBUTES) # only attributes #conn.search(dn, '(objectClass=*)', BASE, attributes=ALL_ATTRIBUTES, controls=security_descriptor_control(sdflags=0x05)) # with ACL results = conn.extend.standard.paged_search(search_base=dn, search_filter='(objectClass=*)', search_scope=BASE, attributes=ALL_ATTRIBUTES, controls=security_descriptor_control(sdflags=0x05), paged_size=1000) # with ACL result = next(results) #attrs = conn.entries[0].entry_attributes_as_dict attrs = result["raw_attributes"] if attrs.get('nTSecurityDescriptor'): attrs['nTSecurityDescriptor'] = parse_acl(attrs['nTSecurityDescriptor'][0]) return attrsdef print_diff(dn): if not dn in objects: return def diff(attrs_before, attrs_after): for attr in attrs_before: if not attr in attrs_after: print(f"{Fore.RED}delete %s: %s{Fore.RESET}" % (attr, str(attrs_before[attr]))) else: if type(attrs_before[attr]) == dict: diff(attrs_before[attr], attrs_after[attr]) else: for value in attrs_before[attr]: if not value in attrs_after[attr]: print(f"{Fore.RED}delete %s: %s{Fore.RESET}" % (attr, value)) for attr in attrs_after: if not attr in attrs_before: print(f"{Fore.GREEN}new %s: %s{Fore.RESET}" % (attr, str(attrs_after[attr]))) for attack in ATTACKS: if (match(ATTACKS[attack]["attr"].lower(), attr.lower()) or match(ATTACKS[attack]["attr"].lower(), str(attrs_after[attr]).lower())) and match(ATTACKS[attack]["dn"].lower(), dn.lower()): alert(dn, attr, attrs_after[attr].decode(), attack) else: if type(attrs_after[attr]) == dict: diff(attrs_before[attr], attrs_after[attr]) else: for value in attrs_after[attr]: if not value in attrs_before[attr]: print(f"{Fore.GREEN}added %s: %s{Fore.RESET}" % (attr, value)) for attack in ATTACKS: if (match(ATTACKS[attack]["attr"].lower(), attr.lower()) or match(ATTACKS[attack]["attr"].lower(), str(value).lower())) and match(ATTACKS[attack]["dn"].lower(), dn.lower()): alert(dn, attr, value.decode(), attack) attrs = get_attrs(dn) diff(objects[dn], attrs) objects[dn] = attrsobjects = {}snapshot_restore() or snapshot_create()print("[*] %d objects" % len(objects))now = datetime.strptime(server_time, '%Y%m%d%H%M%S.0Z').timestamp() or datetime.utcnow().timestamp()first_time = Truewhile True: conn.search(root, f'(whenChanged>={datetime.utcfromtimestamp(now).strftime("%Y%m%d%H%M%S.0Z")})', SUBTREE, attributes=["distinguishedName", "whenChanged", "whenCreated"]) lasts = [now] for result in conn.entries: dn = result.entry_dn changed = result['whenChanged'].value created = result['whenCreated'].value time = changed.strftime("%d.%m.%Y %H:%M:%S") if changed == created: if not first_time: print(f'[{time}] "{dn}" created') objects[dn] = get_attrs(dn) lasts.append(created.timestamp()) else: if not first_time: print(f'[{time}] "{dn}" changed') print_diff(dn) lasts.append(changed.timestamp()) now = max(lasts) + 1 sleep(1) first_time = False
This script consists of slightly more than a hundred lines of code. However, you can run it under any (even unprivileged) domain account, and it will monitor in real time what objects were created, deleted, or modified and show what attributes have changed in them, including ACL analysis to present the results in a canonical readable form.
A single script enabling you to see almost all attacks becomes a universal Active Directory monitoring tool.
Attacks targeting users
As attacks progress, the hacker identifies various access rights misconfigs in the Active Directory infrastructure, as well as relay attacks enabling the intruder to perform actions on behalf of another account. Ultimately, such attacks result in numerous harmful actions.
If an attacker has extended rights to a user account, they can change its password – and you’ll see this immediately thanks to the pwdLastSet
attribute.

Needless to say, when you monitor password change events, your top priorities are service, admin, and other important accounts.
If the attacker has write access to a user object in Active Directory, they can compromise this account more accurately by adding an arbitrary LogonScript.

And you can track such activity.

Another way to attack a user object is as follows: the attacker adds the servicePrincipalName
attribute to the object and delivers a targeted kerberoasting attack to capture that user’s password hash.

In the course of this attack, the script used by the attacker quickly creates an SPN attribute, requests a TGS Kerberos ticket (containing user password hash), and deletes SPN. Accordingly, if you check objects for changes frequently enough, you’ll see the characteristic attributes.

The targetedKerberoast.
script creates and deletes SPN very quickly so that it cannot be detected. However, an attacker can change SPN manually, and the changes.
script will detect such a change.
Attacks on computers
If an attacker has write access to the computer account object, they can use RBCD or Shadow Credentials technique to gain access to the victim’s PC.

This can be detected by the appearance of a specific attribute: msDS-AllowedToActOnBehalfOfOtherIdentity
/msDs-KeyCredentialLink
.

Under normal conditions, such attributes as Resource
and Shadow
are rarely used in regular domains. Therefore, the appearance of these attributes deserves your close attention.
Attacks on GPO
Finally, attention should be paid to Group Policy Objects. If an attacker compromises such an object, the impact can be catastrophic, especially if this policy applies to many hosts or users.

The most valuable attribute in a group policy is gPCFileSysPath
: it points to the folder where an executable script or registry branch can be stored. In any case, a redirection attempt can be detected based on changes in the respective attribute.

Important: an attacker can also compromise a group policy object on the SYSVOL network drive, and you won’t see this.
Attacks on groups
If an attacker manages to gain access to a specific group, they can add an account to that group.

To add an object to a group, the member
attribute is used, an you’ll immediately see its appearance.

Changes in membership of domain groups frequently occur in actively used domains. Accordingly, special attention should be paid to critical groups (e.g. Domain Admins, Enterprise Admins, Account Operators, Server Operators, Backup Operators, Print Operators, DnsAdmins, Organization Management, etc.). In most cases, the final stage of internal network attacks involves domain compromise.

A domain admin group compromise is a pretty ‘noisy’ event. A cautious hacker would rather abstain from it. But that doesn’t mean that you shouldn’t monitor such activity.
ACL attacks
ACL attacks are even more difficult to detect. Almost all above-described examples can be consequences of misconfigured access rights (ACL). Such misconfigs can be easily detected with BloodHound, and are often used as invisible paths leading a simple domain user to high-value targets and the domain admin. But are ACL modifications really undetectable? In fact, even slightest changes in rights result in implicit modifications of the whenChanged
attribute, which means that the changes.
script can be used to detect them.
Changes in ownership
If an internal attacker has the required rights (GENERIC_ALL,
), they can change the owner of an object.

As soon as this happens, object’s nTSecurityDescriptor
attribute (that stores all information about rights to that object) is changed. Information contained in it is presented in binary form, but the changes.
script can parse its structure to present the data in a canonical readable form. And you can immediately see suspicious changes.

Changes in ownership aren’t frequent events, and you should pay attention to every such case. But the progress of a real attack rarely stops at this point, and, most likely, something else should happen.
Changes in permissions
If an internal attacker discovers that their permissions make it possible to change permissions (WRITE_DACL
) in some object, the hacker can assign the arbitrary code execution (ACE) right to that object. Most often, this involves the delegation of full permissions (GENERIC_ALL
) to the object.

In the screenshot below, the internal attacker adds an ACE with the GENERIC_ALL
(full access) mask.

In real infrastructures, the path from a user to the domain admin can be very thorny, and I recommend paying attention to changes in ACL of any object if GENERIC_ALL
and WRITE_DACL
are involved since these access rights have the strongest impact (each in its own situation).
Attacks on ADCS
ADCS security is a separate topic: as many as 15 privilege escalation vectors involve Active Directory Certificate Services. Monitoring of ADCS events is a headache. As a result, many companies simply disable ADCS to be on the safe side.
But if the ADCS configuration is stored in LDAP, then you can monitor changes in it using the same changes.
script. ESC4 is an example of ADCS abuse: an object associated with a certificate template in Active Directory is modified. If a hacker encounters a template with incorrect access rights, they can modify it to make it vulnerable to ESC1.

At the Active Directory level, this is manifested in the addition of new attributes to the attacked object.

And since something has changed in the object, you can detect such an attack.

Generally speaking, this namespace (Configuration) isn’t ‘inhabited’ by users, and nothing changes there by itself. Accordingly, any change in it is a good reason to raise a red flag.
Conclusions
Now you’re aware of sensitive aspects that can be monitored with regular user rights. All you have to do is make special LDAP requests and track changes in certain attributes of Active Directory objects. Just two simple scripts enable you to detect traces of many popular attacks.
An attack delivered by an internal intruder who has reached Active Directory can develop very quickly and require swift reaction. A modern SOC has a fundamental problem: the wide range of events it monitors can significantly slow down analysis and decision-making. But if you know in advance what to pay attention to (i.e. aspects that almost never give false positives), then you can react much faster.
On average, it takes ten steps for a pentester to completely compromise the internal infrastructure. This means that (in theory!) you can detect the attacker ten times and have time to react.
But what if the hacker managed to make it out to their target? In such a situation, you can still do something to make bastard’s life miserable. If an attacker penetrates into your computer, most probably, they are only interested in your password. And you, as a user, can have any passwords, even those containing wildcards and dangerous commands.

If an attacker carelessly inserts a password phrase into the command line, the command ‘hidden’ there would be executed. For example, if you want to stop a hacker, you can try to implement a command that deletes all files on the disk.
In other words, you can implement RCE on the hacker’s host.

A survey conducted among hackers, pentesters, and IT professionals showed that 21% or respondents had occasionally entered incorrect passwords in the command line; as a result, their Kali systems were killed during password reuse attacks. A simple warning was able to stop one hacker out of five. There is no guarantee whether such a trick would work in your case or not, but it definitely won’t make your password weaker. So, even if you have lost both rounds, you still have a chance to deliver a fatality to the hacker.
Good luck!

2022.01.12 — Post-quantum VPN. Understanding quantum computers and installing OpenVPN to protect them against future threats
Quantum computers have been widely discussed since the 1980s. Even though very few people have dealt with them by now, such devices steadily…
Full article →
2022.06.01 — Quarrel on the heap. Heap exploitation on a vulnerable SOAP server in Linux
This paper discusses a challenging CTF-like task. Your goal is to get remote code execution on a SOAP server. All exploitation primitives are involved with…
Full article →
2022.04.04 — Elephants and their vulnerabilities. Most epic CVEs in PostgreSQL
Once a quarter, PostgreSQL publishes minor releases containing vulnerabilities. Sometimes, such bugs make it possible to make an unprivileged user a local king superuser. To fix them,…
Full article →
2022.02.15 — EVE-NG: Building a cyberpolygon for hacking experiments
Virtualization tools are required in many situations: testing of security utilities, personnel training in attack scenarios or network infrastructure protection, etc. Some admins reinvent the wheel by…
Full article →
2023.06.08 — Croc-in-the-middle. Using crocodile clips do dump traffic from twisted pair cable
Some people say that eavesdropping is bad. But for many security specialists, traffic sniffing is a profession, not a hobby. For some reason, it's believed…
Full article →
2022.01.13 — Bug in Laravel. Disassembling an exploit that allows RCE in a popular PHP framework
Bad news: the Ignition library shipped with the Laravel PHP web framework contains a vulnerability. The bug enables unauthorized users to execute arbitrary code. This article examines…
Full article →
2022.02.15 — Reverse shell of 237 bytes. How to reduce the executable file using Linux hacks
Once I was asked: is it possible to write a reverse shell some 200 bytes in size? This shell should perform the following functions: change its name…
Full article →
2022.06.01 — Log4HELL! Everything you must know about Log4Shell
Up until recently, just a few people (aside from specialists) were aware of the Log4j logging utility. However, a vulnerability found in this library attracted to it…
Full article →
2023.02.21 — Pivoting District: GRE Pivoting over network equipment
Too bad, security admins often don't pay due attention to network equipment, which enables malefactors to hack such devices and gain control over them. What…
Full article →
2022.12.15 — What Challenges To Overcome with the Help of Automated e2e Testing?
This is an external third-party advertising publication. Every good developer will tell you that software development is a complex task. It's a tricky process requiring…
Full article →