ubuntils 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ubuntils-1.5.0/LICENSE +21 -0
- ubuntils-1.5.0/PKG-INFO +562 -0
- ubuntils-1.5.0/README.md +525 -0
- ubuntils-1.5.0/pyproject.toml +60 -0
- ubuntils-1.5.0/setup.cfg +4 -0
- ubuntils-1.5.0/tests/test_base_classes.py +181 -0
- ubuntils-1.5.0/tests/test_cli.py +309 -0
- ubuntils-1.5.0/tests/test_collectors.py +553 -0
- ubuntils-1.5.0/tests/test_config.py +77 -0
- ubuntils-1.5.0/tests/test_detection_rules.py +474 -0
- ubuntils-1.5.0/tests/test_json_formatter.py +137 -0
- ubuntils-1.5.0/tests/test_remediators.py +489 -0
- ubuntils-1.5.0/tests/test_timeline.py +203 -0
- ubuntils-1.5.0/tests/test_tui.py +752 -0
- ubuntils-1.5.0/tests/test_utils.py +121 -0
- ubuntils-1.5.0/ubuntils/__init__.py +1 -0
- ubuntils-1.5.0/ubuntils/cli.py +195 -0
- ubuntils-1.5.0/ubuntils/collectors/__init__.py +19 -0
- ubuntils-1.5.0/ubuntils/collectors/base.py +11 -0
- ubuntils-1.5.0/ubuntils/collectors/cron.py +73 -0
- ubuntils-1.5.0/ubuntils/collectors/environment.py +86 -0
- ubuntils-1.5.0/ubuntils/collectors/network.py +58 -0
- ubuntils-1.5.0/ubuntils/collectors/processes.py +48 -0
- ubuntils-1.5.0/ubuntils/collectors/ssh.py +52 -0
- ubuntils-1.5.0/ubuntils/collectors/sudoers.py +75 -0
- ubuntils-1.5.0/ubuntils/collectors/systemd.py +77 -0
- ubuntils-1.5.0/ubuntils/collectors/users.py +74 -0
- ubuntils-1.5.0/ubuntils/detectors/__init__.py +3 -0
- ubuntils-1.5.0/ubuntils/detectors/engine.py +46 -0
- ubuntils-1.5.0/ubuntils/detectors/finding.py +38 -0
- ubuntils-1.5.0/ubuntils/detectors/rules.py +269 -0
- ubuntils-1.5.0/ubuntils/formatters/__init__.py +3 -0
- ubuntils-1.5.0/ubuntils/formatters/json_formatter.py +73 -0
- ubuntils-1.5.0/ubuntils/remediators/__init__.py +12 -0
- ubuntils-1.5.0/ubuntils/remediators/base.py +136 -0
- ubuntils-1.5.0/ubuntils/remediators/cron.py +34 -0
- ubuntils-1.5.0/ubuntils/remediators/environment.py +37 -0
- ubuntils-1.5.0/ubuntils/remediators/ssh.py +30 -0
- ubuntils-1.5.0/ubuntils/remediators/sudoers.py +42 -0
- ubuntils-1.5.0/ubuntils/timeline/__init__.py +1 -0
- ubuntils-1.5.0/ubuntils/timeline/builder.py +114 -0
- ubuntils-1.5.0/ubuntils/tui/__init__.py +6 -0
- ubuntils-1.5.0/ubuntils/tui/app.py +128 -0
- ubuntils-1.5.0/ubuntils/tui/confirm_modal.py +63 -0
- ubuntils-1.5.0/ubuntils/tui/findings_panel.py +166 -0
- ubuntils-1.5.0/ubuntils/tui/results_screen.py +106 -0
- ubuntils-1.5.0/ubuntils/tui/scan_screen.py +77 -0
- ubuntils-1.5.0/ubuntils/tui/stats_panel.py +44 -0
- ubuntils-1.5.0/ubuntils/tui/summary_screen.py +41 -0
- ubuntils-1.5.0/ubuntils/tui/timeline_panel.py +35 -0
- ubuntils-1.5.0/ubuntils/utils/__init__.py +0 -0
- ubuntils-1.5.0/ubuntils/utils/config.py +56 -0
- ubuntils-1.5.0/ubuntils/utils/logging.py +26 -0
- ubuntils-1.5.0/ubuntils/utils/shell.py +9 -0
- ubuntils-1.5.0/ubuntils/utils/since_parser.py +27 -0
- ubuntils-1.5.0/ubuntils/utils/validators.py +23 -0
- ubuntils-1.5.0/ubuntils.egg-info/PKG-INFO +562 -0
- ubuntils-1.5.0/ubuntils.egg-info/SOURCES.txt +60 -0
- ubuntils-1.5.0/ubuntils.egg-info/dependency_links.txt +1 -0
- ubuntils-1.5.0/ubuntils.egg-info/entry_points.txt +2 -0
- ubuntils-1.5.0/ubuntils.egg-info/requires.txt +10 -0
- ubuntils-1.5.0/ubuntils.egg-info/top_level.txt +1 -0
ubuntils-1.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 asmitdesai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ubuntils-1.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ubuntils
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Ubuntu incident response tool for forensic artifact collection, detection, and guided remediation
|
|
5
|
+
Author-email: Asmit Desai <asmitdesai02@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/asmitdesai/ubuntils
|
|
8
|
+
Project-URL: Repository, https://github.com/asmitdesai/ubuntils
|
|
9
|
+
Project-URL: Issues, https://github.com/asmitdesai/ubuntils/issues
|
|
10
|
+
Keywords: incident-response,forensics,ubuntu,security,dfir
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: click>=8.1.0
|
|
28
|
+
Requires-Dist: textual>=0.50.0
|
|
29
|
+
Requires-Dist: structlog>=24.0.0
|
|
30
|
+
Requires-Dist: pyyaml>=6.0
|
|
31
|
+
Requires-Dist: python-dateutil>=2.8
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# ubuntils
|
|
39
|
+
|
|
40
|
+
Forensic triage for live Ubuntu systems — automated artifact collection, persistence detection, and guided remediation in under 5 seconds.
|
|
41
|
+
|
|
42
|
+
[](https://github.com/asmitdesai/ubuntils/actions/workflows/ci.yml)
|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+

|
|
47
|
+

|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The Problem
|
|
53
|
+
|
|
54
|
+
When you suspect a Linux system is compromised, the first 30-40 minutes are usually spent running the same ten commands in sequence: check running processes, look for weird cron jobs, grep for LD_PRELOAD, scan authorized_keys for new entries, audit sudoers. Each step is manual, context-switching, and error-prone under pressure. Miss one source — say, `/etc/sudoers.d/` instead of just `/etc/sudoers`, or user crontabs in addition to `/etc/cron.d/` — and you have an incomplete picture.
|
|
55
|
+
|
|
56
|
+
Existing options don't solve this cleanly. `lynis` is a hardening auditor, not a triage tool — it reports configuration weaknesses on a clean system and generates noise on a hot one. `chkrootkit` and `rkhunter` check for known rootkit signatures but are blind to novel persistence techniques like abused systemd timers or legitimate-looking cron entries. Generic SIEM queries require log infrastructure that may not exist on the system you're looking at. And forensic suites like Volatility target memory images, not a live shell on a running host.
|
|
57
|
+
|
|
58
|
+
The gap is a tool that runs on the live system right now, covers the most common persistence vectors, correlates activity across log sources into a timeline, and tells you exactly what to look at — without requiring an external agent, a database, or an internet connection.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## What ubuntils does
|
|
63
|
+
|
|
64
|
+
ubuntils runs in four sequential stages:
|
|
65
|
+
|
|
66
|
+
1. **Collection** — Eight collectors gather forensic artifacts concurrently from `/proc`, cron tables, systemd units, SSH keys, sudoers files, and environment definitions. Takes roughly 2.5 seconds on a typical system.
|
|
67
|
+
2. **Detection** — A detection engine runs all eight rules over the collected artifacts, producing a ranked findings list in about one second.
|
|
68
|
+
3. **Timeline** — A timeline builder reads syslog, journald, and auditd in parallel and correlates events chronologically, adding roughly 0.3 seconds.
|
|
69
|
+
4. **Output** — Results appear either in an interactive four-tab TUI (default) or as structured JSON on stdout (`--json`).
|
|
70
|
+
|
|
71
|
+
No network calls are made. No data leaves the system.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
**Ubuntu 22.04+ and any system with PEP 668 (recommended):**
|
|
78
|
+
|
|
79
|
+
Ubuntu 22.04+ blocks system-wide `pip install`. Use `pipx` — it handles the environment transparently so you never think about it:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
sudo apt install pipx -y
|
|
83
|
+
cd ubuntils
|
|
84
|
+
pipx install -e .
|
|
85
|
+
ubuntils scan
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Older systems / manual install:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/asmitdesai/ubuntils.git
|
|
92
|
+
cd ubuntils
|
|
93
|
+
pip install -r requirements.txt -e .
|
|
94
|
+
ubuntils scan
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
ubuntils requires root for full artifact access. If you run `ubuntils scan` as a non-root user it will automatically re-invoke itself with `sudo`, preserving your PATH so the correct Python environment is used. Running without root will skip `/etc/shadow`, some `/proc` entries, and protected cron files, and will log warnings for each.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Quick start
|
|
102
|
+
|
|
103
|
+
**Detection only — interactive TUI:**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
sudo ubuntils scan
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Detection with JSON output saved to file:**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
sudo ubuntils scan --json > /tmp/triage-$(hostname)-$(date +%Y%m%d).json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Detection with CLI remediation preview (dry run — no changes applied):**
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
sudo ubuntils scan --remediate
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Detection with CLI remediation applied:**
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
sudo ubuntils scan --remediate --confirm
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Print version:**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
ubuntils version
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Flags and configuration
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
ubuntils scan [OPTIONS]
|
|
139
|
+
--json Output JSON to stdout instead of launching the TUI
|
|
140
|
+
--output FILE Write the JSON report to FILE (implies --json)
|
|
141
|
+
--remediate Run the remediation engine after detection
|
|
142
|
+
--confirm Required with --remediate to actually apply changes (else dry-run)
|
|
143
|
+
--config FILE YAML allowlist of findings to suppress (see below)
|
|
144
|
+
--since TIME Limit the timeline to events since TIME (e.g. '24h', '7d', '2026-05-20')
|
|
145
|
+
--verbose Verbose structlog output
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### False-positive allowlisting (`--config`)
|
|
149
|
+
|
|
150
|
+
A freshly provisioned or CI-managed host generates expected noise — deploy keys, provisioning crontabs, baked-in shell init. Instead of teaching responders to mentally filter it, suppress it explicitly with a YAML allowlist:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# allowlist.yaml
|
|
154
|
+
allowlist:
|
|
155
|
+
rules:
|
|
156
|
+
- SHELL_RC_MODIFICATION # suppress this rule entirely
|
|
157
|
+
paths:
|
|
158
|
+
- /home/ci/.ssh/authorized_keys # suppress any finding on this exact path
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
sudo ubuntils scan --json --config allowlist.yaml
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Suppression is always explicit — by rule id and/or exact artifact path. There is no blanket "ignore everything" switch. A sample lives at [`examples/allowlist.yaml`](examples/allowlist.yaml).
|
|
166
|
+
|
|
167
|
+
### Report integrity
|
|
168
|
+
|
|
169
|
+
Every `--json` report ends with a `report_sha256` field — a SHA-256 over the canonical report content. This makes a collected triage artifact tamper-evident and lets you reference a specific scan by digest in a case file. The report also records `tool_version`, `hostname`, and a UTC `generated_at` timestamp under `scan_metadata`.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## The TUI
|
|
174
|
+
|
|
175
|
+
Running `sudo ubuntils scan` (without `--json`) launches a full-terminal interactive TUI.
|
|
176
|
+
|
|
177
|
+
### Scan screen
|
|
178
|
+
|
|
179
|
+
While collectors run, ubuntils shows a live checklist — one row per collector. Each row updates in real time as the collector finishes:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Scanning system…
|
|
183
|
+
|
|
184
|
+
✓ Process
|
|
185
|
+
✓ Network
|
|
186
|
+
✓ Users
|
|
187
|
+
⠹ Cron
|
|
188
|
+
Systemd
|
|
189
|
+
SSH
|
|
190
|
+
Sudoers
|
|
191
|
+
Environment
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`✓` marks success, `✗` marks failure, the spinner marks the active collector, and blank rows are pending. Once all collectors finish and detection + timeline complete, the TUI switches automatically to the results screen.
|
|
195
|
+
|
|
196
|
+
### Results screen
|
|
197
|
+
|
|
198
|
+
The results screen has four tabs navigated by number keys:
|
|
199
|
+
|
|
200
|
+
| Key | Tab | Contents |
|
|
201
|
+
|-----|-----|----------|
|
|
202
|
+
| `1` | Summary | Scan stats + top findings at a glance |
|
|
203
|
+
| `2` | Findings | Full findings list with inline detail and remediation |
|
|
204
|
+
| `3` | Timeline | Chronological correlated log events |
|
|
205
|
+
| `4` | Stats | Ubuntu version, architecture, duration, collector counts |
|
|
206
|
+
|
|
207
|
+
Press `q` or `Ctrl+C` to exit.
|
|
208
|
+
|
|
209
|
+
### Summary tab (key `1`)
|
|
210
|
+
|
|
211
|
+
Shows scan metadata and the top findings on a single screen:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
Collectors: 8 run · 0 failed
|
|
215
|
+
Findings: 2 HIGH · 1 MEDIUM · 0 LOW
|
|
216
|
+
Timeline: 47 events
|
|
217
|
+
Duration: 2.8s
|
|
218
|
+
|
|
219
|
+
● HIGH CRON_TMP_PATH /etc/cron.d/cleanup
|
|
220
|
+
● HIGH LD_PRELOAD_INJECT /home/alice/.bashrc
|
|
221
|
+
○ MED SSH_UNAUTHORIZED_KEY /home/bob/.ssh/authorized_keys
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
On a clean system, this tab shows `System appears clean.`
|
|
225
|
+
|
|
226
|
+
### Findings tab (key `2`)
|
|
227
|
+
|
|
228
|
+
A scrollable list of all findings, sorted HIGH → MEDIUM → LOW. Selecting a finding (Enter or arrow keys) expands a detail pane at the bottom showing the full description, artifact path, raw triggering value, and remediation information.
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
HIGH CRON_TMP_PATH /etc/cron.d/cleanup
|
|
232
|
+
HIGH LD_PRELOAD_INJECT /home/alice/.bashrc
|
|
233
|
+
MED SSH_UNAUTHORIZED_KEY /home/bob/.ssh/authorized_keys
|
|
234
|
+
───────────────────────────────────────────────────────
|
|
235
|
+
A cron job was found referencing /tmp, /var/tmp, or /dev/shm.
|
|
236
|
+
These directories are world-writable and commonly used as
|
|
237
|
+
attacker staging grounds.
|
|
238
|
+
|
|
239
|
+
Artifact: /etc/cron.d/cleanup
|
|
240
|
+
Raw: 0 * * * * root /tmp/.update
|
|
241
|
+
Fix: Will remove the offending cron entry from
|
|
242
|
+
/etc/cron.d/cleanup after creating a timestamped backup.
|
|
243
|
+
|
|
244
|
+
R: remediate
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### In-TUI remediation
|
|
248
|
+
|
|
249
|
+
For findings that have automated remediation available, press `R` while the finding is selected. A confirmation modal appears:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
┌─────────────────────────────────────────────────┐
|
|
253
|
+
│ Remediate CRON_TMP_PATH? │
|
|
254
|
+
│ │
|
|
255
|
+
│ Will remove the offending cron entry from │
|
|
256
|
+
│ /etc/cron.d/cleanup after creating a backup. │
|
|
257
|
+
│ Backup will be created at /var/backups/ubuntils/…│
|
|
258
|
+
│ │
|
|
259
|
+
│ Y: confirm Esc: cancel │
|
|
260
|
+
└─────────────────────────────────────────────────┘
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Press `Y` to confirm. The remediator runs in a background thread. When it completes, the finding row in the list is updated to `[fixed]` and the detail pane shows the outcome:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
✓ Remediated
|
|
267
|
+
Backup: /var/backups/ubuntils/20260115_142201/etc_cron.d_cleanup
|
|
268
|
+
Rollback: cp /var/backups/ubuntils/20260115_142201/etc_cron.d_cleanup /etc/cron.d/cleanup
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
On failure, the detail pane shows the error. The backup is always created before any change is attempted.
|
|
272
|
+
|
|
273
|
+
Press `Esc` to collapse the detail pane.
|
|
274
|
+
|
|
275
|
+
### Timeline tab (key `3`)
|
|
276
|
+
|
|
277
|
+
A scrollable chronological list of correlated log events. Each row shows timestamp, source, and description. Events are pulled from syslog, journald, and auditd and deduplicated.
|
|
278
|
+
|
|
279
|
+
### Stats tab (key `4`)
|
|
280
|
+
|
|
281
|
+
A summary view of the scan: detected Ubuntu version, architecture, scan duration, collector count and failures, and finding counts per severity alongside total timeline events.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## What it detects
|
|
286
|
+
|
|
287
|
+
| Rule ID | Severity | Remediable | What it checks |
|
|
288
|
+
|---|---|---|---|
|
|
289
|
+
| CRON_ROOT_EXEC | HIGH | Yes | Non-root user crontabs running commands in root-owned paths or inlining sudo |
|
|
290
|
+
| CRON_TMP_PATH | HIGH | Yes | Any cron job referencing /tmp, /var/tmp, or /dev/shm |
|
|
291
|
+
| LD_PRELOAD_INJECT | HIGH | Yes | LD_PRELOAD defined in /etc/ld.so.preload or any shell init file pointing outside /lib, /usr/lib, /lib64, /usr/lib64 |
|
|
292
|
+
| SUSPICIOUS_SYSTEMD_TIMER | HIGH | No | Systemd timers whose service ExecStart points to a world-writable directory or a path not owned by root |
|
|
293
|
+
| SSH_UNAUTHORIZED_KEY | MEDIUM | Yes | authorized_keys files modified within the last 7 days |
|
|
294
|
+
| USER_UID_ZERO | HIGH | No | Any account other than `root` with UID 0 (a hidden second superuser) |
|
|
295
|
+
| SUDOERS_NOPASSWD | MEDIUM | Yes | NOPASSWD sudoers grants for users with UID ≥ 1000 and a login shell |
|
|
296
|
+
| PROCESS_MASQUERADE | MEDIUM | No | Processes whose name matches a known system binary but whose executable path is outside /usr/bin, /usr/sbin, /bin, /sbin |
|
|
297
|
+
| SHELL_RC_MODIFICATION | LOW | No | Shell init files (bashrc, profile, zshrc, etc.) modified within the last 48 hours for any user with a login shell |
|
|
298
|
+
|
|
299
|
+
### Why each rule exists
|
|
300
|
+
|
|
301
|
+
**CRON_ROOT_EXEC** — User crontabs run as the crontab owner. An entry invoking sudo or a root-owned interpreter means the user has arranged for code to run with root privileges on a schedule, without needing persistent sudo access. This survives password changes.
|
|
302
|
+
|
|
303
|
+
*Example finding:*
|
|
304
|
+
```
|
|
305
|
+
[HIGH] CRON_ROOT_EXEC
|
|
306
|
+
Title: User crontab executing with sudo
|
|
307
|
+
Artifact: /var/spool/cron/crontabs/alice
|
|
308
|
+
Raw value: */5 * * * * sudo /usr/bin/python3 /tmp/beacon.py
|
|
309
|
+
Remediation: available
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**CRON_TMP_PATH** — World-writable directories like /tmp and /dev/shm are standard attacker staging grounds. A cron job pointing there means a payload can be swapped out between invocations without touching any persistent path.
|
|
313
|
+
|
|
314
|
+
*Example finding:*
|
|
315
|
+
```
|
|
316
|
+
[HIGH] CRON_TMP_PATH
|
|
317
|
+
Title: Cron job references writable temp directory
|
|
318
|
+
Artifact: /etc/cron.d/cleanup
|
|
319
|
+
Raw value: 0 * * * * root /tmp/.update
|
|
320
|
+
Remediation: available
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**LD_PRELOAD_INJECT** — LD_PRELOAD causes the dynamic linker to load a specified shared library before all others, allowing arbitrary function interception in any dynamically linked binary. A value pointing outside standard library paths is a near-certain userspace rootkit indicator.
|
|
324
|
+
|
|
325
|
+
*Example finding:*
|
|
326
|
+
```
|
|
327
|
+
[HIGH] LD_PRELOAD_INJECT
|
|
328
|
+
Title: LD_PRELOAD set to non-standard library path
|
|
329
|
+
Artifact: /home/alice/.bashrc
|
|
330
|
+
Raw value: export LD_PRELOAD=/tmp/.libssl.so
|
|
331
|
+
Remediation: available
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**SUSPICIOUS_SYSTEMD_TIMER** — Systemd timers are more persistent and less visible than cron jobs to most responders. A timer whose service unit executes from a temp directory or a user-owned path is a sign of attacker-created persistence. Flag-only — systemd unit removal requires human judgment.
|
|
335
|
+
|
|
336
|
+
*Example finding:*
|
|
337
|
+
```
|
|
338
|
+
[HIGH] SUSPICIOUS_SYSTEMD_TIMER
|
|
339
|
+
Title: Systemd timer ExecStart points to suspicious path
|
|
340
|
+
Artifact: /etc/systemd/system/update-check.timer
|
|
341
|
+
Raw value: ExecStart=/tmp/.sys/update
|
|
342
|
+
Remediation: not available
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**SSH_UNAUTHORIZED_KEY** — A newly added SSH key grants persistent remote access independent of passwords. The 7-day window catches recent additions while avoiding noise from initial provisioning on older systems. Note: the rule uses file mtime, which reflects the last write to the authorized_keys file, not the insertion timestamp of each individual key.
|
|
346
|
+
|
|
347
|
+
*Example finding:*
|
|
348
|
+
```
|
|
349
|
+
[MEDIUM] SSH_UNAUTHORIZED_KEY
|
|
350
|
+
Title: SSH authorized key added in last 7 days
|
|
351
|
+
Artifact: /home/bob/.ssh/authorized_keys
|
|
352
|
+
Raw value: ssh-rsa AAAAB3NzaC1... attacker@evil
|
|
353
|
+
Remediation: available
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**SUDOERS_NOPASSWD** — Password-free sudo for a human user account (UID ≥ 1000 with a login shell) is a privilege escalation vector that survives the removal of other persistence mechanisms. Legitimate NOPASSWD grants are almost always for service accounts with no login shell.
|
|
357
|
+
|
|
358
|
+
*Example finding:*
|
|
359
|
+
```
|
|
360
|
+
[MEDIUM] SUDOERS_NOPASSWD
|
|
361
|
+
Title: NOPASSWD sudo grant for regular user
|
|
362
|
+
Artifact: /etc/sudoers.d/alice
|
|
363
|
+
Raw value: alice ALL=(ALL) NOPASSWD: ALL
|
|
364
|
+
Remediation: available
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**PROCESS_MASQUERADE** — Naming a malicious binary after a known system process (sshd, python3, bash) is a basic technique to avoid detection in `ps` output. This rule cross-references the process name from `/proc/<pid>/status` against the resolved exe path from `/proc/<pid>/exe`. Flag-only — killing a process requires human judgment.
|
|
368
|
+
|
|
369
|
+
*Example finding:*
|
|
370
|
+
```
|
|
371
|
+
[MEDIUM] PROCESS_MASQUERADE
|
|
372
|
+
Title: Process masquerading as system binary
|
|
373
|
+
Artifact: /proc/1337/exe
|
|
374
|
+
Raw value: name=sshd, exe=/tmp/.sshd
|
|
375
|
+
Remediation: not available
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**USER_UID_ZERO** — Only `root` should hold UID 0. A second account mapped to UID 0 (CIS Ubuntu Benchmark 6.2.x) is a high-confidence backdoor: it grants full superuser rights without altering root's own credentials, and survives a root password reset. Near-zero false-positive rate. Flag-only — removing a UID-0 account requires human judgment.
|
|
379
|
+
|
|
380
|
+
*Example finding:*
|
|
381
|
+
```
|
|
382
|
+
[HIGH] USER_UID_ZERO
|
|
383
|
+
Title: Non-root account with UID 0
|
|
384
|
+
Artifact: /etc/passwd
|
|
385
|
+
Raw value: toor:x:0:0:...:/bin/bash
|
|
386
|
+
Remediation: not available
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**SHELL_RC_MODIFICATION** — Shell init files are a reliable persistence vector because they execute on every user login. This rule surfaces recent modifications for human review. Flag-only — shell RC content requires reading before acting on it.
|
|
390
|
+
|
|
391
|
+
*Example finding:*
|
|
392
|
+
```
|
|
393
|
+
[LOW] SHELL_RC_MODIFICATION
|
|
394
|
+
Title: Shell init file recently modified
|
|
395
|
+
Artifact: /root/.bashrc
|
|
396
|
+
Raw value: mtime=2024-01-15 14:22:01 (6 hours ago)
|
|
397
|
+
Remediation: not available
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## JSON output
|
|
403
|
+
|
|
404
|
+
`--json` writes a single JSON object to stdout. Nothing else is printed.
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
{
|
|
408
|
+
"scan_metadata": {
|
|
409
|
+
"tool_version": "1.1.0",
|
|
410
|
+
"hostname": "web-01",
|
|
411
|
+
"generated_at": "2026-06-10T08:22:03.114523+00:00",
|
|
412
|
+
"ubuntu_version": "Ubuntu 22.04.3 LTS",
|
|
413
|
+
"architecture": "x86_64",
|
|
414
|
+
"duration_s": 2.84,
|
|
415
|
+
"collector_failures": 0
|
|
416
|
+
},
|
|
417
|
+
"artifact_counts": {
|
|
418
|
+
"ProcessCollector": 142,
|
|
419
|
+
"NetworkCollector": 23,
|
|
420
|
+
"UserCollector": 4,
|
|
421
|
+
"CronCollector": 7,
|
|
422
|
+
"SystemdCollector": 12,
|
|
423
|
+
"SSHCollector": 3,
|
|
424
|
+
"SudoersCollector": 5,
|
|
425
|
+
"EnvironmentCollector": 18
|
|
426
|
+
},
|
|
427
|
+
"findings": [
|
|
428
|
+
{
|
|
429
|
+
"rule_id": "CRON_TMP_PATH",
|
|
430
|
+
"severity": "HIGH",
|
|
431
|
+
"title": "Cron job references writable temp directory",
|
|
432
|
+
"description": "A cron job was found referencing /tmp, /var/tmp, or /dev/shm. These directories are world-writable and commonly used as attacker staging grounds.",
|
|
433
|
+
"artifact_path": "/etc/cron.d/cleanup",
|
|
434
|
+
"raw_value": "0 * * * * root /tmp/.update",
|
|
435
|
+
"remediation_available": true,
|
|
436
|
+
"remediation_description": "Will remove the offending cron entry from /etc/cron.d/cleanup after creating a timestamped backup."
|
|
437
|
+
}
|
|
438
|
+
],
|
|
439
|
+
"timeline": [
|
|
440
|
+
{
|
|
441
|
+
"timestamp": "2024-01-15T08:22:01+00:00",
|
|
442
|
+
"source": "syslog",
|
|
443
|
+
"description": "sshd: Accepted publickey for alice from 10.0.0.42 port 52341"
|
|
444
|
+
}
|
|
445
|
+
],
|
|
446
|
+
"report_sha256": "a3f1c9…(64 hex chars)"
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
`remediation_results` appears as an additional top-level key only when `--remediate` is passed. `report_sha256` is always present and is computed over the rest of the document.
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Remediation
|
|
455
|
+
|
|
456
|
+
Five of the eight detection rules have automated remediation: `CRON_ROOT_EXEC`, `CRON_TMP_PATH`, `LD_PRELOAD_INJECT`, `SSH_UNAUTHORIZED_KEY`, and `SUDOERS_NOPASSWD`. The remaining three (`SUSPICIOUS_SYSTEMD_TIMER`, `PROCESS_MASQUERADE`, and `SHELL_RC_MODIFICATION`) are flag-only and will never be auto-remediated.
|
|
457
|
+
|
|
458
|
+
### In the TUI
|
|
459
|
+
|
|
460
|
+
Select any finding with a remediation in the Findings tab, then press `R`. A confirmation modal previews the planned action. Press `Y` to apply it — the remediator runs in a background thread so the TUI stays responsive. The finding row updates to `[fixed]` when done, with the backup path and exact rollback command shown inline.
|
|
461
|
+
|
|
462
|
+
### From the CLI
|
|
463
|
+
|
|
464
|
+
`--remediate` without `--confirm` is a safe dry run: backups are created and validation runs, but no changes are applied. Pass both flags to actually make changes. The pipeline runs before the TUI launches in this mode.
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
sudo ubuntils scan --remediate # dry run
|
|
468
|
+
sudo ubuntils scan --remediate --confirm # apply changes, then open TUI
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Safeguards
|
|
472
|
+
|
|
473
|
+
Every remediation follows the same pattern regardless of how it is triggered:
|
|
474
|
+
|
|
475
|
+
1. Detect if the artifact path is a symlink — refuse if so (prevents root writing through attacker-controlled symlinks)
|
|
476
|
+
2. Create a timestamped backup at `/var/backups/ubuntils/YYYYMMDD_HHMMSS/` with mode `0700`
|
|
477
|
+
3. Validate current state (sudoers: `visudo -cf`)
|
|
478
|
+
4. Apply the minimum possible change — cron entries removed line by line, LD_PRELOAD lines commented out rather than deleted, sudoers entries validated with `visudo -cf` before and after
|
|
479
|
+
5. Verify the result
|
|
480
|
+
|
|
481
|
+
If any step fails, remediation stops immediately, the system is left unchanged, and the full error is reported with the backup path and rollback command. The sudoers remediator refuses to proceed if removing the entry would leave the system with no sudo rules.
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Collectors
|
|
486
|
+
|
|
487
|
+
| Collector | Artifacts gathered |
|
|
488
|
+
|---|---|
|
|
489
|
+
| ProcessCollector | Running processes from `/proc` and `ps` output |
|
|
490
|
+
| NetworkCollector | Open connections and listeners from `ss`/`netstat` |
|
|
491
|
+
| UserCollector | `/etc/passwd`, `/etc/shadow`, `/etc/group` |
|
|
492
|
+
| CronCollector | `/etc/cron*` directories and `/var/spool/cron/crontabs/*` |
|
|
493
|
+
| SystemdCollector | `systemctl list-timers` and `list-units` output |
|
|
494
|
+
| SSHCollector | `~/.ssh/authorized_keys` for all users |
|
|
495
|
+
| SudoersCollector | `/etc/sudoers` and all files under `/etc/sudoers.d/` |
|
|
496
|
+
| EnvironmentCollector | `/etc/environment`, `/etc/profile.d/*`, user shell init files |
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Compatibility
|
|
501
|
+
|
|
502
|
+
| | Supported |
|
|
503
|
+
|---|---|
|
|
504
|
+
| **Ubuntu** | 20.04, 22.04, 24.04 |
|
|
505
|
+
| **Architecture** | amd64, arm64 |
|
|
506
|
+
| **Python** | 3.9+ |
|
|
507
|
+
| **Privileges** | Root required for full artifact access |
|
|
508
|
+
|
|
509
|
+
Running without root produces a partial scan with warnings. Critical paths like `/etc/shadow`, protected crontab directories, and some `/proc` entries will be skipped.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Roadmap
|
|
514
|
+
|
|
515
|
+
**v1.0.0**
|
|
516
|
+
- [x] All 8 collectors
|
|
517
|
+
- [x] All 8 detection rules
|
|
518
|
+
- [x] Timeline builder (syslog, journald, auditd)
|
|
519
|
+
- [x] Live scan progress screen with per-collector ✓/✗
|
|
520
|
+
- [x] Interactive four-tab TUI (Summary / Findings / Timeline / Stats)
|
|
521
|
+
- [x] In-TUI remediation with confirmation modal and background worker
|
|
522
|
+
- [x] JSON output mode
|
|
523
|
+
- [x] CLI remediation for 5 rules with backup, rollback, and symlink guard
|
|
524
|
+
- [x] Ubuntu 20.04/22.04/24.04 support
|
|
525
|
+
- [x] 240 tests at 90% coverage
|
|
526
|
+
|
|
527
|
+
**v1.1.0**
|
|
528
|
+
- [x] False-positive allowlisting by rule id or path (`--config`)
|
|
529
|
+
- [x] `--output FILE` to write reports directly
|
|
530
|
+
- [x] `--since` timeline windowing
|
|
531
|
+
- [x] Tamper-evident reports (`report_sha256`, hostname, timestamp)
|
|
532
|
+
- [x] `USER_UID_ZERO` detection rule
|
|
533
|
+
|
|
534
|
+
**v1.5.0**
|
|
535
|
+
- VirusTotal hash lookups for suspicious process executables
|
|
536
|
+
- MISP IOC export from findings
|
|
537
|
+
- Custom detection rules (not just allowlists) via YAML
|
|
538
|
+
|
|
539
|
+
**v2.0.0**
|
|
540
|
+
- Web dashboard for multi-host triage
|
|
541
|
+
- Wazuh integration for alert forwarding
|
|
542
|
+
- macOS support
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Contributing
|
|
547
|
+
|
|
548
|
+
The most useful contributions right now are new detection rules (added as standalone functions in `detectors/rules.py` with a matching test), additional collectors for artifact types not yet covered, remediation modules for `SUSPICIOUS_SYSTEMD_TIMER` and `SHELL_RC_MODIFICATION` (both currently flag-only by design, but safe auto-remediation paths may exist), test cases for edge cases on specific Ubuntu configurations, and documentation improvements.
|
|
549
|
+
|
|
550
|
+
Open an issue before starting a large contribution to avoid duplicate work.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## License
|
|
555
|
+
|
|
556
|
+
MIT
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Author
|
|
561
|
+
|
|
562
|
+
Built by Asmit — BTech Computer Science, PES University, Bengaluru. The tool came out of frustration with how long manual Ubuntu triage takes compared to what a well-scoped script can automate.
|