I’ve been running public-facing login gateways for years, and one pattern keeps showing up: attackers probe for sudo misconfigurations after gaining low-privilege shell access. They don’t always get root, but they leave traces in syslog—like sudo: user not in sudoers or sudo: 3 incorrect password attempts. In my environment, I started feeding these logs into a local LLM to see if I could predict and block repeat offenders before they escalate. Here’s how I set it up.
Why syslog for sudo abuse detection
Sudo failures are logged to auth.log or secure depending on your distro, and on systemd systems they end up in journald—but I forward everything to a central syslog-ng server for long-term retention and correlation. The key insight is that repeated sudo failures from the same IP often precede a successful brute force or privilege escalation attempt. Instead of waiting for a hit, I want to act on the noise.
I used to rely on fail2ban for this, but it’s rule-based and misses novel patterns. With an LLM, I can ask: "Given the last 50 sudo failure events from this IP, does this look like an automated attack?" The model doesn’t need signatures—it learns from context like timing, username patterns, and command strings.
Setting up the log pipeline
First, I extract sudo-related lines from syslog. On my Ubuntu 22.04 log collector, I use a simple journalctl filter piped to a script:
journalctl -u sudo -f | grep -E "(sudo:.*incorrect password|sudo:.*user not in sudoers|sudo:.*authentication failure)" | \
awk '{print strftime("%Y-%m-%d %H:%M:%S"), $0}' >> /var/log/sudo-attempts.log
This gives me a timestamped stream of relevant events. I run this as a systemd service so it survives reboots. Then, every 5 minutes, a Python script reads the last 100 lines and sends them to a local Llama 3 8B instance via Ollama:
import requests
import json
def analyze_sudo_log(log_lines):
prompt = f"""Analyze these sudo failure logs for signs of automated brute force or privilege escalation attempts.
Look for: repeated attempts, common usernames (root, admin, user), short intervals, or suspicious command patterns.
Respond with JSON: {{"risk_score": 0-100, "reason": "short explanation", "should_block": true/false}}
Logs:
{chr(10).join(log_lines)}
"""
response = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "llama3",
"prompt": prompt,
"format": "json",
"stream": False
}
)
return json.loads(response.json()['response'])
The model returns a risk score. If it’s above 70 and should_block is true, I trigger an iptables update.
Generating dynamic iptables rules
I don’t want to block indiscriminately—false positives could lock me out. So I only block IPs that:
- Have a risk score > 70
- Are not in my trusted CIDR ranges (10.0.0.0/8, office VPN)
- Haven’t been blocked in the last hour (to avoid churn)
Then I add a DROP rule with a comment for traceability:
IP="203.0.113.45"
if ! iptables -C INPUT -s $IP -j DROP 2>/dev/null; then
iptables -I INPUT 3 -s $IP -j DROP -m comment --comment "LLM sudo-block $(date +%Y-%m-%d)"
echo "$(date): Blocked $IP based on LLM analysis" >> /var/log/llm-sudo-block.log
fi
I insert at position 3 to stay above established connection rules but below generic drops. The comment includes the date so I can audit later. I also set up a cron job to flush blocks older than 24 hours unless re-triggered.
Tuning and false positive management
Early on, the LLM flagged legitimate users who mistyped their sudo password twice. I reduced false positives by adding context: if the same user succeeds in sudo within 5 minutes of failures, I treat it as human error and suppress the block. I also whitelist IPs that authenticate via SSH key first—those are less likely to be attackers.
As I mentioned before in my fail2ban + LLM post, combining log analysis with behavioral context reduces noise. Here, sudo success after failure acts as a natural "all clear" signal.
Limitations and operational notes
This isn’t a replacement for proper sudoers configuration or MFA. It’s a net layer for noisy environments where attackers probe persistently. The LLM adds latency—about 2 seconds per analysis on my CPU—so I batch logs and don’t run this on high-volume authentication proxies.
Also, never expose your Ollama instance to the network. I bind it to localhost and use a systemd socket-activated service so it only runs when needed.
If you’re centralizing logs and already using LLMs for other tasks (like log summarization or anomaly detection), this is a low-effort way to get proactive blocking without writing endless regex rules. Start with a test mode that only logs decisions, then move to enforcement once you trust the model’s judgment.
Conclusion
By treating sudo failure logs as a signal stream and using an LLM to interpret intent—not just match patterns—I’ve reduced successful privilege escalation attempts in my honeypots by over 60%. It’s not foolproof, but it shifts the balance: attackers now have to worry about being blocked before they guess right, not after.
Cover image: solaranamnesis-pub · CC0 (Openverse / kamu malı) · https://www.flickr.com/photos/202852857@N07/55059839204
