v2.3
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['windows_audit.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('audit_cis_windows.ps1', '.')],
|
||||
hiddenimports=['Crypto.Cipher', 'Crypto.PublicKey', 'Crypto.Util.Padding'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='AuditTool',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,442 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
import base64, os, socket, subprocess, tempfile, sys, binascii
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
# ---- Compatibility for CentOS 6 (Py2.6/2.7) ----
|
||||
# Safe print (UTF-8, hỗ trợ stream=file)
|
||||
def _p(msg, color=None, stream=None, **kwargs):
|
||||
# Back-compat cho calls kiểu file=...
|
||||
if stream is None and 'file' in kwargs:
|
||||
stream = kwargs['file']
|
||||
if stream is None:
|
||||
stream = sys.stdout
|
||||
try:
|
||||
unicode # Py2 check
|
||||
b = msg.encode('utf-8') if isinstance(msg, unicode) else msg
|
||||
except NameError:
|
||||
b = msg
|
||||
try:
|
||||
if color:
|
||||
stream.write(color)
|
||||
stream.write(b)
|
||||
if color:
|
||||
try:
|
||||
stream.write(Fore.RESET)
|
||||
except Exception:
|
||||
pass
|
||||
stream.write("\n")
|
||||
except Exception:
|
||||
try:
|
||||
stream.write((u'' + msg).encode('utf-8') + "\n")
|
||||
except Exception:
|
||||
sys.stdout.write((u'' + msg).encode('utf-8') + "\n")
|
||||
|
||||
# optparse (có sẵn trên Py2.6)
|
||||
try:
|
||||
from optparse import OptionParser
|
||||
except Exception:
|
||||
OptionParser = None # fallback rất cũ
|
||||
|
||||
# Color handling (tùy chọn)
|
||||
try:
|
||||
from colorama import Fore, init as colorama_init
|
||||
colorama_init()
|
||||
except Exception:
|
||||
class _NoColor(object):
|
||||
def __getattr__(self, name): return ''
|
||||
Fore = _NoColor()
|
||||
|
||||
BUNDLED_SCRIPT = "audit_cis_centos6.sh"
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
if getattr(sys, "frozen", False):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)
|
||||
|
||||
|
||||
def resolve_script_path(user_path=None):
|
||||
if user_path and os.path.isfile(user_path):
|
||||
return os.path.abspath(user_path)
|
||||
bundled = resource_path(BUNDLED_SCRIPT)
|
||||
if os.path.isfile(bundled):
|
||||
return bundled
|
||||
same_dir = os.path.join(os.getcwd(), BUNDLED_SCRIPT)
|
||||
if os.path.isfile(same_dir):
|
||||
return same_dir
|
||||
return None
|
||||
|
||||
|
||||
def detect_os_name():
|
||||
"""
|
||||
Trả về chuỗi tên HĐH ngắn gọn.
|
||||
Ưu tiên /etc/*-release; fallback sang uname -srm.
|
||||
"""
|
||||
paths = [
|
||||
'/etc/centos-release',
|
||||
'/etc/redhat-release',
|
||||
'/etc/os-release', # có trên bản mới hơn
|
||||
'/etc/lsb-release', # có thể chứa DISTRIB_DESCRIPTION=
|
||||
]
|
||||
for p in paths:
|
||||
try:
|
||||
if os.path.isfile(p):
|
||||
data = open(p, 'rb').read()
|
||||
try:
|
||||
text = data.decode('utf-8', 'ignore')
|
||||
except Exception:
|
||||
text = data
|
||||
# /etc/lsb-release
|
||||
if 'DISTRIB_DESCRIPTION=' in text:
|
||||
for line in text.splitlines():
|
||||
if line.startswith('DISTRIB_DESCRIPTION='):
|
||||
val = line.split('=',1)[1].strip().strip('"')
|
||||
return val
|
||||
# các file *release khác: dùng dòng đầu
|
||||
first = text.splitlines()[0].strip()
|
||||
if first:
|
||||
return first
|
||||
except Exception:
|
||||
pass
|
||||
# Fallback: uname
|
||||
try:
|
||||
out = subprocess.Popen("uname -srm", shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
|
||||
try:
|
||||
return out.decode('utf-8','ignore').strip()
|
||||
except Exception:
|
||||
return out.strip()
|
||||
except Exception:
|
||||
return 'Unknown-OS'
|
||||
|
||||
try:
|
||||
hostname = socket.gethostname()
|
||||
IPAddr = socket.gethostbyname(hostname)
|
||||
except Exception:
|
||||
hostname = 'unknown-host'
|
||||
IPAddr = '127.0.0.1'
|
||||
|
||||
cwd = os.getcwd()
|
||||
|
||||
|
||||
def pkcs7_unpad(data, block_size=16):
|
||||
if not data:
|
||||
return data
|
||||
pad_len = ord(data[-1]) if isinstance(data[-1], str) else data[-1]
|
||||
if pad_len < 1 or pad_len > block_size:
|
||||
# Defensive: return as-is to avoid exception storms in Py2 envs
|
||||
return data
|
||||
return data[:-pad_len]
|
||||
|
||||
|
||||
def write_content(content, use_oaep=True):
|
||||
"""
|
||||
Hybrid AES-256-CBC + RSA encryption using OpenSSL CLI.
|
||||
Only 1 RSA operation for the AES key; bulk data encrypted with fast AES.
|
||||
Output: HYBRID_V1:<b64_enc_key>:<b64_iv>:<b64_ciphertext>
|
||||
"""
|
||||
import binascii
|
||||
|
||||
pubkey_path = os.path.join(os.getcwd(), 'public_key.pem')
|
||||
if not os.path.isfile(pubkey_path):
|
||||
raise RuntimeError("public_key.pem khong ton tai tai: %s" % pubkey_path)
|
||||
|
||||
try:
|
||||
unicode
|
||||
if isinstance(content, unicode):
|
||||
message = content.encode('utf-8')
|
||||
else:
|
||||
message = content
|
||||
except NameError:
|
||||
if isinstance(content, str):
|
||||
message = content.encode('utf-8')
|
||||
else:
|
||||
message = content
|
||||
|
||||
aes_key = os.urandom(32)
|
||||
iv = os.urandom(16)
|
||||
|
||||
fd_in, path_in = tempfile.mkstemp(prefix='aes_in_', dir='/tmp')
|
||||
fd_out, path_out = tempfile.mkstemp(prefix='aes_out_', dir='/tmp')
|
||||
os.close(fd_in)
|
||||
os.close(fd_out)
|
||||
|
||||
try:
|
||||
with open(path_in, 'wb') as f:
|
||||
f.write(message)
|
||||
|
||||
key_hex = binascii.hexlify(aes_key)
|
||||
iv_hex = binascii.hexlify(iv)
|
||||
|
||||
cmd_aes = [
|
||||
'/usr/bin/openssl', 'enc', '-aes-256-cbc',
|
||||
'-K', key_hex, '-iv', iv_hex,
|
||||
'-in', path_in, '-out', path_out
|
||||
]
|
||||
p = subprocess.Popen(cmd_aes, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError("OpenSSL AES encrypt error (rc=%d): %s" % (p.returncode, err))
|
||||
|
||||
with open(path_out, 'rb') as f:
|
||||
ciphertext = f.read()
|
||||
finally:
|
||||
try: os.remove(path_in)
|
||||
except: pass
|
||||
try: os.remove(path_out)
|
||||
except: pass
|
||||
|
||||
fd_key_in, path_key_in = tempfile.mkstemp(prefix='rsa_key_in_', dir='/tmp')
|
||||
fd_key_out, path_key_out = tempfile.mkstemp(prefix='rsa_key_out_', dir='/tmp')
|
||||
os.close(fd_key_in)
|
||||
os.close(fd_key_out)
|
||||
|
||||
try:
|
||||
with open(path_key_in, 'wb') as f:
|
||||
f.write(aes_key)
|
||||
|
||||
cmd_rsa = [
|
||||
'/usr/bin/openssl', 'rsautl', '-encrypt',
|
||||
'-pubin', '-inkey', pubkey_path,
|
||||
'-in', path_key_in, '-out', path_key_out
|
||||
]
|
||||
if use_oaep:
|
||||
cmd_rsa.insert(3, '-oaep')
|
||||
else:
|
||||
cmd_rsa.insert(3, '-pkcs')
|
||||
|
||||
p = subprocess.Popen(cmd_rsa, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError("OpenSSL rsautl error (rc=%d): %s" % (p.returncode, err))
|
||||
|
||||
with open(path_key_out, 'rb') as f:
|
||||
enc_key = f.read()
|
||||
finally:
|
||||
try: os.remove(path_key_in)
|
||||
except: pass
|
||||
try: os.remove(path_key_out)
|
||||
except: pass
|
||||
|
||||
b64_enc_key = base64.b64encode(enc_key)
|
||||
b64_iv = base64.b64encode(iv)
|
||||
b64_ct = base64.b64encode(ciphertext)
|
||||
|
||||
return b"HYBRID_V1:" + b64_enc_key + b":" + b64_iv + b":" + b64_ct
|
||||
|
||||
|
||||
def _key_b64_to_hex(key_b64):
|
||||
key = base64.b64decode(key_b64) # 16/24/32 bytes
|
||||
try:
|
||||
# Py2 cách cũ
|
||||
return key.encode('hex')
|
||||
except Exception:
|
||||
# fallback portable
|
||||
return binascii.b2a_hex(key)
|
||||
|
||||
|
||||
def _key_b64_to_hex_and_cipher(key_b64):
|
||||
"""
|
||||
Base64 -> bytes -> (hex_key, cipher_name) theo độ dài key:
|
||||
16B -> aes-128-ecb, 24B -> aes-192-ecb, 32B -> aes-256-ecb
|
||||
"""
|
||||
key = base64.b64decode(key_b64)
|
||||
klen = len(key)
|
||||
if klen == 16:
|
||||
cipher = 'aes-128-ecb'
|
||||
elif klen == 24:
|
||||
cipher = 'aes-192-ecb'
|
||||
elif klen == 32:
|
||||
cipher = 'aes-256-ecb'
|
||||
else:
|
||||
raise RuntimeError("Độ dài key không hợp lệ: %d bytes (cần 16/24/32)" % klen)
|
||||
# Py2: trả về hex dạng str
|
||||
try:
|
||||
hexkey = key.encode('hex')
|
||||
except Exception:
|
||||
hexkey = binascii.b2a_hex(key)
|
||||
return hexkey, cipher
|
||||
|
||||
|
||||
def run_bash(script_bytes):
|
||||
"""Run bash script passed as BYTES via stdin; returns stdout bytes (Py2-safe)."""
|
||||
if isinstance(script_bytes, unicode):
|
||||
script_bytes = script_bytes.encode('utf-8')
|
||||
p = subprocess.Popen("/bin/bash -s", shell=True,
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = p.communicate(script_bytes)
|
||||
if p.returncode != 0:
|
||||
# Ghi stderr vào cuối output để tiện debug nhưng không làm hỏng bytes
|
||||
out = (out or b"") + b"\n===== STDERR =====\n" + (err or b"")
|
||||
return out
|
||||
|
||||
|
||||
ANSI_RE = re.compile(r'\x1b\[[0-9;]*[A-Za-z]')
|
||||
|
||||
|
||||
def strip_ansi(s):
|
||||
try:
|
||||
# Py2: s có thể là bytes; cứ để bytes, regex vẫn xử được
|
||||
return ANSI_RE.sub('', s)
|
||||
except Exception:
|
||||
return s
|
||||
|
||||
|
||||
def safe_slug(s, maxlen=80):
|
||||
# Chuyển sang ASCII-safe cho tên file: thay khoảng trắng/ký tự lạ bằng _
|
||||
if isinstance(s, unicode):
|
||||
s = s.encode('utf-8')
|
||||
# bỏ ANSI, rồi thay ký tự không an toàn
|
||||
s = strip_ansi(s)
|
||||
s = re.sub(r'[^\w\.-]+', '_', s) # chỉ giữ a-zA-Z0-9_ . -
|
||||
s = s.strip('_')
|
||||
if len(s) > maxlen:
|
||||
s = s[:maxlen]
|
||||
return s
|
||||
|
||||
|
||||
def extract_host_time(text):
|
||||
"""
|
||||
Tìm Hostname và Time bằng regex, không phụ thuộc thứ tự dòng.
|
||||
Kỳ vọng trong output có dạng:
|
||||
Hostname: myhost
|
||||
Time: 2025-11-06 13:00:00
|
||||
"""
|
||||
t = strip_ansi(text)
|
||||
# Tách dòng để tìm
|
||||
lines = t.splitlines()
|
||||
hostname_out, time_out = None, None
|
||||
|
||||
# Regex linh hoạt (bỏ qua hoa/thường, khoảng trắng)
|
||||
re_host = re.compile(r'^\s*Hostname:\s*(\S+)\s*$', re.I)
|
||||
re_time = re.compile(r'^\s*Time:\s*(.+?)\s*$', re.I)
|
||||
|
||||
for line in lines:
|
||||
if hostname_out is None:
|
||||
m = re_host.match(line)
|
||||
if m:
|
||||
hostname_out = m.group(1)
|
||||
if time_out is None:
|
||||
m = re_time.match(line)
|
||||
if m:
|
||||
time_out = m.group(1)
|
||||
if hostname_out and time_out:
|
||||
break
|
||||
|
||||
if not hostname_out:
|
||||
# fallback an toàn
|
||||
try:
|
||||
hostname_out = socket.gethostname()
|
||||
except Exception:
|
||||
hostname_out = 'unknown-host'
|
||||
|
||||
if not time_out:
|
||||
# fallback: now
|
||||
try:
|
||||
time_out = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
except Exception:
|
||||
time_out = 'unknown-time'
|
||||
|
||||
# Chuẩn hoá
|
||||
hostname_out = safe_slug(hostname_out, maxlen=80)
|
||||
# format lại time sang safe filename
|
||||
time_safe = time_out.replace('/', '-').replace('\\', '-').replace(':', '_').replace(' ', '-')
|
||||
time_safe = safe_slug(time_safe, maxlen=80)
|
||||
|
||||
return hostname_out, time_safe
|
||||
|
||||
|
||||
def run_audit(file_path=None):
|
||||
# Banner
|
||||
print(Fore.BLUE + """
|
||||
_ _ _____ _____ _______ _ _ _____ _____ ______ _ _ _____ _ _ _____
|
||||
/\\ | | | | __ \\_ _|__ __| | | | | /\\ | __ \\| __ \\| ____| \\ | |_ _| \\ | |/ ____|
|
||||
/ \\ | | | | | | || | | | ______ | |__| | / \\ | |__) | | | | |__ | \\| | | | | \\| | | __
|
||||
/ /\\ \\| | | | | | || | | | |______| | __ | / /\\ \\ | _ /| | | | __| | . ` | | | | . ` | | |_ |
|
||||
/ ____ \\ |__| | |__| || |_ | | | | | |/ ____ \\| | \\ \\| |__| | |____| |\\ |_| |_| |\\ | |__| |
|
||||
/_/ \\_\\____/|_____/_____| |_| |_| |_/_/ \\_\\_| \\_\\_____/|______|_| \\_|_____|_| \\_|\\_____|
|
||||
|
||||
""" + Fore.RESET)
|
||||
print(Fore.RED + "Running Audit Hardening..." + Fore.RESET)
|
||||
|
||||
script_path = resolve_script_path(file_path)
|
||||
if script_path is None:
|
||||
_p(u"ERROR: No audit script found. Use -p <script.sh> or bundle the script.", getattr(Fore, 'RED', None))
|
||||
return
|
||||
|
||||
_p(u" Script: %s" % script_path)
|
||||
print(" [1/3] Reading script...")
|
||||
with open(script_path, 'r') as f:
|
||||
script_content = f.read()
|
||||
|
||||
content_u = script_content
|
||||
script_parts = content_u.split(
|
||||
"##################################################################################################################")
|
||||
output = ""
|
||||
for part in script_parts:
|
||||
# Chuẩn hoá: part_bytes là bytes UTF-8; so sánh shebang ở dạng bytes
|
||||
try:
|
||||
unicode
|
||||
if isinstance(part, unicode):
|
||||
part_bytes = part.encode('utf-8')
|
||||
else:
|
||||
part_bytes = part
|
||||
except NameError:
|
||||
part_bytes = part # (không dùng trên Py3 ở đây)
|
||||
|
||||
if b"#!/bin/bash" not in part_bytes:
|
||||
bash_src = b"#!/bin/bash\n" + part_bytes
|
||||
else:
|
||||
bash_src = part_bytes
|
||||
|
||||
out = run_bash(bash_src) # out là bytes
|
||||
try:
|
||||
output += out.decode('utf-8', 'ignore')
|
||||
except Exception:
|
||||
output += out # worst-case, giữ nguyên bytes
|
||||
|
||||
# Extract hostname/time lines from the produced output (keep logic as original)
|
||||
hostname_out, time_generate = extract_host_time(output)
|
||||
|
||||
os_name_raw = detect_os_name()
|
||||
os_name = safe_slug(os_name_raw, maxlen=60) # ASCII-safe
|
||||
file_encrypt_name = '%s_%s_%s.txt.enc' % (hostname_out, os_name, time_generate)
|
||||
enc_b64 = write_content(output)
|
||||
|
||||
with open(file_encrypt_name, 'wb') as f:
|
||||
# enc_b64 is bytes in Py2
|
||||
f.write(enc_b64)
|
||||
|
||||
if is_file_exist(file_encrypt_name):
|
||||
_p(u"THÀNH CÔNG - FILE ENCRYPT %s" % file_encrypt_name, getattr(Fore, 'GREEN', None))
|
||||
else:
|
||||
_p(u"THẤT BẠI!!!", getattr(Fore, 'RED', None))
|
||||
|
||||
|
||||
def run_ubuntu_audit(file_path=None):
|
||||
old_file_path = '%s.txt' % hostname
|
||||
if is_file_exist(old_file_path):
|
||||
try:
|
||||
os.remove(old_file_path)
|
||||
except Exception:
|
||||
pass
|
||||
run_audit(file_path)
|
||||
|
||||
|
||||
def parse_options():
|
||||
if not OptionParser:
|
||||
raise SystemExit("[ERROR] optparse not available. Please use Python 2.6/2.7.")
|
||||
parser = OptionParser(usage="usage: %prog [-p PATH_TO_SCRIPT]")
|
||||
parser.add_option("-p", "--path", dest="path",
|
||||
help="Path to audit script (optional if bundled)", metavar="FILE", action="store", default=None)
|
||||
(options, args) = parser.parse_args()
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
opts = parse_options()
|
||||
run_ubuntu_audit(opts.path)
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
@echo off
|
||||
REM ============================================================
|
||||
REM Build AuditTool.exe for Windows Server 2012+
|
||||
REM Requires: Python 3.8+, pip
|
||||
REM ============================================================
|
||||
|
||||
echo [1/3] Installing dependencies...
|
||||
python -m pip install pycryptodome colorama unidecode pyinstaller
|
||||
if %errorlevel% neq 0 (
|
||||
echo ERROR: pip install failed. Check Python installation.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [2/3] Copying PowerShell audit script...
|
||||
copy /Y ..\audit_check_script\audit_cis_windows.ps1 audit_cis_windows.ps1
|
||||
if %errorlevel% neq 0 (
|
||||
echo ERROR: audit_cis_windows.ps1 not found at ..\audit_check_script\
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [3/3] Building AuditTool.exe with PyInstaller...
|
||||
python -m PyInstaller --onefile --console --name AuditTool ^
|
||||
--add-data "audit_cis_windows.ps1;." ^
|
||||
--hidden-import Crypto.Cipher ^
|
||||
--hidden-import Crypto.PublicKey ^
|
||||
--hidden-import Crypto.Util.Padding ^
|
||||
windows_audit.py
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo ERROR: PyInstaller build failed
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Build complete!
|
||||
echo Output: dist\AuditTool.exe
|
||||
echo.
|
||||
echo Deploy to Windows Server:
|
||||
echo 1. Copy dist\AuditTool.exe to target server
|
||||
echo 2. Copy keys\public_key.pem to same folder as AuditTool.exe
|
||||
echo 3. Run: AuditTool.exe
|
||||
echo ========================================
|
||||
|
||||
pause
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# Build Linux audit binaries with PyInstaller
|
||||
# Run this on the TARGET OS version:
|
||||
# - Ubuntu binary → build on Ubuntu 20.04+
|
||||
# - CentOS 7 binary → build on CentOS 7
|
||||
# - CentOS 6 binary → build on CentOS 6 (Python 2.6+)
|
||||
# - Oracle binary → build on Oracle Linux 7+
|
||||
#
|
||||
# Usage: bash build_all.sh [ubuntu|centos|oracle|centos6|all]
|
||||
# ============================================================
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CHECK_SCRIPT_DIR="${PARENT_DIR}/audit_check_script"
|
||||
DIST_DIR="${SCRIPT_DIR}/dist"
|
||||
|
||||
mkdir -p "${DIST_DIR}"
|
||||
|
||||
build_ubuntu() {
|
||||
echo "=== Building Ubuntu Audit Tool ==="
|
||||
cp "${CHECK_SCRIPT_DIR}/audit_cis_ubuntu_v202.sh" "${SCRIPT_DIR}/"
|
||||
pip install pycryptodome cryptography colorama unidecode pyinstaller
|
||||
pyinstaller --onefile --console --name ubuntu_audit \
|
||||
--add-data "audit_cis_ubuntu_v202.sh:." \
|
||||
--hidden-import colorama \
|
||||
--hidden-import unidecode \
|
||||
--hidden-import Crypto.Cipher \
|
||||
--hidden-import Crypto.PublicKey \
|
||||
--hidden-import Crypto.Util.Padding \
|
||||
--hidden-import cryptography \
|
||||
--hidden-import cryptography.hazmat.primitives.ciphers \
|
||||
--hidden-import cryptography.hazmat.primitives.padding \
|
||||
ubuntu_audit_v2.py
|
||||
echo " -> ${DIST_DIR}/ubuntu_audit"
|
||||
}
|
||||
|
||||
build_centos() {
|
||||
echo "=== Building CentOS/RHEL Audit Tool ==="
|
||||
cp "${CHECK_SCRIPT_DIR}/audit_cis_centos_v202.sh" "${SCRIPT_DIR}/"
|
||||
pip install pycryptodome cryptography colorama unidecode pyinstaller
|
||||
pyinstaller --onefile --console --name centos_rhel_audit \
|
||||
--add-data "audit_cis_centos_v202.sh:." \
|
||||
--hidden-import colorama \
|
||||
--hidden-import unidecode \
|
||||
--hidden-import Crypto.Cipher \
|
||||
--hidden-import Crypto.PublicKey \
|
||||
--hidden-import Crypto.Util.Padding \
|
||||
--hidden-import cryptography \
|
||||
--hidden-import cryptography.hazmat.primitives.ciphers \
|
||||
--hidden-import cryptography.hazmat.primitives.padding \
|
||||
centos_rhel_audit_v2.py
|
||||
echo " -> ${DIST_DIR}/centos_rhel_audit"
|
||||
}
|
||||
|
||||
build_oracle() {
|
||||
echo "=== Building Oracle Linux Audit Tool ==="
|
||||
cp "${CHECK_SCRIPT_DIR}/audit_cis_centos_v202.sh" "${SCRIPT_DIR}/"
|
||||
pip install pycryptodome cryptography colorama unidecode pyinstaller
|
||||
pyinstaller --onefile --console --name oracle_linux_audit \
|
||||
--add-data "audit_cis_centos_v202.sh:." \
|
||||
--hidden-import colorama \
|
||||
--hidden-import unidecode \
|
||||
--hidden-import Crypto.Cipher \
|
||||
--hidden-import Crypto.PublicKey \
|
||||
--hidden-import Crypto.Util.Padding \
|
||||
--hidden-import cryptography \
|
||||
--hidden-import cryptography.hazmat.primitives.ciphers \
|
||||
--hidden-import cryptography.hazmat.primitives.padding \
|
||||
oracle_linux_audit_v2.py
|
||||
echo " -> ${DIST_DIR}/oracle_linux_audit"
|
||||
}
|
||||
|
||||
build_centos6() {
|
||||
echo "=== Building CentOS 6 Audit Tool ==="
|
||||
cp "${CHECK_SCRIPT_DIR}/audit_cis_centos6.sh" "${SCRIPT_DIR}/"
|
||||
pip install pyinstaller
|
||||
pyinstaller --onefile --console --name centos6_audit \
|
||||
--add-data "audit_cis_centos6.sh:." \
|
||||
audit_centos6.py
|
||||
echo " -> ${DIST_DIR}/centos6_audit"
|
||||
}
|
||||
|
||||
build_all() {
|
||||
build_ubuntu
|
||||
build_centos
|
||||
build_oracle
|
||||
build_centos6
|
||||
}
|
||||
|
||||
case "${1:-all}" in
|
||||
ubuntu) build_ubuntu ;;
|
||||
centos) build_centos ;;
|
||||
oracle) build_oracle ;;
|
||||
centos6) build_centos6 ;;
|
||||
all) build_all ;;
|
||||
*)
|
||||
echo "Usage: bash build_all.sh [ubuntu|centos|oracle|centos6|all]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Build complete! Output: ${DIST_DIR}/"
|
||||
echo ""
|
||||
echo "Deploy to target server:"
|
||||
echo " 1. Copy the binary from dist/"
|
||||
echo " 2. Copy public_key.pem to same folder"
|
||||
echo " 3. Run: ./<binary> (script is bundled, no -p needed)"
|
||||
echo " Or: ./<binary> -p custom_script.sh"
|
||||
echo "========================================"
|
||||
@@ -0,0 +1,42 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec for CentOS 6 Audit Tool (Python 2.6+)
|
||||
# Build: pyinstaller centos6_audit.spec
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['audit_centos6.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('audit_cis_centos6.sh', '.')],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='centos6_audit',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec for CentOS/RHEL Audit Tool
|
||||
# Build: pyinstaller centos_rhel_audit.spec
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['centos_rhel_audit_v2.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('audit_cis_centos_v202.sh', '.')],
|
||||
hiddenimports=[
|
||||
'colorama',
|
||||
'unidecode',
|
||||
'Crypto', 'Crypto.Cipher', 'Crypto.PublicKey', 'Crypto.Util.Padding',
|
||||
'cryptography', 'cryptography.hazmat', 'cryptography.hazmat.primitives',
|
||||
'cryptography.hazmat.primitives.ciphers', 'cryptography.hazmat.primitives.padding',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='centos_rhel_audit',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,253 @@
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import PKCS1_OAEP
|
||||
from Crypto.PublicKey import RSA
|
||||
from colorama import Fore
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
hostname = socket.gethostname()
|
||||
IPAddr = socket.gethostbyname(hostname)
|
||||
cwd = os.getcwd()
|
||||
BUNDLED_SCRIPT = "audit_cis_centos_v202.sh"
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
if getattr(sys, "frozen", False):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)
|
||||
|
||||
|
||||
def resolve_script_path(user_path=None):
|
||||
if user_path and os.path.isfile(user_path):
|
||||
return os.path.abspath(user_path)
|
||||
bundled = resource_path(BUNDLED_SCRIPT)
|
||||
if os.path.isfile(bundled):
|
||||
return bundled
|
||||
same_dir = os.path.join(os.getcwd(), BUNDLED_SCRIPT)
|
||||
if os.path.isfile(same_dir):
|
||||
return same_dir
|
||||
return None
|
||||
|
||||
def print_progress(current, total):
|
||||
if total == 0:
|
||||
return
|
||||
|
||||
bar_len = 40 # độ dài thanh
|
||||
filled = int(bar_len * current / total)
|
||||
bar = '█' * filled + '-' * (bar_len - filled)
|
||||
percent = int(current * 100 / total)
|
||||
|
||||
sys.stdout.write(f'\rĐang xử lý... [{bar}] {percent}%')
|
||||
sys.stdout.flush()
|
||||
|
||||
# Khi xong hết thì xuống dòng mới cho đẹp
|
||||
if current == total:
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def get_os_tag():
|
||||
"""Trả về nhãn OS dạng 'ubuntu_22_04' hoặc 'centos_7', ..."""
|
||||
try:
|
||||
if os.path.isfile("/etc/os-release"):
|
||||
name = ""
|
||||
version = ""
|
||||
with open("/etc/os-release") as f:
|
||||
for line in f:
|
||||
if line.startswith("NAME=") and not name:
|
||||
name = line.split("=", 1)[1].strip().strip('"').lower()
|
||||
elif line.startswith("VERSION_ID=") and not version:
|
||||
version = line.split("=", 1)[1].strip().strip('"').lower()
|
||||
tag = f"{name}_{version}" if version else name
|
||||
# chuẩn hoá: thay khoảng trắng và ký tự lạ
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", tag).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if os.path.isfile("/etc/redhat-release"):
|
||||
txt = open("/etc/redhat-release").read().strip().lower()
|
||||
# ví dụ: "centos linux release 7.9.2009 (core)"
|
||||
# lấy vendor + major.minor
|
||||
m = re.search(r"(centos|red hat|rhel|rocky|almalinux).*?release\s+([0-9]+(?:\.[0-9]+)?)", txt)
|
||||
if m:
|
||||
vendor = m.group(1).replace(" ", "")
|
||||
ver = m.group(2).replace(".", "_")
|
||||
return f"{vendor}_{ver}"
|
||||
# fallback rút gọn
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", txt).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import platform
|
||||
sysname = platform.system().lower()
|
||||
release = platform.release().lower()
|
||||
tag = f"{sysname}_{release}"
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", tag).strip("_")
|
||||
return tag or "unknown_os"
|
||||
except Exception:
|
||||
return "unknown_os"
|
||||
|
||||
def write_content(content):
|
||||
message = content.encode('utf-8')
|
||||
|
||||
aes_key = os.urandom(32)
|
||||
iv = os.urandom(16)
|
||||
|
||||
padder = padding.PKCS7(128).padder()
|
||||
padded_data = padder.update(message) + padder.finalize()
|
||||
|
||||
cipher_aes = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
|
||||
encryptor = cipher_aes.encryptor()
|
||||
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
||||
|
||||
public_key = RSA.import_key(open('public_key.pem').read())
|
||||
cipher_rsa = PKCS1_OAEP.new(public_key)
|
||||
enc_key = cipher_rsa.encrypt(aes_key)
|
||||
|
||||
b64_enc_key = base64.b64encode(enc_key).decode('utf-8')
|
||||
b64_iv = base64.b64encode(iv).decode('utf-8')
|
||||
b64_ct = base64.b64encode(ciphertext).decode('utf-8')
|
||||
|
||||
return f"HYBRID_V1:{b64_enc_key}:{b64_iv}:{b64_ct}"
|
||||
|
||||
|
||||
def is_file_exist(path):
|
||||
isFile = os.path.isfile(path)
|
||||
if isFile:
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def run_bash(command):
|
||||
process = subprocess.Popen(
|
||||
['/bin/bash', '-c', command],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
rc = process.returncode
|
||||
out = stdout.decode(errors="ignore")
|
||||
err = stderr.decode(errors="ignore")
|
||||
|
||||
# Luôn luôn ghép stderr (nếu có) vào log để không mất thông tin
|
||||
if err.strip():
|
||||
out += "\n########## STDERR BEGIN ##########\n"
|
||||
out += err
|
||||
out += "\n########## STDERR END ##########\n"
|
||||
|
||||
# Trả về tuple: (output, stderr, return_code)
|
||||
return out, err, rc
|
||||
|
||||
|
||||
|
||||
def run_audit(file_path):
|
||||
print(Fore.BLUE + """
|
||||
_ _ _____ _____ _______ _ _ _____ _____ ______ _ _ _____ _ _ _____
|
||||
/\ | | | | __ \_ _|__ __| | | | | /\ | __ \| __ \| ____| \ | |_ _| \ | |/ ____|
|
||||
/ \ | | | | | | || | | | ______ | |__| | / \ | |__) | | | | |__ | \| | | | | \| | | __
|
||||
/ /\ \| | | | | | || | | | |______| | __ | / /\ \ | _ /| | | | __| | . ` | | | | . ` | | |_ |
|
||||
/ ____ \ |__| | |__| || |_ | | | | | |/ ____ \| | \ \| |__| | |____| |\ |_| |_| |\ | |__| |
|
||||
/_/ \_\____/|_____/_____| |_| |_| |_/_/ \_\_| \_\_____/|______|_| \_|_____|_| \_|\_____|
|
||||
|
||||
|
||||
""" + Fore.RESET)
|
||||
print(Fore.RED + "Running Audit Hardening..." + Fore.RESET)
|
||||
|
||||
script_path = resolve_script_path(file_path)
|
||||
if script_path is None:
|
||||
print(Fore.RED + "ERROR: No audit script found. Use -p <script.sh> or bundle the script." + Fore.RESET)
|
||||
return
|
||||
|
||||
print(f" Script: {script_path}")
|
||||
print(" [1/3] Reading script...")
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
script_content = f.read()
|
||||
|
||||
script = script_content.split(
|
||||
"##################################################################################################################")
|
||||
|
||||
# Lọc bỏ các block rỗng trước
|
||||
script_blocks = [block.strip() for block in script if block.strip()]
|
||||
total_blocks = len(script_blocks)
|
||||
|
||||
output = ""
|
||||
errors = [] # Lưu lỗi để hiển thị sau khi loading xong
|
||||
|
||||
for idx, i in enumerate(script_blocks, 1):
|
||||
# Hiển thị progress bar
|
||||
print_progress(idx, total_blocks)
|
||||
|
||||
if "#!/bin/bash" not in i:
|
||||
result, err, rc = run_bash("#!/bin/bash\n{0}".format(i))
|
||||
else:
|
||||
result, err, rc = run_bash(i)
|
||||
|
||||
# Lưu lỗi nếu có (exit code != 0)
|
||||
if rc != 0 and err.strip():
|
||||
errors.append(f"[Block {idx}] {err.strip().split(chr(10))[0]}")
|
||||
|
||||
# Nếu lệnh lỗi và run_bash trả về None thì bỏ qua
|
||||
if result is None:
|
||||
continue
|
||||
|
||||
output += result
|
||||
|
||||
# Hiển thị các lỗi sau khi loading xong
|
||||
if errors:
|
||||
print(Fore.YELLOW + f"\n⚠ Có {len(errors)} cảnh báo:" + Fore.RESET)
|
||||
for e in errors:
|
||||
print(Fore.YELLOW + f" • {e}" + Fore.RESET)
|
||||
# Dùng regex để tìm Hostname và Audit Time trong output
|
||||
hostname_match = re.search(r"Hostname:\s*(\S+)", output)
|
||||
time_match = re.search(r"Audit Time:\s*(.+)", output)
|
||||
|
||||
hostname = hostname_match.group(1) if hostname_match else "unknown_host"
|
||||
time_generate = time_match.group(1).strip().replace("-", "_").replace(":", "_").replace(" ", "-") if time_match else "unknown_time"
|
||||
os_tag = get_os_tag()
|
||||
file_encrypt_name = f"{hostname}_{os_tag}_{time_generate}.txt.enc"
|
||||
# file_encrypt_name = '{}_{}.txt.enc'.format(hostname, time_generate)
|
||||
encrypt_result = write_content(output)
|
||||
with open(file_encrypt_name, 'w') as f:
|
||||
f.write(encrypt_result)
|
||||
|
||||
if is_file_exist(file_encrypt_name):
|
||||
print(Fore.GREEN + "THÀNH CÔNG - FILE ENCRYPT {}".format(file_encrypt_name) + Fore.RESET)
|
||||
else:
|
||||
print(Fore.RED + "THẤT BẠI RỒI THỬ LẠI NHÁ !!!" + Fore.RESET)
|
||||
|
||||
|
||||
def run_ubuntu_audit(file_path=None):
|
||||
old_file_path = '{}.txt'.format(hostname)
|
||||
old_file = is_file_exist(old_file_path)
|
||||
if old_file:
|
||||
os.remove(old_file_path)
|
||||
run_audit(file_path)
|
||||
|
||||
|
||||
def main():
|
||||
# Parse Arguments
|
||||
parser = argparse.ArgumentParser(description='Audit Hardening')
|
||||
parser.add_argument('-p', '--path', help='Path to audit script (optional if bundled)', default=None)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = main()
|
||||
run_ubuntu_audit(args.path)
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec for Oracle Linux Audit Tool
|
||||
# Build: pyinstaller oracle_linux_audit.spec
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['oracle_linux_audit_v2.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('audit_cis_centos_v202.sh', '.')],
|
||||
hiddenimports=[
|
||||
'colorama',
|
||||
'unidecode',
|
||||
'Crypto', 'Crypto.Cipher', 'Crypto.PublicKey', 'Crypto.Util.Padding',
|
||||
'cryptography', 'cryptography.hazmat', 'cryptography.hazmat.primitives',
|
||||
'cryptography.hazmat.primitives.ciphers', 'cryptography.hazmat.primitives.padding',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='oracle_linux_audit',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,272 @@
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import PKCS1_OAEP
|
||||
from Crypto.PublicKey import RSA
|
||||
from colorama import Fore
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
hostname = socket.gethostname()
|
||||
IPAddr = socket.gethostbyname(hostname)
|
||||
cwd = os.getcwd()
|
||||
BUNDLED_SCRIPT = "audit_cis_centos_v202.sh"
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
if getattr(sys, "frozen", False):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)
|
||||
|
||||
|
||||
def resolve_script_path(user_path=None):
|
||||
if user_path and os.path.isfile(user_path):
|
||||
return os.path.abspath(user_path)
|
||||
bundled = resource_path(BUNDLED_SCRIPT)
|
||||
if os.path.isfile(bundled):
|
||||
return bundled
|
||||
same_dir = os.path.join(os.getcwd(), BUNDLED_SCRIPT)
|
||||
if os.path.isfile(same_dir):
|
||||
return same_dir
|
||||
return None
|
||||
|
||||
def print_progress(current, total):
|
||||
if total == 0:
|
||||
return
|
||||
|
||||
bar_len = 40 # độ dài thanh
|
||||
filled = int(bar_len * current / total)
|
||||
bar = '█' * filled + '-' * (bar_len - filled)
|
||||
percent = int(current * 100 / total)
|
||||
|
||||
sys.stdout.write(f'\rĐang xử lý... [{bar}] {percent}%')
|
||||
sys.stdout.flush()
|
||||
|
||||
# Khi xong hết thì xuống dòng mới cho đẹp
|
||||
if current == total:
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def get_os_tag():
|
||||
"""
|
||||
Trả về nhãn OS dạng 'oracle_linux_8_9', 'rhel_7', 'centos_7_9', 'ubuntu_22_04', ...
|
||||
Ưu tiên /etc/os-release; fallback /etc/oracle-release hoặc /etc/redhat-release; cuối cùng uname.
|
||||
"""
|
||||
# 1) /etc/os-release (chuẩn mới trên hầu hết distro)
|
||||
try:
|
||||
if os.path.isfile("/etc/os-release"):
|
||||
name = ""
|
||||
version = ""
|
||||
with open("/etc/os-release") as f:
|
||||
for line in f:
|
||||
if line.startswith("NAME=") and not name:
|
||||
name = line.split("=", 1)[1].strip().strip('"').lower()
|
||||
elif line.startswith("VERSION_ID=") and not version:
|
||||
version = line.split("=", 1)[1].strip().strip('"').lower()
|
||||
tag = f"{name}_{version}" if version else name
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", tag).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2) Oracle Linux đời cũ
|
||||
try:
|
||||
if os.path.isfile("/etc/oracle-release"):
|
||||
txt = open("/etc/oracle-release").read().strip().lower()
|
||||
# ví dụ: "oracle linux server release 8.9"
|
||||
m = re.search(r"(oracle)\s+linux.*?release\s+([0-9]+(?:\.[0-9]+)?)", txt)
|
||||
if m:
|
||||
vendor = "oracle_linux"
|
||||
ver = m.group(2).replace(".", "_")
|
||||
return f"{vendor}_{ver}"
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", txt).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 3) RHEL/CentOS/Alma/Rocky đời cũ
|
||||
try:
|
||||
if os.path.isfile("/etc/redhat-release"):
|
||||
txt = open("/etc/redhat-release").read().strip().lower()
|
||||
# ví dụ: "centos linux release 7.9.2009 (core)"
|
||||
m = re.search(r"(centos|red hat|rhel|rocky|almalinux).*?release\s+([0-9]+(?:\.[0-9]+)?)", txt)
|
||||
if m:
|
||||
vendor = m.group(1).replace(" ", "")
|
||||
ver = m.group(2).replace(".", "_")
|
||||
return f"{vendor}_{ver}"
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", txt).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 4) uname (fallback cuối)
|
||||
try:
|
||||
import platform
|
||||
sysname = platform.system().lower()
|
||||
release = platform.release().lower()
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", f"{sysname}_{release}").strip("_")
|
||||
return tag or "unknown_os"
|
||||
except Exception:
|
||||
return "unknown_os"
|
||||
|
||||
|
||||
def write_content(content):
|
||||
message = content.encode('utf-8')
|
||||
|
||||
aes_key = os.urandom(32)
|
||||
iv = os.urandom(16)
|
||||
|
||||
padder = padding.PKCS7(128).padder()
|
||||
padded_data = padder.update(message) + padder.finalize()
|
||||
|
||||
cipher_aes = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
|
||||
encryptor = cipher_aes.encryptor()
|
||||
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
||||
|
||||
public_key = RSA.import_key(open('public_key.pem').read())
|
||||
cipher_rsa = PKCS1_OAEP.new(public_key)
|
||||
enc_key = cipher_rsa.encrypt(aes_key)
|
||||
|
||||
b64_enc_key = base64.b64encode(enc_key).decode('utf-8')
|
||||
b64_iv = base64.b64encode(iv).decode('utf-8')
|
||||
b64_ct = base64.b64encode(ciphertext).decode('utf-8')
|
||||
|
||||
return f"HYBRID_V1:{b64_enc_key}:{b64_iv}:{b64_ct}"
|
||||
|
||||
|
||||
def is_file_exist(path):
|
||||
isFile = os.path.isfile(path)
|
||||
if isFile:
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def run_bash(command):
|
||||
process = subprocess.Popen(
|
||||
['/bin/bash', '-c', command],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
rc = process.returncode
|
||||
out = stdout.decode(errors="ignore")
|
||||
err = stderr.decode(errors="ignore")
|
||||
|
||||
# Luôn luôn ghép stderr (nếu có) vào log để không mất thông tin
|
||||
if err.strip():
|
||||
out += "\n########## STDERR BEGIN ##########\n"
|
||||
out += err
|
||||
out += "\n########## STDERR END ##########\n"
|
||||
|
||||
# Trả về tuple: (output, stderr, return_code)
|
||||
return out, err, rc
|
||||
|
||||
|
||||
|
||||
def run_audit(file_path=None):
|
||||
print(Fore.BLUE + """
|
||||
_ _ _____ _____ _______ _ _ _____ _____ ______ _ _ _____ _ _ _____
|
||||
/\ | | | | __ \_ _|__ __| | | | | /\ | __ \| __ \| ____| \ | |_ _| \ | |/ ____|
|
||||
/ \ | | | | | | || | | | ______ | |__| | / \ | |__) | | | | |__ | \| | | | | \| | | __
|
||||
/ /\ \| | | | | | || | | | |______| | __ | / /\ \ | _ /| | | | __| | . ` | | | | . ` | | |_ |
|
||||
/ ____ \ |__| | |__| || |_ | | | | | |/ ____ \| | \ \| |__| | |____| |\ |_| |_| |\ | |__| |
|
||||
/_/ \_\____/|_____/_____| |_| |_| |_/_/ \_\_| \_\_____/|______|_| \_|_____|_| \_|\_____|
|
||||
|
||||
|
||||
""" + Fore.RESET)
|
||||
print(Fore.RED + "Running Audit Hardening..." + Fore.RESET)
|
||||
|
||||
script_path = resolve_script_path(file_path)
|
||||
if script_path is None:
|
||||
print(Fore.RED + "ERROR: No audit script found. Use -p <script.sh> or bundle the script." + Fore.RESET)
|
||||
return
|
||||
|
||||
print(f" Script: {script_path}")
|
||||
print(" [1/3] Reading script...")
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
script_content = f.read()
|
||||
|
||||
script = script_content.split(
|
||||
"##################################################################################################################")
|
||||
|
||||
# Lọc bỏ các block rỗng trước
|
||||
script_blocks = [block.strip() for block in script if block.strip()]
|
||||
total_blocks = len(script_blocks)
|
||||
|
||||
output = ""
|
||||
errors = [] # Lưu lỗi để hiển thị sau khi loading xong
|
||||
|
||||
for idx, i in enumerate(script_blocks, 1):
|
||||
# Hiển thị progress bar
|
||||
print_progress(idx, total_blocks)
|
||||
|
||||
if "#!/bin/bash" not in i:
|
||||
result, err, rc = run_bash("#!/bin/bash\n{0}".format(i))
|
||||
else:
|
||||
result, err, rc = run_bash(i)
|
||||
|
||||
# Lưu lỗi nếu có (exit code != 0)
|
||||
if rc != 0 and err.strip():
|
||||
errors.append(f"[Block {idx}] {err.strip().split(chr(10))[0]}")
|
||||
|
||||
# Nếu lệnh lỗi và run_bash trả về None thì bỏ qua
|
||||
if result is None:
|
||||
continue
|
||||
|
||||
output += result
|
||||
|
||||
# Hiển thị các lỗi sau khi loading xong
|
||||
if errors:
|
||||
print(Fore.YELLOW + f"\n⚠ Có {len(errors)} cảnh báo:" + Fore.RESET)
|
||||
for e in errors:
|
||||
print(Fore.YELLOW + f" • {e}" + Fore.RESET)
|
||||
# Dùng regex để tìm Hostname và Audit Time trong output
|
||||
hostname_match = re.search(r"Hostname:\s*(\S+)", output)
|
||||
time_match = re.search(r"Audit Time:\s*(.+)", output)
|
||||
|
||||
hostname = hostname_match.group(1) if hostname_match else "unknown_host"
|
||||
time_generate = time_match.group(1).strip().replace("-", "_").replace(":", "_").replace(" ", "-") if time_match else "unknown_time"
|
||||
os_tag = get_os_tag()
|
||||
file_encrypt_name = f"{hostname}_{os_tag}_{time_generate}.txt.enc"
|
||||
# file_encrypt_name = '{}_{}.txt.enc'.format(hostname, time_generate)
|
||||
encrypt_result = write_content(output)
|
||||
with open(file_encrypt_name, 'w') as f:
|
||||
f.write(encrypt_result)
|
||||
|
||||
if is_file_exist(file_encrypt_name):
|
||||
print(Fore.GREEN + "THÀNH CÔNG - FILE ENCRYPT {}".format(file_encrypt_name) + Fore.RESET)
|
||||
else:
|
||||
print(Fore.RED + "THẤT BẠI RỒI THỬ LẠI NHÁ !!!" + Fore.RESET)
|
||||
|
||||
|
||||
def run_os_audit(file_path=None):
|
||||
old_file_path = '{}.txt'.format(hostname)
|
||||
old_file = is_file_exist(old_file_path)
|
||||
if old_file:
|
||||
os.remove(old_file_path)
|
||||
run_audit(file_path)
|
||||
|
||||
|
||||
def main():
|
||||
# Parse Arguments
|
||||
parser = argparse.ArgumentParser(description='Audit Hardening')
|
||||
parser.add_argument('-p', '--path', help='Path to audit script (optional if bundled)', default=None)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = main()
|
||||
run_os_audit(args.path)
|
||||
@@ -0,0 +1,12 @@
|
||||
# Windows Audit Python Dependencies
|
||||
# Target: Python 3.8 (Windows Server 2012 compatible)
|
||||
#
|
||||
# Setup with conda:
|
||||
# conda create -n audit python=3.8 -y
|
||||
# conda activate audit
|
||||
# pip install -r requirements.txt
|
||||
|
||||
pycryptodome>=3.15,<4
|
||||
colorama>=0.4
|
||||
unidecode>=1.3
|
||||
pyinstaller>=5.0,<6
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec for Ubuntu Audit Tool
|
||||
# Build: pyinstaller ubuntu_audit.spec
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['ubuntu_audit_v2.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('audit_cis_ubuntu_v202.sh', '.')],
|
||||
hiddenimports=[
|
||||
'colorama',
|
||||
'unidecode',
|
||||
'Crypto', 'Crypto.Cipher', 'Crypto.PublicKey', 'Crypto.Util.Padding',
|
||||
'cryptography', 'cryptography.hazmat', 'cryptography.hazmat.primitives',
|
||||
'cryptography.hazmat.primitives.ciphers', 'cryptography.hazmat.primitives.padding',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ubuntu_audit',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,252 @@
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import PKCS1_OAEP
|
||||
from Crypto.PublicKey import RSA
|
||||
from colorama import Fore
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
hostname = socket.gethostname()
|
||||
IPAddr = socket.gethostbyname(hostname)
|
||||
cwd = os.getcwd()
|
||||
BUNDLED_SCRIPT = "audit_cis_ubuntu_v202.sh"
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
if getattr(sys, "frozen", False):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)
|
||||
|
||||
|
||||
def resolve_script_path(user_path=None):
|
||||
if user_path and os.path.isfile(user_path):
|
||||
return os.path.abspath(user_path)
|
||||
bundled = resource_path(BUNDLED_SCRIPT)
|
||||
if os.path.isfile(bundled):
|
||||
return bundled
|
||||
same_dir = os.path.join(os.getcwd(), BUNDLED_SCRIPT)
|
||||
if os.path.isfile(same_dir):
|
||||
return same_dir
|
||||
return None
|
||||
|
||||
def print_progress(current, total):
|
||||
if total == 0:
|
||||
return
|
||||
|
||||
bar_len = 40 # độ dài thanh
|
||||
filled = int(bar_len * current / total)
|
||||
bar = '█' * filled + '-' * (bar_len - filled)
|
||||
percent = int(current * 100 / total)
|
||||
|
||||
sys.stdout.write(f'\rĐang xử lý... [{bar}] {percent}%')
|
||||
sys.stdout.flush()
|
||||
|
||||
# Khi xong hết thì xuống dòng mới cho đẹp
|
||||
if current == total:
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def get_os_tag():
|
||||
"""Trả về nhãn OS dạng 'ubuntu_22_04' hoặc 'centos_7', ..."""
|
||||
try:
|
||||
if os.path.isfile("/etc/os-release"):
|
||||
name = ""
|
||||
version = ""
|
||||
with open("/etc/os-release") as f:
|
||||
for line in f:
|
||||
if line.startswith("NAME=") and not name:
|
||||
name = line.split("=", 1)[1].strip().strip('"').lower()
|
||||
elif line.startswith("VERSION_ID=") and not version:
|
||||
version = line.split("=", 1)[1].strip().strip('"').lower()
|
||||
tag = f"{name}_{version}" if version else name
|
||||
# chuẩn hoá: thay khoảng trắng và ký tự lạ
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", tag).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if os.path.isfile("/etc/redhat-release"):
|
||||
txt = open("/etc/redhat-release").read().strip().lower()
|
||||
# ví dụ: "centos linux release 7.9.2009 (core)"
|
||||
# lấy vendor + major.minor
|
||||
m = re.search(r"(centos|red hat|rhel|rocky|almalinux).*?release\s+([0-9]+(?:\.[0-9]+)?)", txt)
|
||||
if m:
|
||||
vendor = m.group(1).replace(" ", "")
|
||||
ver = m.group(2).replace(".", "_")
|
||||
return f"{vendor}_{ver}"
|
||||
# fallback rút gọn
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", txt).strip("_")
|
||||
if tag:
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import platform
|
||||
sysname = platform.system().lower()
|
||||
release = platform.release().lower()
|
||||
tag = f"{sysname}_{release}"
|
||||
tag = re.sub(r"[^a-z0-9]+", "_", tag).strip("_")
|
||||
return tag or "unknown_os"
|
||||
except Exception:
|
||||
return "unknown_os"
|
||||
|
||||
def write_content(content):
|
||||
message = content.encode('utf-8')
|
||||
|
||||
aes_key = os.urandom(32)
|
||||
iv = os.urandom(16)
|
||||
|
||||
padder = padding.PKCS7(128).padder()
|
||||
padded_data = padder.update(message) + padder.finalize()
|
||||
|
||||
cipher_aes = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
|
||||
encryptor = cipher_aes.encryptor()
|
||||
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
||||
|
||||
public_key = RSA.import_key(open('public_key.pem').read())
|
||||
cipher_rsa = PKCS1_OAEP.new(public_key)
|
||||
enc_key = cipher_rsa.encrypt(aes_key)
|
||||
|
||||
b64_enc_key = base64.b64encode(enc_key).decode('utf-8')
|
||||
b64_iv = base64.b64encode(iv).decode('utf-8')
|
||||
b64_ct = base64.b64encode(ciphertext).decode('utf-8')
|
||||
|
||||
return f"HYBRID_V1:{b64_enc_key}:{b64_iv}:{b64_ct}"
|
||||
|
||||
|
||||
def is_file_exist(path):
|
||||
isFile = os.path.isfile(path)
|
||||
if isFile:
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def run_bash(command):
|
||||
process = subprocess.Popen(
|
||||
['/bin/bash', '-c', command],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
rc = process.returncode
|
||||
out = stdout.decode(errors="ignore")
|
||||
err = stderr.decode(errors="ignore")
|
||||
|
||||
# Luôn luôn ghép stderr (nếu có) vào log để không mất thông tin
|
||||
if err.strip():
|
||||
out += "\n########## STDERR BEGIN ##########\n"
|
||||
out += err
|
||||
out += "\n########## STDERR END ##########\n"
|
||||
|
||||
# Trả về tuple: (output, stderr, return_code)
|
||||
return out, err, rc
|
||||
|
||||
|
||||
|
||||
def run_audit(file_path=None):
|
||||
print(Fore.BLUE + """
|
||||
_ _ _____ _____ _______ _ _ _____ _____ ______ _ _ _____ _ _ _____
|
||||
/\ | | | | __ \_ _|__ __| | | | | /\ | __ \| __ \| ____| \ | |_ _| \ | |/ ____|
|
||||
/ \ | | | | | | || | | | ______ | |__| | / \ | |__) | | | | |__ | \| | | | | \| | | __
|
||||
/ /\ \| | | | | | || | | | |______| | __ | / /\ \ | _ /| | | | __| | . ` | | | | . ` | | |_ |
|
||||
/ ____ \ |__| | |__| || |_ | | | | | |/ ____ \| | \ \| |__| | |____| |\ |_| |_| |\ | |__| |
|
||||
/_/ \_\____/|_____/_____| |_| |_| |_/_/ \_\_| \_\_____/|______|_| \_|_____|_| \_|\_____|
|
||||
|
||||
|
||||
""" + Fore.RESET)
|
||||
print(Fore.RED + "Running Audit Hardening..." + Fore.RESET)
|
||||
|
||||
script_path = resolve_script_path(file_path)
|
||||
if script_path is None:
|
||||
print(Fore.RED + "ERROR: No audit script found. Use -p <script.sh> or bundle the script." + Fore.RESET)
|
||||
return
|
||||
|
||||
print(f" Script: {script_path}")
|
||||
print(" [1/3] Reading script...")
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
script_content = f.read()
|
||||
|
||||
script = script_content.split(
|
||||
"##################################################################################################################")
|
||||
|
||||
# Lọc bỏ các block rỗng trước
|
||||
script_blocks = [block.strip() for block in script if block.strip()]
|
||||
total_blocks = len(script_blocks)
|
||||
|
||||
output = ""
|
||||
errors = [] # Lưu lỗi để hiển thị sau khi loading xong
|
||||
|
||||
for idx, i in enumerate(script_blocks, 1):
|
||||
# Hiển thị progress bar
|
||||
print_progress(idx, total_blocks)
|
||||
|
||||
if "#!/bin/bash" not in i:
|
||||
result, err, rc = run_bash("#!/bin/bash\n{0}".format(i))
|
||||
else:
|
||||
result, err, rc = run_bash(i)
|
||||
|
||||
# Lưu lỗi nếu có (exit code != 0)
|
||||
if rc != 0 and err.strip():
|
||||
errors.append(f"[Block {idx}] {err.strip().split(chr(10))[0]}")
|
||||
|
||||
# Nếu lệnh lỗi và run_bash trả về None thì bỏ qua
|
||||
if result is None:
|
||||
continue
|
||||
|
||||
output += result
|
||||
|
||||
# Hiển thị các lỗi sau khi loading xong
|
||||
if errors:
|
||||
print(Fore.YELLOW + f"\n⚠ Có {len(errors)} cảnh báo:" + Fore.RESET)
|
||||
for e in errors:
|
||||
print(Fore.YELLOW + f" • {e}" + Fore.RESET)
|
||||
# Dùng regex để tìm Hostname và Audit Time trong output
|
||||
hostname_match = re.search(r"Hostname:\s*(\S+)", output)
|
||||
time_match = re.search(r"Audit Time:\s*(.+)", output)
|
||||
|
||||
hostname = hostname_match.group(1) if hostname_match else "unknown_host"
|
||||
time_generate = time_match.group(1).strip().replace("-", "_").replace(":", "_").replace(" ", "-") if time_match else "unknown_time"
|
||||
os_tag = get_os_tag()
|
||||
file_encrypt_name = f"{hostname}_{os_tag}_{time_generate}.txt.enc"
|
||||
# file_encrypt_name = '{}_{}.txt.enc'.format(hostname, time_generate)
|
||||
encrypt_result = write_content(output)
|
||||
with open(file_encrypt_name, 'w') as f:
|
||||
f.write(encrypt_result)
|
||||
|
||||
if is_file_exist(file_encrypt_name):
|
||||
print(Fore.GREEN + "THÀNH CÔNG - FILE ENCRYPT {}".format(file_encrypt_name) + Fore.RESET)
|
||||
else:
|
||||
print(Fore.RED + "THẤT BẠI RỒI THỬ LẠI NHÁ !!!" + Fore.RESET)
|
||||
|
||||
|
||||
def run_ubuntu_audit(file_path=None):
|
||||
old_file_path = '{}.txt'.format(hostname)
|
||||
old_file = is_file_exist(old_file_path)
|
||||
if old_file:
|
||||
os.remove(old_file_path)
|
||||
run_audit(file_path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Audit Hardening')
|
||||
parser.add_argument('-p', '--path', help='Path to audit script (optional if bundled)', default=None)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = main()
|
||||
run_ubuntu_audit(args.path)
|
||||
@@ -0,0 +1,286 @@
|
||||
"""
|
||||
Windows Audit Hardening Tool - v2.0
|
||||
Usage: AuditTool.exe -p audit_cis_windows.ps1
|
||||
AuditTool.exe (uses bundled .ps1 file)
|
||||
|
||||
Build: python -m PyInstaller --onefile --console -n AuditTool ^
|
||||
--add-data "audit_cis_windows.ps1;." ^
|
||||
windows_audit.py
|
||||
|
||||
Environment: Python 3.8+ (3.8 for Windows Server 2012 compatibility)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
from Crypto.Cipher import PKCS1_OAEP, AES
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.Padding import pad
|
||||
from colorama import Fore, init as colorama_init
|
||||
from unidecode import unidecode
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Globals
|
||||
# ----------------------------------------------------------
|
||||
colorama_init(strip=False, autoreset=True)
|
||||
|
||||
HOSTNAME = socket.gethostname()
|
||||
try:
|
||||
IP_ADDR = socket.gethostbyname(HOSTNAME)
|
||||
except Exception:
|
||||
IP_ADDR = "127.0.0.1"
|
||||
|
||||
BUNDLED_SCRIPT = "audit_cis_windows.ps1"
|
||||
PUBLIC_KEY_FILE = "public_key.pem"
|
||||
|
||||
BANNER = r"""
|
||||
_ _ _____ _____ _______ _ _ _____ _____ ______ _ _ _____ _ _ _____
|
||||
/\ | | | | __ \_ _|__ __| | | | | /\ | __ \| __ \| ____| \ | |_ _| \ | |/ ____|
|
||||
/ \ | | | | | | || | | | ______ | |__| | / \ | |__) | | | | |__ | \| | | | | \| | | __
|
||||
/ /\ \| | | | | | || | | | |______| | __ | / /\ \ | _ /| | | | __| | . ` | | | | . ` | | |_ |
|
||||
/ ____ \ |__| | |__| || |_ | | | | | |/ ____ \| | \ \| |__| | |____| |\ |_| |_| |\ | |__| |
|
||||
/_/ \_\____/|_____/_____| |_| |_| |_/_/ \_\_| \_\_____/|______|_| \_|_____|_| \_|\_____|
|
||||
"""
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Path resolution (supports PyInstaller bundle)
|
||||
# ----------------------------------------------------------
|
||||
def resource_path(relative_path):
|
||||
"""Get absolute path to resource, works for dev and PyInstaller."""
|
||||
if getattr(sys, "frozen", False):
|
||||
base = sys._MEIPASS
|
||||
else:
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
return os.path.join(base, relative_path)
|
||||
|
||||
|
||||
def resolve_script_path(user_path=None):
|
||||
"""
|
||||
Resolve the .ps1 script path.
|
||||
Priority: user-supplied arg > bundled file > same-dir file.
|
||||
Returns absolute path or None.
|
||||
"""
|
||||
if user_path:
|
||||
if os.path.isfile(user_path):
|
||||
return os.path.abspath(user_path)
|
||||
print(Fore.YELLOW + "[WARNING] Provided path not found: {}".format(user_path) + Fore.RESET)
|
||||
|
||||
# Try PyInstaller bundled location
|
||||
bundled = resource_path(BUNDLED_SCRIPT)
|
||||
if os.path.isfile(bundled):
|
||||
return bundled
|
||||
|
||||
# Try same directory
|
||||
same_dir = os.path.join(os.getcwd(), BUNDLED_SCRIPT)
|
||||
if os.path.isfile(same_dir):
|
||||
return same_dir
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def resolve_public_key():
|
||||
"""Find public_key.pem - bundled, same dir, or current dir."""
|
||||
for loc in [
|
||||
resource_path(PUBLIC_KEY_FILE),
|
||||
os.path.join(os.getcwd(), PUBLIC_KEY_FILE),
|
||||
]:
|
||||
if os.path.isfile(loc):
|
||||
return loc
|
||||
return None
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Crypto helpers
|
||||
# ----------------------------------------------------------
|
||||
def encrypt_output_rsa(content):
|
||||
"""Encrypt output with Hybrid AES-256-CBC + RSA (HYBRID_V1 format)."""
|
||||
pk_path = resolve_public_key()
|
||||
if pk_path is None:
|
||||
return "PLAINTEXT:" + content
|
||||
try:
|
||||
raw = content.encode("utf-8")
|
||||
|
||||
aes_key = os.urandom(32)
|
||||
iv = os.urandom(16)
|
||||
|
||||
padded = pad(raw, AES.block_size, style="pkcs7")
|
||||
cipher_aes = AES.new(aes_key, AES.MODE_CBC, iv)
|
||||
ciphertext = cipher_aes.encrypt(padded)
|
||||
|
||||
pub_key = RSA.import_key(open(pk_path, "rb").read())
|
||||
cipher_rsa = PKCS1_OAEP.new(pub_key)
|
||||
enc_key = cipher_rsa.encrypt(aes_key)
|
||||
|
||||
b64_enc_key = base64.b64encode(enc_key).decode("utf-8")
|
||||
b64_iv = base64.b64encode(iv).decode("utf-8")
|
||||
b64_ct = base64.b64encode(ciphertext).decode("utf-8")
|
||||
|
||||
return f"HYBRID_V1:{b64_enc_key}:{b64_iv}:{b64_ct}"
|
||||
except Exception as e:
|
||||
print(Fore.YELLOW + "[WARNING] RSA encrypt failed: {}".format(e) + Fore.RESET)
|
||||
return "PLAINTEXT:" + content
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# PowerShell runner
|
||||
# ----------------------------------------------------------
|
||||
def run_powershell_script(script_content, timeout_sec=600):
|
||||
"""
|
||||
Write script to temp .ps1 file, execute via 'powershell -File',
|
||||
return (stdout, stderr, returncode).
|
||||
Handles PowerShell's UTF-16LE output encoding correctly.
|
||||
"""
|
||||
tmp_path = None
|
||||
try:
|
||||
fd, tmp_path = tempfile.mkstemp(suffix=".ps1", prefix="audit_")
|
||||
with os.fdopen(fd, "w", encoding="utf-8-sig") as fh:
|
||||
fh.write(script_content)
|
||||
|
||||
proc = subprocess.run(
|
||||
[
|
||||
"powershell.exe",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", tmp_path,
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=timeout_sec,
|
||||
)
|
||||
# PowerShell outputs UTF-16LE by default
|
||||
stdout = proc.stdout.decode("utf-16-le", errors="replace") if proc.stdout else ""
|
||||
stderr = proc.stderr.decode("utf-16-le", errors="replace") if proc.stderr else ""
|
||||
return stdout, stderr, proc.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
return "", "PowerShell execution timed out ({}s)".format(timeout_sec), -1
|
||||
except Exception:
|
||||
return "", traceback.format_exc(), -1
|
||||
finally:
|
||||
if tmp_path and os.path.exists(tmp_path):
|
||||
try:
|
||||
os.remove(tmp_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Output parsing
|
||||
# ----------------------------------------------------------
|
||||
def extract_audit_info(output):
|
||||
"""Extract hostname + timestamp from audit output lines."""
|
||||
hostname = HOSTNAME
|
||||
timestamp = "unknown"
|
||||
for line in output.splitlines():
|
||||
if line.startswith("Hostname:"):
|
||||
val = line.replace("Hostname:", "").strip()
|
||||
hostname = val if val else HOSTNAME
|
||||
elif line.startswith("Time:"):
|
||||
val = line.replace("Time:", "").strip()
|
||||
timestamp = val.replace("-", "_").replace(":", "_").replace(" ", "-") if val else "unknown"
|
||||
return hostname, timestamp
|
||||
|
||||
|
||||
def count_pass_fail(output):
|
||||
"""Count PASSED/FAILED from JSON lines."""
|
||||
passed = failed = 0
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("{") and line.endswith("}"):
|
||||
if '"PASSED"' in line or "'PASSED'" in line:
|
||||
passed += 1
|
||||
elif '"FAILED"' in line or "'FAILED'" in line:
|
||||
failed += 1
|
||||
return passed, failed
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Main audit flow
|
||||
# ----------------------------------------------------------
|
||||
def run_audit(script_path=None):
|
||||
"""Read .ps1, run PowerShell audit, encrypt+save results."""
|
||||
print(Fore.BLUE + BANNER + Fore.RESET)
|
||||
print(Fore.RED + "Running Audit Hardening v2.0" + Fore.RESET)
|
||||
print(" Hostname : {}".format(HOSTNAME))
|
||||
print(" IP : {}".format(IP_ADDR))
|
||||
print("-" * 70)
|
||||
|
||||
# Step 0 – Resolve input script
|
||||
script_path = resolve_script_path(script_path)
|
||||
if script_path is None:
|
||||
print(Fore.RED + "ERROR: No .ps1 file found.")
|
||||
print(" Provide path: AuditTool.exe -p audit_cis_windows.ps1")
|
||||
print(" Or place {} in the same directory.".format(BUNDLED_SCRIPT) + Fore.RESET)
|
||||
return
|
||||
|
||||
print(" Source : {}".format(script_path))
|
||||
|
||||
# Step 1 – Read PowerShell script
|
||||
print(" [1/4] Reading script...")
|
||||
try:
|
||||
with open(script_path, "r", encoding="utf-8") as fh:
|
||||
ps_content = fh.read()
|
||||
except Exception as e:
|
||||
print(Fore.RED + " FAILED: {}".format(e) + Fore.RESET)
|
||||
return
|
||||
print(" [1/4] OK - {} bytes".format(len(ps_content)))
|
||||
|
||||
# Step 2 – Execute PowerShell
|
||||
print(" [2/4] Running PowerShell...")
|
||||
stdout, stderr, rc = run_powershell_script(ps_content)
|
||||
if rc != 0:
|
||||
print(Fore.YELLOW + " [2/4] PowerShell rc={}: {}".format(rc, (stderr or "")[:200]) + Fore.RESET)
|
||||
else:
|
||||
print(" [2/4] OK - {} output lines".format(len(stdout.splitlines())))
|
||||
|
||||
# Step 3 – Combine
|
||||
full_output = stdout
|
||||
if stderr:
|
||||
full_output += "\n[STDERR]\n" + stderr
|
||||
if not full_output.strip():
|
||||
print(Fore.RED + "ERROR: No output from PowerShell" + Fore.RESET)
|
||||
return
|
||||
|
||||
passed, failed = count_pass_fail(full_output)
|
||||
print(" [3/4] Audit: {} PASSED / {} FAILED".format(passed, failed))
|
||||
|
||||
# Step 4 – Encrypt & save
|
||||
hostname_out, timestamp = extract_audit_info(stdout)
|
||||
out_filename = "{}_{}.txt.enc".format(hostname_out, timestamp)
|
||||
print(" [4/4] Encrypting -> {}".format(out_filename))
|
||||
|
||||
encrypted = encrypt_output_rsa(full_output)
|
||||
with open(out_filename, "w", encoding="utf-8") as fh:
|
||||
fh.write(encrypted)
|
||||
|
||||
if os.path.isfile(out_filename):
|
||||
print(Fore.GREEN + "=" * 70 + Fore.RESET)
|
||||
print(Fore.GREEN + " SUCCESS: {} | {} PASS / {} FAIL".format(
|
||||
out_filename, passed, failed) + Fore.RESET)
|
||||
print(Fore.GREEN + "=" * 70 + Fore.RESET)
|
||||
else:
|
||||
print(Fore.RED + " FAILED: Could not write output file" + Fore.RESET)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CLI
|
||||
# ----------------------------------------------------------
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Audit Hardening v2.0")
|
||||
parser.add_argument(
|
||||
"-p", "--path",
|
||||
help="Path to PowerShell .ps1 script (optional if bundled or in same dir)",
|
||||
default=None,
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = main()
|
||||
run_audit(args.path)
|
||||
Reference in New Issue
Block a user