273 lines
9.3 KiB
Python
273 lines
9.3 KiB
Python
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)
|