akkki012/sysadmin_env
1
1"""2End-to-end attack scripts for all 16 sysadmin tasks.3 4Usage:5 Against a running server (Docker):6 python tests/test_all_tasks.py --url http://localhost:78607 8 Single task:9 python tests/test_all_tasks.py --url http://localhost:7860 --task ssh_hardening10 11 Local-only (no server, tests task setup/check directly — requires root):12 sudo python tests/test_all_tasks.py --local13"""14 15import argparse16import asyncio17import sys18from dataclasses import dataclass19from typing import List, Tuple20 21# ---------------------------------------------------------------------------22# Attack playbooks — each is a list of (command, explanation) tuples that23# a competent sysadmin would use to fix the injected problem.24# ---------------------------------------------------------------------------25 26PLAYBOOKS: dict[str, List[Tuple[str, str]]] = {27 # ── Original 8 ──────────────────────────────────────────────────────28 "ssh_hardening": [29 ("sudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config",30 "Disable root login"),31 ("sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config",32 "Disable password authentication"),33 ("sudo sed -i 's/^MaxAuthTries 6/MaxAuthTries 3/' /etc/ssh/sshd_config",34 "Reduce max auth tries to 3"),35 ("sudo service ssh reload 2>/dev/null || true",36 "Reload SSH service to apply changes"),37 ("done", "Finished SSH hardening"),38 ],39 40 "open_port": [41 ("sudo ss -tlnp | grep ':23 '",42 "Identify what is listening on port 23"),43 ("sudo pkill -f 'nc -lk 23'",44 "Kill the netcat process on port 23"),45 ("sudo ufw deny 23",46 "Block port 23 in firewall"),47 ("done", "Port 23 closed and blocked"),48 ],49 50 "malicious_cron": [51 ("sudo ls -la /etc/cron.d/",52 "List cron.d to find suspicious files"),53 ("sudo cat /etc/cron.d/backdoor",54 "Inspect the malicious cron file"),55 ("sudo rm /etc/cron.d/backdoor",56 "Remove the malicious cron file"),57 ("sudo grep -r 'curl.*|.*bash' /etc/cron* 2>/dev/null || echo clean",58 "Verify no curl-pipe-bash patterns remain"),59 ("done", "Malicious cron job removed"),60 ],61 62 "disk_cleanup": [63 ("sudo du -sh /var/log/bloat/bigfile.log",64 "Check the size of the bloated log file"),65 ("sudo rm /var/log/bloat/bigfile.log",66 "Remove the bloated log file"),67 ("sudo bash -c 'cat > /etc/logrotate.d/bloat << EOF\n/var/log/bloat/*.log {\n weekly\n rotate 4\n compress\n missingok\n notifempty\n}\nEOF'",68 "Create logrotate config to prevent future bloat"),69 ("done", "Disk cleaned and logrotate configured"),70 ],71 72 "nginx_fix": [73 ("sudo cat /etc/nginx/sites-enabled/default",74 "Inspect broken nginx config"),75 ("sudo bash -c 'cat > /etc/nginx/sites-enabled/default << EOF\nserver {\n listen 80;\n server_name _;\n\n location / {\n root /var/www/html;\n index index.html;\n }\n}\nEOF'",76 "Write fixed nginx config with semicolons"),77 ("sudo nginx -t",78 "Test nginx configuration"),79 ("sudo service nginx start",80 "Start nginx service"),81 ("done", "Nginx fixed and running"),82 ],83 84 "suspicious_process": [85 ("ps aux | grep xmrig",86 "Find the xmrig_sim cryptocurrency miner process"),87 ("sudo pkill -f xmrig_sim",88 "Kill the suspicious miner process"),89 ("sudo rm /usr/local/bin/xmrig_sim",90 "Remove the miner binary"),91 ("done", "Miner process killed and binary removed"),92 ],93 94 "fail2ban_setup": [95 ("sudo cat /var/log/auth.log | tail -20",96 "Check auth.log for brute-force attempts"),97 ("sudo apt-get update -qq && sudo apt-get install -y -qq fail2ban",98 "Install fail2ban"),99 ("sudo bash -c 'cat > /etc/fail2ban/jail.local << EOF\n[sshd]\nenabled = true\nmaxretry = 3\nbantime = 3600\nfindtime = 600\nEOF'",100 "Configure fail2ban with strict settings"),101 ("sudo service fail2ban start",102 "Start fail2ban service"),103 ("done", "Fail2ban installed and configured"),104 ],105 106 "world_writable": [107 ("sudo find /etc /usr/local/bin -perm -o+w -type f 2>/dev/null",108 "Find all world-writable files"),109 ("sudo chmod o-w /etc/world_writable_test1.conf /etc/world_writable_test2.conf /usr/local/bin/world_writable_test3.sh",110 "Remove world-writable permission from all found files"),111 ("done", "World-writable permissions fixed"),112 ],113 114 # ── New 8 ───────────────────────────────────────────────────────────115 "unauthorized_ssh_keys": [116 ("sudo cat /root/.ssh/authorized_keys",117 "Inspect root authorized_keys for rogue entries"),118 ("sudo cat /home/appuser/.ssh/authorized_keys",119 "Inspect appuser authorized_keys"),120 ("sudo sed -i '/attacker@evil.com/d' /root/.ssh/authorized_keys",121 "Remove rogue key from root"),122 ("sudo sed -i '/attacker@evil.com/d' /home/appuser/.ssh/authorized_keys",123 "Remove rogue key from appuser"),124 ("sudo cat /root/.ssh/authorized_keys",125 "Verify only legitimate keys remain"),126 ("done", "Unauthorized SSH keys removed"),127 ],128 129 "rogue_user": [130 ("sudo cat /etc/passwd | grep backdoor_admin",131 "Confirm rogue user exists"),132 ("sudo cat /etc/sudoers.d/backdoor_admin",133 "Check sudoers entry for rogue user"),134 ("sudo userdel -r backdoor_admin",135 "Delete rogue user and home directory"),136 ("sudo rm -f /etc/sudoers.d/backdoor_admin",137 "Remove sudoers entry"),138 ("sudo grep -r backdoor_admin /etc/sudoers /etc/sudoers.d/ 2>/dev/null || echo clean",139 "Verify no sudoers references remain"),140 ("done", "Rogue user account fully removed"),141 ],142 143 "suid_binaries": [144 ("sudo find /tmp /var/tmp -perm -4000 -type f 2>/dev/null",145 "Find SUID binaries in temp directories"),146 ("sudo rm -f /tmp/.hidden_shell",147 "Remove hidden SUID shell from /tmp"),148 ("sudo rm -f /var/tmp/escalate",149 "Remove SUID escalation binary from /var/tmp"),150 ("sudo rm -f /tmp/fake_sudo",151 "Remove fake sudo SUID binary"),152 ("done", "All rogue SUID binaries removed"),153 ],154 155 "dns_resolver": [156 ("cat /etc/resolv.conf",157 "Check current DNS configuration"),158 ("sudo bash -c 'cat > /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 1.1.1.1\nEOF'",159 "Set valid public DNS nameservers"),160 ("sudo chmod 644 /etc/resolv.conf",161 "Fix file permissions — remove world-writable"),162 ("done", "DNS resolver fixed"),163 ],164 165 "broken_apt": [166 ("sudo rm -f /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock",167 "Remove stale lock files"),168 ("sudo sed -i '/BROKEN_ENTRY/d' /var/lib/dpkg/status",169 "Remove corrupt entry from dpkg status"),170 ("sudo dpkg --configure -a",171 "Reconfigure any interrupted packages"),172 ("sudo apt-get update -qq",173 "Verify apt-get update works"),174 ("done", "Package manager repaired"),175 ],176 177 "hosts_hijack": [178 ("cat /etc/hosts",179 "Inspect current hosts file for hijacked entries"),180 ("sudo sed -i '/198.51.100.66/d' /etc/hosts",181 "Remove all lines with the malicious IP"),182 ("grep '127.0.0.1' /etc/hosts",183 "Verify localhost entry is still intact"),184 ("done", "Hosts file cleaned"),185 ],186 187 "kernel_hardening": [188 ("sudo sysctl net.ipv4.ip_forward net.ipv4.conf.all.accept_redirects net.ipv4.tcp_syncookies",189 "Check current insecure kernel parameters"),190 ("sudo sysctl -w net.ipv4.ip_forward=0",191 "Disable IP forwarding"),192 ("sudo sysctl -w net.ipv4.conf.all.accept_redirects=0",193 "Disable ICMP redirect acceptance"),194 ("sudo sysctl -w net.ipv4.tcp_syncookies=1",195 "Enable SYN flood protection"),196 ("sudo bash -c 'cat > /etc/sysctl.d/99-hardening.conf << EOF\nnet.ipv4.ip_forward = 0\nnet.ipv4.conf.all.accept_redirects = 0\nnet.ipv4.tcp_syncookies = 1\nEOF'",197 "Persist hardened settings across reboots"),198 ("done", "Kernel parameters hardened and persisted"),199 ],200 201 "reverse_shell": [202 ("ps aux | grep .rs_agent",203 "Locate the reverse shell process"),204 ("sudo pkill -f '.rs_agent'",205 "Kill the reverse shell process"),206 ("sudo rm -f /var/tmp/.rs_agent",207 "Remove the reverse shell binary"),208 ("sudo rm -f /etc/cron.d/rs_persist",209 "Remove cron persistence"),210 ("sudo rm -f /etc/systemd/system/rs_agent.timer /etc/systemd/system/rs_agent.service",211 "Remove systemd timer and service persistence"),212 ("sudo ls /etc/cron.d/ /etc/systemd/system/rs_agent* 2>/dev/null || echo all_clean",213 "Verify all persistence mechanisms removed"),214 ("done", "Reverse shell fully eradicated"),215 ],216}217 218 219# ---------------------------------------------------------------------------220# Remote runner — talks to the environment server over WebSocket221# ---------------------------------------------------------------------------222 223async def run_remote(url: str, task_id: str) -> float:224 from sysadmin_env import SysadminEnv, SysadminAction225 226 async with SysadminEnv(base_url=url) as env:227 result = await env.reset(task_id=task_id)228 obs = result.observation229 print(f"\n{'='*70}")230 print(f" TASK: {task_id}")231 print(f" {obs.task_description}")232 print(f"{'='*70}")233 234 playbook = PLAYBOOKS[task_id]235 for command, explanation in playbook:236 action = SysadminAction(command=command, explanation=explanation)237 result = await env.step(action)238 obs = result.observation239 240 status = "OK" if obs.exit_code == 0 else f"EXIT {obs.exit_code}"241 print(f" [{obs.step:2d}] [{status:>8}] $ {command[:80]}")242 if obs.stderr and obs.exit_code != 0:243 print(f" stderr: {obs.stderr[:120]}")244 if obs.hint:245 print(f" hint: {obs.hint}")246 if result.reward is not None:247 print(f"\n >>> REWARD: {result.reward:.3f}")248 if obs.done:249 break250 251 reward = result.reward if result.reward is not None else 0.0252 passed = "PASS" if reward >= 0.5 else "FAIL"253 print(f" >>> {passed} (reward={reward:.3f})\n")254 return reward255 256 257# ---------------------------------------------------------------------------258# Local runner — directly instantiates tasks (needs root, no server needed)259# ---------------------------------------------------------------------------260 261def run_local(task_id: str) -> float:262 sys.path.insert(0, "/home/akshay/Desktop/meta-ai-linuxsysadmin/sysadmin_env")263 sys.path.insert(0, "/home/akshay/Desktop/meta-ai-linuxsysadmin")264 265 import subprocess266 from server.sysadmin_environment import ALL_TASKS267 268 task = ALL_TASKS[task_id]269 print(f"\n{'='*70}")270 print(f" TASK: {task_id} (local mode)")271 print(f" {task.description}")272 print(f"{'='*70}")273 274 task.setup()275 print(" [setup] Broken state injected")276 277 playbook = PLAYBOOKS[task_id]278 action_history = []279 280 for command, explanation in playbook:281 if command == "done":282 action_history.append({"command": command, "explanation": explanation,283 "stdout": "", "stderr": "", "exit_code": 0})284 continue285 286 result = subprocess.run(command, shell=True, executable="/bin/bash",287 capture_output=True, text=True, timeout=15)288 action_history.append({289 "command": command, "explanation": explanation,290 "stdout": result.stdout[:4000], "stderr": result.stderr[:2000],291 "exit_code": result.returncode,292 })293 status = "OK" if result.returncode == 0 else f"EXIT {result.returncode}"294 print(f" [{status:>8}] $ {command[:80]}")295 if result.stderr and result.returncode != 0:296 print(f" stderr: {result.stderr[:120]}")297 298 check_result = task.check(action_history)299 print(f"\n Programmatic score : {check_result.score:.3f}")300 print(f" Passed : {check_result.passed}")301 print(f" Feedback : {check_result.feedback}")302 303 task.teardown()304 print(" [teardown] Clean state restored")305 306 passed = "PASS" if check_result.score >= 0.8 else "FAIL"307 print(f" >>> {passed} (score={check_result.score:.3f})\n")308 return check_result.score309 310 311# ---------------------------------------------------------------------------312# Main313# ---------------------------------------------------------------------------314 315async def main():316 parser = argparse.ArgumentParser(description="Test all sysadmin RL tasks")317 parser.add_argument("--url", default="http://localhost:7860",318 help="Server URL (default: http://localhost:7860)")319 parser.add_argument("--task", default=None,320 help="Run a single task by ID (default: all)")321 parser.add_argument("--local", action="store_true",322 help="Run locally without a server (requires root)")323 args = parser.parse_args()324 325 task_ids = [args.task] if args.task else list(PLAYBOOKS.keys())326 327 print(f"\nRunning {len(task_ids)} task(s) in {'local' if args.local else 'remote'} mode\n")328 329 results = {}330 for task_id in task_ids:331 try:332 if args.local:333 score = run_local(task_id)334 else:335 score = await run_remote(args.url, task_id)336 results[task_id] = score337 except Exception as e:338 print(f" >>> ERROR on {task_id}: {e}\n")339 results[task_id] = -1.0340 341 # Summary342 print(f"\n{'='*70}")343 print(" SUMMARY")344 print(f"{'='*70}")345 for tid, score in results.items():346 if score < 0:347 status = "ERROR"348 elif score >= 0.5:349 status = " PASS"350 else:351 status = " FAIL"352 print(f" [{status}] {tid:30s} score={score:.3f}")353 354 passed = sum(1 for s in results.values() if s >= 0.5)355 total = len(results)356 errored = sum(1 for s in results.values() if s < 0)357 print(f"\n {passed}/{total} passed, {errored} errors")358 print(f"{'='*70}\n")359 360 361if __name__ == "__main__":362 asyncio.run(main())363 