Skip to content

Commit

Permalink
Updates, cleanup, fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
sscottgvit committed Nov 20, 2023
1 parent e27480c commit df4f0d7
Show file tree
Hide file tree
Showing 15 changed files with 118 additions and 69 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
LEGION 0.4.2

* Tweak the screenshooter to use eyewitness as suggested by daniruiz
* Add a Wsl check before running unixPath2Win
* Include Revision by daniruiz to tempPath creation routine
* Revise to monospaced font to improve readability as suggested by daniruiz
* Revise dependancies to resolve missing PhantomJs import
* Set log level to Info
* Eliminate some temporary code, debug lines, and other cleanup
* Revise screenshooter to use schema://ip:port when url is a single node
* Fix typo in startLegion.sh

LEGION 0.4.1

* Add checkXserver.sh to help users troubleshoot connections to X
Expand Down
6 changes: 3 additions & 3 deletions app/ApplicationInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

applicationInfo = {
"name": "LEGION",
"version": "0.4.1",
"build": '1699894526',
"version": "0.4.2",
"build": '1700506312',
"author": "Gotham Security",
"copyright": "2023",
"links": ["http://github.com/GoVanguard/legion/issues", "https://gotham-security.com/legion"],
"emails": [],
"update": '11/13/2023',
"update": '11/20/2023',
"license": "GPL v3",
"desc": "Legion is a fork of SECFORCE's Sparta, Legion is an open source, easy-to-use, \n" +
"super-extensible and semi-automated network penetration testing tool that aids in " +
Expand Down
52 changes: 37 additions & 15 deletions app/Screenshooter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
If not, see <http://www.gnu.org/licenses/>.
"""

import os

import warnings
warnings.filterwarnings("ignore", category=UserWarning)

from selenium import webdriver
from PyQt6 import QtCore

from app.logging.legionLog import getAppLogger
from app.http.isHttps import isHttps
from app.timing import getTimestamp
from app.auxiliary import isKali

logger = getAppLogger()

Expand Down Expand Up @@ -59,15 +61,8 @@ def run(self):
ip = queueItem[0]
port = queueItem[1]
url = queueItem[2]
self.tsLog("------> %s" % str(url))
outputfile = getTimestamp() + '-screenshot-' + url.replace(':', '-') + '.png'
#ip = url.split(':')[0]
#port = url.split(':')[1]

if isHttps(ip, port):
self.save("https://" + url, ip, port, outputfile)
else:
self.save("http://" + url, ip, port, outputfile)
self.save(url, ip, port, outputfile)

except Exception as e:
self.tsLog('Unable to take the screenshot. Error follows.')
Expand All @@ -80,10 +75,37 @@ def run(self):
self.run()

def save(self, url, ip, port, outputfile):
self.tsLog('Saving screenshot as: ' + str(outputfile))
driver = webdriver.PhantomJS(executable_path="/usr/bin/phantomjs")
driver.set_window_size(1280, 1024)
driver.get(url)
driver.save_screenshot("{0}/{1}".format(self.outputfolder, outputfile))
driver.quit()
# Handle single node URI case by pivot to IP
if len(str(url).split('.')) == 1:
url = '{0}:{1}'.format(str(ip), str(port))

if isHttps(ip, port):
url = 'https://{0}'.format(url)
else:
url = 'http://{0}'.format(url)

self.tsLog('Taking Screenshot of: {0}'.format(str(url)))

# Use eyewitness under Kali. Use webdriver is not Kali. Once eyewitness is more boradly available, the conter case can be eliminated.
if isKali():
import tempfile
import subprocess

tmpOutputfolder = tempfile.mkdtemp(dir=self.outputfolder)
command = ('xvfb-run --server-args="-screen 0:0, 1024x768x24" /usr/bin/eyewitness --single "{url}/"'
' --no-prompt -d "{outputfolder}"') \
.format(url=url, outputfolder=tmpOutputfolder)
p = subprocess.Popen(command, shell=True)
p.wait() # wait for command to finish
fileName = os.listdir(tmpOutputfolder + '/screens/')[0]
outputfile = tmpOutputfolder.removeprefix(self.outputfolder) + '/screens/' + fileName
else:
from selenium import webdriver

driver = webdriver.PhantomJS(executable_path='/usr/bin/phantomjs')
driver.set_window_size(1280, 1024)
driver.get(url)
driver.save_screenshot('{0}/{1}'.format(self.outputfolder, outputfile))
driver.quit()
self.tsLog('Saving screenshot as: {0}'.format(str(outputfile)))
self.done.emit(ip, port, outputfile) # send a signal to add the 'process' to the DB
14 changes: 9 additions & 5 deletions app/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def winPath2Unix(windowsPath):
windowsPath = windowsPath.replace("C:", "/mnt/c")
return windowsPath


# Convert Posix path to Windows
def unixPath2Win(posixPath):
posixPath = posixPath.replace("/", "\\")
Expand All @@ -49,12 +48,17 @@ def isWsl():
release = str(platform.uname().release).lower()
return "microsoft" in release

# Check if running in Kali
def isKali():
release = str(platform.uname().release).lower()
return "kali" in release

# Get the AppData Temp directory path if WSL
def getAppdataTemp():
try:
username = os.environ["WSL_USER_NAME"]
except KeyError:
raise Exception("WSL detected but environment variable 'WSL_USER_NAME' is unset.")
raise Exception("WSL detected but environment variable 'WSL_USER_NAME' is unset. Please run 'export WSL_USER_NAME=' followed by your username as it appears in c:\\Users\\")

appDataTemp = "C:\\Users\\{0}\\AppData\\Local\\Temp".format(username)
appDataTempUnix = winPath2Unix(appDataTemp)
Expand All @@ -74,9 +78,9 @@ def getTempFolder():
os.makedirs(tempPath)
log.info("WSL is detected. The AppData Temp directory path is {0} ({1})".format(tempPath, tempPathWin))
else:
tempPath = "~/.local/share/legion/tmp"
if not os.path.isdir(os.path.expanduser(tempPath)):
os.makedirs(os.path.expanduser(tempPath))
tempPath = os.path.expanduser("~/.local/share/legion/tmp")
if not os.path.isdir(tempPath):
os.makedirs(tempPath)
log.info("Non-WSL The AppData Temp directory path is {0}".format(tempPath))
return tempPath

Expand Down
28 changes: 9 additions & 19 deletions app/importers/NmapImporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,13 @@ def run(self):
s = p.getService()

if not (s is None): # check if service already exists to avoid adding duplicates
print("Processing service result *********** name={0} prod={1} ver={2} extra={3} fing={4}"
self.tsLog(" Processing service result *********** name={0} prod={1} ver={2} extra={3} fing={4}"
.format(s.name, s.product, s.version, s.extrainfo, s.fingerprint))
db_service = session.query(serviceObj).filter_by(hostId=db_host.id) \
.filter_by(name=s.name).filter_by(product=s.product).filter_by(version=s.version) \
.filter_by(extrainfo=s.extrainfo).filter_by(fingerprint=s.fingerprint).first()
if not db_service:
print("Did not find service *********** name={0} prod={1} ver={2} extra={3} fing={4}"
self.tsLog(" Did not find service *********** name={0} prod={1} ver={2} extra={3} fing={4}"
.format(s.name, s.product, s.version, s.extrainfo, s.fingerprint))
db_service = serviceObj(s.name, db_host.id, s.product, s.version, s.extrainfo,
s.fingerprint)
Expand All @@ -171,34 +171,30 @@ def run(self):
.filter_by(protocol=p.protocol).first()

if not db_port:
# print("Did not find port *********** portid={0} proto={1}".format(p.portId, p.protocol))
self.tsLog(" Did not find port *********** portid={0} proto={1}".format(p.portId, p.protocol))
if db_service:
db_port = portObj(p.portId, p.protocol, p.state, db_host.id, db_service.id)
else:
db_port = portObj(p.portId, p.protocol, p.state, db_host.id, '')
session.add(db_port)
# else:
# print('FOUND port *************** portid={0}'.format(db_port.portId))
createPortsProgress = createPortsProgress + ((100.0 / hostCount) / 5)
totalprogress = totalprogress + createPortsProgress
self.updateProgressObservable.updateProgress(totalprogress)

session.commit()

# totalprogress += progress
# self.tick.emit(int(totalprogress))

for h in allHosts: # create all script objects that need to be created
db_host = self.hostRepository.getHostInformation(h.ip)

for p in h.all_ports():
for scr in p.getScripts():
self.tsLog(" Processing script obj {scr}".format(scr=str(scr)))
print(" Processing script obj {scr}".format(scr=str(scr)))
db_port = session.query(portObj).filter_by(hostId=db_host.id) \
.filter_by(portId=p.portId).filter_by(protocol=p.protocol).first()
#db_script = session.query(l1ScriptObj).filter_by(scriptId=scr.scriptId) \
# .filter_by(portId=db_port.id).first()
# Todo
db_script = session.query(l1ScriptObj).filter_by(scriptId=scr.scriptId) \
.filter_by(portId=db_port.id).first()
# end todo
db_script = session.query(l1ScriptObj).filter_by(hostId=db_host.id) \
.filter_by(portId=db_port.id).first()

Expand Down Expand Up @@ -271,14 +267,14 @@ def run(self):
session.add(db_host)

for scr in h.getHostScripts():
print("-----------------------Host SCR: {0}".format(scr.scriptId))
self.tsLog("-----------------------Host SCR: {0}".format(scr.scriptId))
db_host = self.hostRepository.getHostInformation(h.ip)
scrProcessorResults = scr.scriptSelector(db_host)
for scrProcessorResult in scrProcessorResults:
session.add(scrProcessorResult)

for scr in h.getScripts():
print("-----------------------SCR: {0}".format(scr.scriptId))
self.tsLog("-----------------------SCR: {0}".format(scr.scriptId))
db_host = self.hostRepository.getHostInformation(h.ip)
scrProcessorResults = scr.scriptSelector(db_host)
for scrProcessorResult in scrProcessorResults:
Expand All @@ -291,25 +287,19 @@ def run(self):
.filter_by(name=s.name).filter_by(product=s.product) \
.filter_by(version=s.version).filter_by(extrainfo=s.extrainfo) \
.filter_by(fingerprint=s.fingerprint).first()
#db_service = session.query(serviceObj).filter_by(hostId=db_host.id) \
# .filter_by(name=s.name).first()
else:
db_service = None
# fetch the port
db_port = session.query(portObj).filter_by(hostId=db_host.id).filter_by(portId=p.portId) \
.filter_by(protocol=p.protocol).first()
if db_port:
# print("************************ Found {0}".format(db_port))

if db_port.state != p.state:
db_port.state = p.state
session.add(db_port)

# if there is some new service information, update it -- might be causing issue 164
if not (db_service is None) and db_port.serviceId != db_service.id:
db_port.serviceId = db_service.id
session.add(db_port)

# store the script results (note that existing script outputs are also kept)
for scr in p.getScripts():
db_script = session.query(l1ScriptObj).filter_by(scriptId=scr.scriptId) \
Expand Down
4 changes: 2 additions & 2 deletions app/logging/legionLog.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ def getOrCreateCachedLogger(logName: str, logPath: str, console: bool, cachedLog
from rich.logging import RichHandler

logging.basicConfig(
level="NOTSET",
level="WARN",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)

log = logging.getLogger("rich")
log.setLevel(logging.DEBUG)
log.setLevel(logging.INFO)
return log
30 changes: 21 additions & 9 deletions controller/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from app.importers.NmapImporter import NmapImporter
from app.importers.PythonImporter import PythonImporter
from app.tools.nmap.NmapPaths import getNmapRunningFolder
from app.auxiliary import unixPath2Win, winPath2Unix, getPid, formatCommandQProcess
from app.auxiliary import unixPath2Win, winPath2Unix, getPid, formatCommandQProcess, isWsl
from ui.observers.QtUpdateProgressObserver import QtUpdateProgressObserver

try:
Expand Down Expand Up @@ -110,11 +110,13 @@ def initBrowserOpener(self):
def initTimers(self):
self.updateUITimer = QTimer()
self.updateUITimer.setSingleShot(True)
# Moving to deprecate all these general interface update timers
#self.updateUITimer.timeout.connect(self.view.updateProcessesTableView)
#self.updateUITimer.timeout.connect(self.view.updateToolsTableView)

self.updateUI2Timer = QTimer()
self.updateUI2Timer.setSingleShot(True)
# Moving to deprecate all these general interface update timers
#self.updateUI2Timer.timeout.connect(self.view.updateInterface)

self.processTableUiUpdateTimer = QTimer()
Expand Down Expand Up @@ -247,20 +249,23 @@ def addHosts(self, targetHosts, runHostDiscovery, runStagedNmap, nmapSpeed, scan
self.runStagedNmap(targetHosts, runHostDiscovery)
elif runHostDiscovery:
outputfile = getNmapRunningFolder(runningFolder) + "/" + getTimestamp() + '-host-discover'
outputfile = unixPath2Win(outputfile)
if isWsl():
outputfile = unixPath2Win(outputfile)
command = f"nmap -n -sV -O --version-light -T{str(nmapSpeed)} {targetHosts} -oA {outputfile}"
self.runCommand('nmap', 'nmap (discovery)', targetHosts, '', '', command, getTimestamp(True),
outputfile, self.view.createNewTabForHost(str(targetHosts), 'nmap (discovery)', True))
else:
outputfile = getNmapRunningFolder(runningFolder) + "/" + getTimestamp() + '-nmap-list'
outputfile = unixPath2Win(outputfile)
if isWsl():
outputfile = unixPath2Win(outputfile)
command = "nmap -n -sL -T" + str(nmapSpeed) + " " + targetHosts + " -oA " + outputfile
self.runCommand('nmap', 'nmap (list)', targetHosts, '', '', command, getTimestamp(True),
outputfile,
self.view.createNewTabForHost(str(targetHosts), 'nmap (list)', True))
elif scanMode == 'Hard':
outputfile = getNmapRunningFolder(runningFolder) + "/" + getTimestamp() + '-nmap-custom'
outputfile = unixPath2Win(outputfile)
if isWsl():
outputfile = unixPath2Win(outputfile)
nmapOptionsString = ' '.join(nmapOptions)
if 'randomize' not in nmapOptionsString:
nmapOptionsString = nmapOptionsString + " -T" + str(nmapSpeed)
Expand Down Expand Up @@ -363,7 +368,10 @@ def handleHostAction(self, ip, hostid, actions, action):
command = str(self.settings.hostActions[i][2])
command = command.replace('[IP]', ip).replace('[OUTPUT]', outputfile)
if 'nmap' in command:
command = "{0} -oA {1}".format(command, unixPath2Win(outputfile))
if isWsl():
command = "{0} -oA {1}".format(command, unixPath2Win(outputfile))
else:
command = "{0} -oA {1}".format(command, outputfile)

# check if same type of nmap scan has already been made and purge results before scanning
if 'nmap' in command:
Expand Down Expand Up @@ -434,7 +442,10 @@ def handleServiceNameAction(self, targets, actions, action, restoring=True):
command = str(self.settings.portActions[srvc_num][2])
command = command.replace('[IP]', ip[0]).replace('[PORT]', ip[1]).replace('[OUTPUT]', outputfile)
if 'nmap' in command:
command = "{0} -oA {1}".format(command, unixPath2Win(outputfile))
if isWsl():
command = "{0} -oA {1}".format(command, unixPath2Win(outputfile))
else:
command = "{0} -oA {1}".format(command, outputfile)

if 'nmap' in command and ip[2] == 'udp':
command = command.replace("-sV", "-sVU")
Expand Down Expand Up @@ -701,8 +712,8 @@ def handleProcUpdate(*vargs):
qProcess.readyReadStandardOutput.connect(lambda: qProcess.display.appendPlainText(
str(qProcess.readAllStandardOutput().data().decode('ISO-8859-1'))))

qProcess.readyReadStandardError.connect(lambda: qProcess.display.appendPlainText(
str(qProcess.readAllStandardError().data().decode('ISO-8859-1'))))
#qProcess.readyReadStandardError.connect(lambda: qProcess.display.appendPlainText(
# str(qProcess.readAllStandardError().data().decode('ISO-8859-1'))))

qProcess.sigHydra.connect(self.handleHydraFindings)
qProcess.finished.connect(lambda: self.processFinished(qProcess))
Expand Down Expand Up @@ -761,7 +772,8 @@ def runStagedNmap(self, targetHosts, discovery = True, stage = 1, stop = False):
if not stop:
textbox = self.view.createNewTabForHost(str(targetHosts), 'nmap (stage ' + str(stage) + ')', True)
outputfile = getNmapRunningFolder(runningFolder) + "/" + getTimestamp() + '-nmapstage' + str(stage)
outputfile = unixPath2Win(outputfile)
if isWsl():
outputfile = unixPath2Win(outputfile)

if stage == 1:
stageData = self.settings.tools_nmap_stage1_ports
Expand Down
14 changes: 14 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ legion (0.4.1-0) UNRELEASED; urgency=medium
* Fix a few missing dependencies

-- Shane Scott <sscot@gotham-security.com> Thur, 13 Nov 2023 10:54:55 -0600

legion (0.4.2-0) UNRELEASED; urgency=medium

* Tweak the screenshooter to use eyewitness as suggested by daniruiz
* Add a Wsl check before running unixPath2Win
* Include Revision by daniruiz to tempPath creation routine
* Revise to monospaced font to improve readability as suggested by daniruiz
* Revise dependancies to resolve missing PhantomJs import
* Set log level to Info
* Eliminate some temporary code, debug lines, and other cleanup
* Revise screenshooter to use schema://ip:port when url is a single node
* Fix typo in startLegion.sh

-- Shane Scott <sscot@gotham-security.com> Mon, 20 Nov 2023 12:50:55 -0600
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Priority: optional
Maintainer: GoVanguard <hello@gotham-security.com>
Uploaders: Shane Scott <sscott@gotham-security.com>
Build-Depends: debhelper, python3, python3-requests
Standards-Version: 0.4.1
Standards-Version: 0.4.2
Homepage: https://github.com/GoVanguard/Legion

Package: legion
Expand Down
Loading

0 comments on commit df4f0d7

Please sign in to comment.