Skip to content

Commit c82ab51

Browse files
committed
update Legba module, add tests
1 parent b1600da commit c82ab51

File tree

2 files changed

+117
-9
lines changed

2 files changed

+117
-9
lines changed

bbot/modules/deadly/legba.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,34 +57,62 @@ class legba(BaseModule):
5757
{
5858
"name": "Install dev tools (Debian)",
5959
"package": {
60-
"name": ["libssl-dev", "libsmbclient-dev", "pkg-config", "cmake"],
60+
"name": ["pkg-config", "cmake", "libclang-dev", "clang"],
6161
"state": "present",
6262
},
6363
"become": True,
64-
"when": "ansible_facts['distribution'] == 'Debian'",
64+
"when": "ansible_facts['os_family'] == 'Debian'",
65+
"ignore_errors": True,
66+
},
67+
{
68+
"name": "Install dev tools (Fedora)",
69+
"package": {
70+
"name": ["pkgconf-pkg-config", "cmake", "clang-devel", "llvm-devel", "perl-core"],
71+
"state": "present",
72+
},
73+
"become": True,
74+
"when": "ansible_facts['os_family'] == 'RedHat'",
75+
"ignore_errors": True,
76+
},
77+
{
78+
"name": "Install dev tools (Arch)",
79+
"package": {
80+
"name": ["pkgconf", "cmake", "clang", "openssl"],
81+
"state": "present",
82+
},
83+
"become": True,
84+
"when": "ansible_facts['os_family'] == 'Archlinux'",
6585
"ignore_errors": True,
6686
},
6787
{
6888
"name": "Get legba repo",
6989
"git": {
7090
"repo": "https://github.com/evilsocket/legba",
71-
"dest": "#{BBOT_TEMP}/legba",
72-
"version": "v0.11.0", # Newest stable, 2025-07-18
91+
"dest": "#{BBOT_TEMP}/legba/gitrepo",
92+
"version": "1.1.1", # Newest stable, 2025-08-25
93+
},
94+
},
95+
{
96+
# The git repo will be copied because during build, files and subfolders get created. That prevents the Ansible git module to cache the repo.
97+
"name": "Copy legba repo",
98+
"copy": {
99+
"src": "#{BBOT_TEMP}/legba/gitrepo/",
100+
"dest": "#{BBOT_TEMP}/legba/workdir/",
73101
},
74102
},
75103
{
76104
"name": "Build legba",
77105
"command": {
78-
"chdir": "#{BBOT_TEMP}/legba",
79-
"cmd": "cargo build --release --features http_relative_paths",
80-
"creates": "#{BBOT_TEMP}/legba/target/release/legba",
106+
"chdir": "#{BBOT_TEMP}/legba/workdir",
107+
"cmd": "cargo build --release",
108+
"creates": "#{BBOT_TEMP}/legba/workdir/target/release/legba",
81109
},
82-
"environment": {"PATH": "{{ ansible_env.PATH }}:{{ ansible_env.HOME }}/.cargo/bin", "RUST_BACKTRACE": "1"},
110+
"environment": {"PATH": "{{ ansible_env.PATH }}:{{ ansible_env.HOME }}/.cargo/bin"},
83111
},
84112
{
85113
"name": "Install legba",
86114
"copy": {
87-
"src": "#{BBOT_TEMP}/legba/target/release/legba",
115+
"src": "#{BBOT_TEMP}/legba/workdir/target/release/legba",
88116
"dest": "#{BBOT_TOOLS}/",
89117
"mode": "u+x,g+x,o+x",
90118
},
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from pathlib import Path
2+
from .base import ModuleTestBase, tempwordlist
3+
import pytest
4+
5+
6+
@pytest.fixture(params=["ssh", "ftp"])
7+
def protocol(request):
8+
return request.param
9+
10+
11+
@pytest.fixture
12+
def mock_legba_run_process(monkeypatch, request):
13+
async def fake_run_process(self, cmd):
14+
try:
15+
# find index of `--output` in cmd
16+
output_index = cmd.index("--output")
17+
# output_path is directly after `--output` in cmd
18+
output_path = Path(cmd[output_index + 1])
19+
except Exception as e:
20+
raise Exception(f"Could not determine output file path from command {cmd}: {e}")
21+
22+
protocol = request.getfixturevalue("protocol")
23+
24+
expected_file_content_per_protocol = {
25+
"ssh": '{"found_at":"2025-07-22T20:50:19.541305293+02:00","target":"127.0.0.1:2222","plugin":"ssh","data":{"username":"remnux","password":"malware"},"partial":false}',
26+
"ftp": '{"found_at":"2025-07-22T20:51:19.541305293+02:00","target":"127.0.0.1:21","plugin":"ftp","data":{"username":"ftp_boot","password":"ftp_boot"},"partial":false}',
27+
}
28+
29+
output_path.write_text(expected_file_content_per_protocol[protocol])
30+
31+
from bbot.modules.base import BaseModule
32+
33+
monkeypatch.setattr(BaseModule, "run_process", fake_run_process)
34+
35+
36+
@pytest.mark.usefixtures("mock_legba_run_process")
37+
class TestLegba(ModuleTestBase):
38+
targets = ["127.0.0.1"]
39+
40+
temp_ssh_wordlist = tempwordlist(["test:test", "admin:admin", "admin:password", "remnux:malware", "user:pass"])
41+
temp_ftp_wordlist = tempwordlist(["test:test", "ftp_boot:ftp_boot", "admin:password", "root:root", "user:pass"])
42+
43+
config_overrides = {
44+
"modules": {
45+
"legba": {
46+
"ssh_wordlist": str(temp_ssh_wordlist),
47+
"ftp_wordlist": str(temp_ftp_wordlist),
48+
}
49+
}
50+
}
51+
52+
@pytest.fixture(autouse=True)
53+
def _protocol_dependency(self, protocol):
54+
# ensure pytest sees dependency and runs one test per protocol
55+
self._protocol = protocol
56+
57+
async def setup_after_prep(self, module_test):
58+
protocol = module_test.request_fixture.getfixturevalue("protocol")
59+
ports = {"ssh": 2222, "ftp": 21}
60+
event_data = {"host": str(self.targets[0]), "protocol": protocol.upper(), "port": ports[protocol]}
61+
protocol_event = module_test.scan.make_event(
62+
event_data,
63+
"PROTOCOL",
64+
parent=module_test.scan.root_event,
65+
)
66+
67+
await module_test.module.emit_event(protocol_event)
68+
69+
def check(self, module_test, events):
70+
protocol = module_test.request_fixture.getfixturevalue("protocol")
71+
vuln_events = [e for e in events if e.type == "VULNERABILITY"]
72+
73+
assert len(vuln_events) == 1
74+
75+
expected_desc = {
76+
"ssh": "Valid ssh credentials found - remnux:malware",
77+
"ftp": "Valid ftp credentials found - ftp_boot:ftp_boot",
78+
}
79+
80+
assert expected_desc[protocol] in vuln_events[0].data["description"]

0 commit comments

Comments
 (0)